Lleva un tiempo el blog parado, pero traigo algo interesante, veamos como
reversear un binario de los presentados en las quals del CTF de la No Con Name.
Aviso: Soy bastante novato en esto, así que seguro que se podrían obviar
muchos pasos o hacer más sencillo con los conocimientos y herramientas
adecuadas. Si sabes algo de esto, mejor ve ya a la parte curiosa ;).
El binario en cuestión es “inbincible”,
si lo ejecutamos produce el siguiente resultado:
Obviamente no es el que nos interesa, así que veamos lo que hace, abrimos con
gdb y buscamos una función desde la que empezar
| $ gdb -q ./inbincible
Reading symbols from ./inbincible...(no debugging symbols found)...done.
(gdb) break main<tab><tab>
main main.init main.main main.statictmp_0022 main.statictmp_0027
main.func main.initdone. main.statictmp_0019 main.statictmp_0024 main.statictmp_0030
|
Hay un main, así que empecemos por ahí
| (gdb) break main
Breakpoint 1 at 0x806dc20
(gdb) run
Starting program: /home/kenkeiras/ctf/2014/ncn/quals/inbincible/inbincible
Breakpoint 1, 0x0806dc20 in main ()
(gdb)
|
Veamos que hay por ahí...
| (gdb) disassemble
Dump of assembler code for function main:
=> 0x0806dc20 <+0>: jmp 0x806b890 <_rt0_go>
0x0806dc25 <+5>: add %al,(%eax)
0x0806dc27 <+7>: add %al,(%eax)
0x0806dc29 <+9>: add %al,(%eax)
0x0806dc2b <+11>: add %al,(%eax)
0x0806dc2d <+13>: add %al,(%eax)
0x0806dc2f <+15>: add %cl,%ch
End of assembler dump.
(gdb)
|
Parece que sigue por _rt0_go
(y nunca vuelve), podemos cambiar el breakpoint
a esa función
| (gdb) delete 1
(gdb) break _rt0_go
Breakpoint 2 at 0x806b890
(gdb) continue
Continuing.
Breakpoint 2, 0x0806b890 in _rt0_go ()
(gdb)
|
Y el código que contiene es:
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 | (gdb) disassemble
Dump of assembler code for function _rt0_go:
=> 0x0806b890 <+0>: mov 0x4(%esp),%eax
0x0806b894 <+4>: mov 0x8(%esp),%ebx
0x0806b898 <+8>: sub $0x80,%esp
0x0806b89e <+14>: and $0xfffffff0,%esp
0x0806b8a1 <+17>: mov %eax,0x78(%esp)
0x0806b8a5 <+21>: mov %ebx,0x7c(%esp)
0x0806b8a9 <+25>: mov $0x814e460,%ebp
0x0806b8ae <+30>: lea -0xff98(%esp),%ebx
0x0806b8b5 <+37>: mov %ebx,0x3c(%ebp)
0x0806b8b8 <+40>: mov %ebx,0x0(%ebp)
0x0806b8bb <+43>: mov %esp,0x4(%ebp)
0x0806b8be <+46>: xor %eax,%eax
0x0806b8c0 <+48>: cpuid
0x0806b8c2 <+50>: cmp $0x0,%eax
0x0806b8c5 <+53>: je 0x806b8da <_rt0_go+74>
0x0806b8c7 <+55>: mov $0x1,%eax
0x0806b8cc <+60>: cpuid
0x0806b8ce <+62>: mov %ecx,0x814e008
0x0806b8d4 <+68>: mov %edx,0x814e00c
0x0806b8da <+74>: mov 0x814df28,%eax
0x0806b8e0 <+80>: test %eax,%eax
0x0806b8e2 <+82>: je 0x806b905 <_rt0_go+117>
0x0806b8e4 <+84>: mov $0x806cfe0,%ebx
|
Y sigue hasta +270, pero aceleremos las cosas, no queremos que nos muestre ese
mensaje, no? que la función se llame _rt0_go
nos indica
que es un programa hecho en golang,
en ese lenguaje la función para printear es fmt.Print
, esperemos por el
programa ahí
| (gdb) break fmt.Print
Breakpoint 3 at 0x8071720
(gdb) continue
Continuing.
Breakpoint 3, 0x08071720 in fmt.Print ()
(gdb) bt
#0 0x08071720 in fmt.Print ()
#1 0x08048ebd in main.main ()
(gdb)
|
Volvemos a empezar, ahora esperando en la dirección que llama a fmt.Print
| (gdb) break *0x08048ebd
Breakpoint 4 at 0x8048ebd
(gdb) delete 2-3
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kenkeiras/ctf/2014/ncn/quals/inbincible/inbincible
N
Breakpoint 4, 0x08048ebd in main.main ()
(gdb)
|
Ahora tenemos que buscar que nos lleva ahí para poder evitarlo... el
desensamblado es grande, haciendo zoom en la parte donde quedó el programa
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102 | (gdb) disassemble
...
0x08048d9d <+413>: movl $0x6,0x88(%esp)
0x08048da8 <+424>: movl $0x6,0x8c(%esp)
0x08048db3 <+435>: mov 0x814e17c,%ebx
0x08048db9 <+441>: cmp $0x2,%ebx
0x08048dbc <+444>: je 0x8048ef1 <main.main+753>
0x08048dc2 <+450>: xor %ecx,%ecx
0x08048dc4 <+452>: mov 0x88(%esp),%ebp
0x08048dcb <+459>: cmp %ecx,%ebp
0x08048dcd <+461>: jle 0x8048ed1 <main.main+721>
0x08048dd3 <+467>: mov %ecx,%edx
0x08048dd5 <+469>: mov 0xa0(%esp),%ebx
0x08048ddc <+476>: mov %ecx,%eax
0x08048dde <+478>: cmp $0xffffffff,%ebx
0x08048de1 <+481>: je 0x8048eea <main.main+746>
0x08048de7 <+487>: cltd
0x08048de8 <+488>: idiv %ebx
0x08048dea <+490>: mov %edx,%ebp
0x08048dec <+492>: cmp 0xa0(%esp),%ebp
0x08048df3 <+499>: jae 0x8048ee3 <main.main+739>
0x08048df9 <+505>: mov 0x9c(%esp),%edx
0x08048e00 <+512>: lea (%edx,%ebp,1),%edx
0x08048e03 <+515>: movzbl (%edx),%edx
0x08048e06 <+518>: mov %ecx,0x28(%esp)
0x08048e0a <+522>: cmp 0x88(%esp),%ecx
0x08048e11 <+529>: jae 0x8048edc <main.main+732>
0x08048e17 <+535>: mov 0x84(%esp),%ebx
0x08048e1e <+542>: lea (%ebx,%ecx,1),%ebx
0x08048e21 <+545>: movzbl (%ebx),%ebx
0x08048e24 <+548>: mov %edx,%ebp
0x08048e26 <+550>: xor %ebx,%ebp
0x08048e28 <+552>: xchg %eax,%ebp
0x08048e29 <+553>: movzbl %al,%eax
0x08048e2c <+556>: xchg %eax,%ebp
0x08048e2d <+557>: xor %ecx,%ecx
0x08048e2f <+559>: mov %ebp,(%esp)
0x08048e32 <+562>: mov %ecx,0x4(%esp)
0x08048e36 <+566>: call 0x806a6b0 <runtime.intstring>
0x08048e3b <+571>: mov 0x8(%esp),%ebx
0x08048e3f <+575>: mov %ebx,0x74(%esp)
0x08048e43 <+579>: mov 0xc(%esp),%ebx
0x08048e47 <+583>: mov %ebx,0x78(%esp)
0x08048e4b <+587>: lea 0x7c(%esp),%edi
0x08048e4f <+591>: xor %eax,%eax
0x08048e51 <+593>: stos %eax,%es:(%edi)
0x08048e52 <+594>: stos %eax,%es:(%edi)
0x08048e53 <+595>: lea 0x7c(%esp),%ebx
0x08048e57 <+599>: cmp $0x0,%ebx
0x08048e5a <+602>: je 0x8048ed8 <main.main+728>
0x08048e5c <+604>: mov %ebx,0xa8(%esp)
0x08048e63 <+611>: movl $0x1,0xac(%esp)
0x08048e6e <+622>: movl $0x1,0xb0(%esp)
0x08048e79 <+633>: movl $0x80cc440,(%esp)
0x08048e80 <+640>: lea 0x74(%esp),%ebx
0x08048e84 <+644>: mov %ebx,0x4(%esp)
0x08048e88 <+648>: call 0x8065ed0 <runtime.convT2E>
0x08048e8d <+653>: mov 0xa8(%esp),%edi
0x08048e94 <+660>: lea 0x8(%esp),%ebx
0x08048e98 <+664>: mov %ebx,%esi
0x08048e9a <+666>: mov %edi,%edx
0x08048e9c <+668>: cld
0x08048e9d <+669>: movsl %ds:(%esi),%es:(%edi)
0x08048e9e <+670>: movsl %ds:(%esi),%es:(%edi)
0x08048e9f <+671>: mov %edx,(%esp)
0x08048ea2 <+674>: mov 0xac(%esp),%ebx
0x08048ea9 <+681>: mov %ebx,0x4(%esp)
0x08048ead <+685>: mov 0xb0(%esp),%ebx
0x08048eb4 <+692>: mov %ebx,0x8(%esp)
0x08048eb8 <+696>: call 0x8071720 <fmt.Print>
=> 0x08048ebd <+701>: mov 0x28(%esp),%ecx
0x08048ec1 <+705>: inc %ecx
0x08048ec2 <+706>: mov 0x88(%esp),%ebp
0x08048ec9 <+713>: cmp %ecx,%ebp
0x08048ecb <+715>: jg 0x8048dd3 <main.main+467>
0x08048ed1 <+721>: add $0xc0,%esp
0x08048ed7 <+727>: ret
0x08048ed8 <+728>: mov %eax,(%ebx)
0x08048eda <+730>: jmp 0x8048e5c <main.main+604>
0x08048edc <+732>: call 0x8055f40 <runtime.panicindex>
0x08048ee1 <+737>: ud2
0x08048ee3 <+739>: call 0x8055f40 <runtime.panicindex>
0x08048ee8 <+744>: ud2
0x08048eea <+746>: xor %ebp,%ebp
0x08048eec <+748>: jmp 0x8048dec <main.main+492>
0x08048ef1 <+753>: cmpl $0x1,0x814e17c
0x08048ef8 <+760>: jbe 0x804937d <main.main+1917>
0x08048efe <+766>: mov 0x814e178,%ebx
0x08048f04 <+772>: add $0x8,%ebx
0x08048f07 <+775>: mov 0x4(%ebx),%esi
0x08048f0a <+778>: mov 0x6c(%esp),%ebx
0x08048f0e <+782>: mov 0x4(%ebx),%ebp
0x08048f11 <+785>: cmp %ebp,%esi
0x08048f13 <+787>: je 0x8049048 <main.main+1096>
0x08048f19 <+793>: xor %ecx,%ecx
0x08048f1b <+795>: mov 0x88(%esp),%ebp
0x08048f22 <+802>: cmp %ecx,%ebp
0x08048f24 <+804>: jle 0x8049028 <main.main+1064>
---Type <return> to continue, or q <return> to quit---q
Quit
(gdb)
|
Podemos ver que por lo menos hasta +467 es parte del bucle, ¿que jump
se pudo tomar o evitar antes?
| 0x08048d9d <+413>: movl $0x6,0x88(%esp)
0x08048da8 <+424>: movl $0x6,0x8c(%esp)
0x08048db3 <+435>: mov 0x814e17c,%ebx
0x08048db9 <+441>: cmp $0x2,%ebx
0x08048dbc <+444>: je 0x8048ef1 <main.main+753>
0x08048dc2 <+450>: xor %ecx,%ecx
0x08048dc4 <+452>: mov 0x88(%esp),%ebp
0x08048dcb <+459>: cmp %ecx,%ebp
0x08048dcd <+461>: jle 0x8048ed1 <main.main+721>
|
El más cercano, que nos lleva a +721 provocará un ret
, que no es lo que
queremos
| 0x08048ed1 <+721>: add $0xc0,%esp
0x08048ed7 <+727>: ret
|
El siguiente, un je
nos lleva a +753 que parece que tiene más recorrido,
veamos el cmp
que lo precede
1
2
3
4
5
6
7
8
9
10
11
12 | (gdb) break *0x08048db9
Breakpoint 5 at 0x8048db9
(gdb) delete 4
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kenkeiras/ctf/2014/ncn/quals/inbincible/inbincible
Breakpoint 5, 0x08048db9 in main.main ()
(gdb) x/i $eip
=> 0x8048db9 <main.main+441>: cmp $0x2,%ebx
(gdb)
|
Se supone que debería dar true, y el valor de %ebx
es...
| (gdb) print $ebx
$1 = 1
(gdb)
|
Así que no, ahora, ¿cúal es el 1 que podemos haber metido en el programa?
¿El número de parámetros?
| (gdb) run test
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kenkeiras/ctf/2014/ncn/quals/inbincible/inbincible test
Breakpoint 5, 0x08048db9 in main.main ()
(gdb) print $ebx
$2 = 2
(gdb)
|
Parece que sí, por supuesto no es suficiente :P
| (gdb) continue
Continuing.
Nope!
[Inferior 1 (process 16416) exited normally]
(gdb)
|
Repetimos...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | (gdb) delete 5
(gdb) break fmt.Print
Breakpoint 6 at 0x8071720
(gdb) run
Starting program: /home/kenkeiras/ctf/2014/ncn/quals/inbincible/inbincible test
Breakpoint 6, 0x08071720 in fmt.Print ()
(gdb) bt
#0 0x08071720 in fmt.Print ()
#1 0x08049014 in main.main ()
(gdb) break *0x08049014
Breakpoint 7 at 0x8049014
(gdb) delete 6
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kenkeiras/ctf/2014/ncn/quals/inbincible/inbincible test
N
Breakpoint 7, 0x08049014 in main.main ()
(gdb)
|
Ahora nos quedamos aquí
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 | (gdb) disassemble
...
0x08048f07 <+775>: mov 0x4(%ebx),%esi
0x08048f0a <+778>: mov 0x6c(%esp),%ebx
0x08048f0e <+782>: mov 0x4(%ebx),%ebp
0x08048f11 <+785>: cmp %ebp,%esi
0x08048f13 <+787>: je 0x8049048 <main.main+1096>
0x08048f19 <+793>: xor %ecx,%ecx
0x08048f1b <+795>: mov 0x88(%esp),%ebp
0x08048f22 <+802>: cmp %ecx,%ebp
0x08048f24 <+804>: jle 0x8049028 <main.main+1064>
0x08048f2a <+810>: mov %ecx,%edx
0x08048f2c <+812>: mov 0xa0(%esp),%ebx
0x08048f33 <+819>: mov %ecx,%eax
0x08048f35 <+821>: cmp $0xffffffff,%ebx
0x08048f38 <+824>: je 0x8049041 <main.main+1089>
0x08048f3e <+830>: cltd
0x08048f3f <+831>: idiv %ebx
0x08048f41 <+833>: mov %edx,%ebp
0x08048f43 <+835>: cmp 0xa0(%esp),%ebp
0x08048f4a <+842>: jae 0x804903a <main.main+1082>
0x08048f50 <+848>: mov 0x9c(%esp),%edx
0x08048f57 <+855>: lea (%edx,%ebp,1),%edx
0x08048f5a <+858>: movzbl (%edx),%edx
0x08048f5d <+861>: mov %ecx,0x1c(%esp)
0x08048f61 <+865>: cmp 0x88(%esp),%ecx
0x08048f68 <+872>: jae 0x8049033 <main.main+1075>
0x08048f6e <+878>: mov 0x84(%esp),%ebx
0x08048f75 <+885>: lea (%ebx,%ecx,1),%ebx
0x08048f78 <+888>: movzbl (%ebx),%ebx
0x08048f7b <+891>: mov %edx,%ebp
0x08048f7d <+893>: xor %ebx,%ebp
0x08048f7f <+895>: xchg %eax,%ebp
0x08048f80 <+896>: movzbl %al,%eax
0x08048f83 <+899>: xchg %eax,%ebp
0x08048f84 <+900>: xor %ecx,%ecx
0x08048f86 <+902>: mov %ebp,(%esp)
0x08048f89 <+905>: mov %ecx,0x4(%esp)
0x08048f8d <+909>: call 0x806a6b0 <runtime.intstring>
0x08048f92 <+914>: mov 0x8(%esp),%ebx
0x08048f96 <+918>: mov %ebx,0x74(%esp)
0x08048f9a <+922>: mov 0xc(%esp),%ebx
0x08048f9e <+926>: mov %ebx,0x78(%esp)
0x08048fa2 <+930>: lea 0x7c(%esp),%edi
0x08048fa6 <+934>: xor %eax,%eax
0x08048fa8 <+936>: stos %eax,%es:(%edi)
0x08048fa9 <+937>: stos %eax,%es:(%edi)
0x08048faa <+938>: lea 0x7c(%esp),%ebx
0x08048fae <+942>: cmp $0x0,%ebx
0x08048fb1 <+945>: je 0x804902f <main.main+1071>
0x08048fb3 <+947>: mov %ebx,0xa8(%esp)
0x08048fba <+954>: movl $0x1,0xac(%esp)
0x08048fc5 <+965>: movl $0x1,0xb0(%esp)
0x08048fd0 <+976>: movl $0x80cc440,(%esp)
0x08048fd7 <+983>: lea 0x74(%esp),%ebx
0x08048fdb <+987>: mov %ebx,0x4(%esp)
0x08048fdf <+991>: call 0x8065ed0 <runtime.convT2E>
0x08048fe4 <+996>: mov 0xa8(%esp),%edi
0x08048feb <+1003>: lea 0x8(%esp),%ebx
0x08048fef <+1007>: mov %ebx,%esi
0x08048ff1 <+1009>: mov %edi,%edx
0x08048ff3 <+1011>: cld
0x08048ff4 <+1012>: movsl %ds:(%esi),%es:(%edi)
0x08048ff5 <+1013>: movsl %ds:(%esi),%es:(%edi)
0x08048ff6 <+1014>: mov %edx,(%esp)
0x08048ff9 <+1017>: mov 0xac(%esp),%ebx
0x08049000 <+1024>: mov %ebx,0x4(%esp)
0x08049004 <+1028>: mov 0xb0(%esp),%ebx
0x0804900b <+1035>: mov %ebx,0x8(%esp)
0x0804900f <+1039>: call 0x8071720 <fmt.Print>
=> 0x08049014 <+1044>: mov 0x1c(%esp),%ecx
0x08049018 <+1048>: inc %ecx
0x08049019 <+1049>: mov 0x88(%esp),%ebp
0x08049020 <+1056>: cmp %ecx,%ebp
0x08049022 <+1058>: jg 0x8048f2a <main.main+810>
0x08049028 <+1064>: add $0xc0,%esp
0x0804902e <+1070>: ret
0x0804902f <+1071>: mov %eax,(%ebx)
0x08049031 <+1073>: jmp 0x8048fb3 <main.main+947>
0x08049033 <+1075>: call 0x8055f40 <runtime.panicindex>
0x08049038 <+1080>: ud2
0x0804903a <+1082>: call 0x8055f40 <runtime.panicindex>
0x0804903f <+1087>: ud2
0x08049041 <+1089>: xor %ebp,%ebp
0x08049043 <+1091>: jmp 0x8048f43 <main.main+835>
0x08049048 <+1096>: movl $0x80ca7c0,(%esp)
0x0804904f <+1103>: call 0x80681a0 <runtime.new>
0x08049054 <+1108>: mov 0x4(%esp),%ebx
Quit
(gdb)
|
De nuevo otro bucle, siguiendo la misma estrategia buscaremos el jump anterior a
el bucle que nos sacaría de el, obviando el primero que nos lleva a un ret
, el
siguente es
| 0x08048f11 <+785>: cmp %ebp,%esi
0x08048f13 <+787>: je 0x8049048 <main.main+1096>
|
Vamos allá (no nos olvidemos del parámetro)
1
2
3
4
5
6
7
8
9
10
11
12 | (gdb) break *0x08048f11
Breakpoint 8 at 0x8048f11
(gdb) delete 7
(gdb) run test
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kenkeiras/ctf/2014/ncn/quals/inbincible/inbincible test
Breakpoint 8, 0x08048f11 in main.main ()
(gdb) x/i $eip
=> 0x8048f11 <main.main+785>: cmp %ebp,%esi
(gdb)
|
Los valores son...
| (gdb) print $ebp
$3 = (void *) 0x10
(gdb) print $esi
$4 = 4
(gdb)
|
Un 0x10 y un 4, y la comprobación anterior era el número de parámetros, y el que
habíamos probado tiene 4 caracteres, ¿uno de 16 será la solución?
| (gdb) run testtesttesttest
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kenkeiras/ctf/2014/ncn/quals/inbincible/inbincible testtesttesttest
Breakpoint 8, 0x08048f11 in main.main ()
(gdb) print $ebp
$7 = (void *) 0x10
(gdb) print $esi
$8 = 16
(gdb)
|
Toma un parámetro de 16 caracteres, vale, repetimos
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 | (gdb) delete 8
(gdb) continue
Continuing.
Nope!
[Inferior 1 (process 20465) exited normally]
(gdb) break fmt.Print
Breakpoint 9 at 0x8071720
(gdb) run testtesttesttest
Starting program: /home/kenkeiras/ctf/2014/ncn/quals/inbincible/inbincible testtesttesttest
[New LWP 20722]
[Switching to LWP 20722]
Breakpoint 9, 0x08071720 in fmt.Print ()
(gdb) bt
#0 0x08071720 in fmt.Print ()
#1 0x0804935a in main.main ()
(gdb) break *0x0804935a
Breakpoint 10 at 0x804935a
(gdb) delete 9
(gdb) run testtesttesttest
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kenkeiras/ctf/2014/ncn/quals/inbincible/inbincible testtesttesttest
N[New LWP 20800]
[Switching to LWP 20800]
Breakpoint 10, 0x0804935a in main.main ()
(gdb)
|
¿LWP?, ¡threads!, parece
que la cosa se complica, pero busquemos la condición
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 | ...
0x0804924e <+1614>: call 0x8055f40 <runtime.panicindex>
0x08049253 <+1619>: ud2
0x08049255 <+1621>: call 0x8055f40 <runtime.panicindex>
0x0804925a <+1626>: ud2
0x0804925c <+1628>: xor %ebp,%ebp
0x0804925e <+1630>: jmp 0x804915e <main.main+1374>
0x08049263 <+1635>: xor %ecx,%ecx
0x08049265 <+1637>: mov 0x88(%esp),%ebp
0x0804926c <+1644>: cmp %ecx,%ebp
0x0804926e <+1646>: jle 0x8049243 <main.main+1603>
0x08049270 <+1648>: mov %ecx,%edx
0x08049272 <+1650>: mov 0xa0(%esp),%ebx
0x08049279 <+1657>: mov %ecx,%eax
0x0804927b <+1659>: cmp $0xffffffff,%ebx
0x0804927e <+1662>: je 0x8049376 <main.main+1910>
0x08049284 <+1668>: cltd
0x08049285 <+1669>: idiv %ebx
0x08049287 <+1671>: mov %edx,%ebp
0x08049289 <+1673>: cmp 0xa0(%esp),%ebp
0x08049290 <+1680>: jae 0x804936f <main.main+1903>
0x08049296 <+1686>: mov 0x9c(%esp),%edx
0x0804929d <+1693>: lea (%edx,%ebp,1),%edx
0x080492a0 <+1696>: movzbl (%edx),%edx
0x080492a3 <+1699>: mov %ecx,0x2c(%esp)
0x080492a7 <+1703>: cmp 0x88(%esp),%ecx
0x080492ae <+1710>: jae 0x8049368 <main.main+1896>
0x080492b4 <+1716>: mov 0x84(%esp),%ebx
0x080492bb <+1723>: lea (%ebx,%ecx,1),%ebx
0x080492be <+1726>: movzbl (%ebx),%ebx
0x080492c1 <+1729>: mov %edx,%ebp
0x080492c3 <+1731>: xor %ebx,%ebp
0x080492c5 <+1733>: xchg %eax,%ebp
0x080492c6 <+1734>: movzbl %al,%eax
0x080492c9 <+1737>: xchg %eax,%ebp
0x080492ca <+1738>: xor %ecx,%ecx
0x080492cc <+1740>: mov %ebp,(%esp)
0x080492cf <+1743>: mov %ecx,0x4(%esp)
0x080492d3 <+1747>: call 0x806a6b0 <runtime.intstring>
0x080492d8 <+1752>: mov 0x8(%esp),%ebx
0x080492dc <+1756>: mov %ebx,0x74(%esp)
0x080492e0 <+1760>: mov 0xc(%esp),%ebx
0x080492e4 <+1764>: mov %ebx,0x78(%esp)
0x080492e8 <+1768>: lea 0x7c(%esp),%edi
0x080492ec <+1772>: xor %eax,%eax
0x080492ee <+1774>: stos %eax,%es:(%edi)
0x080492ef <+1775>: stos %eax,%es:(%edi)
0x080492f0 <+1776>: lea 0x7c(%esp),%ebx
0x080492f4 <+1780>: cmp $0x0,%ebx
0x080492f7 <+1783>: je 0x8049364 <main.main+1892>
0x080492f9 <+1785>: mov %ebx,0xa8(%esp)
0x08049300 <+1792>: movl $0x1,0xac(%esp)
0x0804930b <+1803>: movl $0x1,0xb0(%esp)
0x08049316 <+1814>: movl $0x80cc440,(%esp)
0x0804931d <+1821>: lea 0x74(%esp),%ebx
0x08049321 <+1825>: mov %ebx,0x4(%esp)
0x08049325 <+1829>: call 0x8065ed0 <runtime.convT2E>
0x0804932a <+1834>: mov 0xa8(%esp),%edi
0x08049331 <+1841>: lea 0x8(%esp),%ebx
0x08049335 <+1845>: mov %ebx,%esi
0x08049337 <+1847>: mov %edi,%edx
0x08049339 <+1849>: cld
0x0804933a <+1850>: movsl %ds:(%esi),%es:(%edi)
0x0804933b <+1851>: movsl %ds:(%esi),%es:(%edi)
0x0804933c <+1852>: mov %edx,(%esp)
0x0804933f <+1855>: mov 0xac(%esp),%ebx
0x08049346 <+1862>: mov %ebx,0x4(%esp)
0x0804934a <+1866>: mov 0xb0(%esp),%ebx
0x08049351 <+1873>: mov %ebx,0x8(%esp)
0x08049355 <+1877>: call 0x8071720 <fmt.Print>
=> 0x0804935a <+1882>: mov 0x2c(%esp),%ecx
0x0804935e <+1886>: inc %ecx
0x0804935f <+1887>: jmp 0x8049265 <main.main+1637>
---Type <return> to continue, or q <return> to quit---q
Quit
(gdb)
|
El bucle llega hasta +1637, pero hay algun jump incondicional y alguna
llamada nada prometedora a <runtime.panicindex>
, así que esta vez habrá
que buscar el jump que nos lleva aquí (por lo menos hasta +1635) y negarlo,
en vez de al revés.
Podemos encontrarlo en +1326
| 0x0804912b <+1323>: cmp $0x0,%cl
0x0804912e <+1326>: je 0x8049263 <main.main+1635>
|
Ahí encontramos el bucle que nos interesa
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
56
57
58
59
60
61
62
63
64 | ...
0x08049084 <+1156>: mov 0x6c(%esp),%ebx
0x08049088 <+1160>: mov 0x4(%ebx),%ebp
0x0804908b <+1163>: mov %esi,0x24(%esp)
0x0804908f <+1167>: cmp %esi,%ebp
0x08049091 <+1169>: jle 0x80490d6 <main.main+1238>
0x08049093 <+1171>: movl $0x80dfa60,(%esp)
0x0804909a <+1178>: call 0x80681a0 <runtime.new>
0x0804909f <+1183>: mov 0x4(%esp),%eax
0x080490a3 <+1187>: movl $0x80493c0,(%eax)
0x080490a9 <+1193>: mov 0x68(%esp),%edx
0x080490ad <+1197>: mov %edx,0x4(%eax)
0x080490b0 <+1200>: mov 0x6c(%esp),%edx
0x080490b4 <+1204>: mov %edx,0x8(%eax)
0x080490b7 <+1207>: mov 0x70(%esp),%edx
0x080490bb <+1211>: mov %edx,0xc(%eax)
0x080490be <+1214>: mov 0x24(%esp),%ebx
0x080490c2 <+1218>: mov %ebx,(%esp)
0x080490c5 <+1221>: push %eax
0x080490c6 <+1222>: push $0x4
0x080490c5 <+1221>: push %eax
0x080490c6 <+1222>: push $0x4
0x080490c8 <+1224>: call 0x805acf0 <runtime.newproc>
0x080490cd <+1229>: pop %ecx
0x080490ce <+1230>: pop %ecx
0x080490cf <+1231>: mov 0x24(%esp),%esi
0x080490d3 <+1235>: inc %esi
0x080490d4 <+1236>: jmp 0x8049084 <main.main+1156>
0x080490d6 <+1238>: mov $0x1,%ecx
0x080490db <+1243>: xor %edi,%edi
0x080490dd <+1245>: mov 0x6c(%esp),%ebx
0x080490e1 <+1249>: mov 0x4(%ebx),%ebp
0x080490e4 <+1252>: mov %edi,0x20(%esp)
0x080490e8 <+1256>: cmp %edi,%ebp
0x080490ea <+1258>: jle 0x804912b <main.main+1323>
0x080490ec <+1260>: cmp $0x0,%cl
0x080490ef <+1263>: je 0x8049127 <main.main+1319>
0x080490f1 <+1265>: movb $0x0,0x1b(%esp)
0x080490f6 <+1270>: movl $0x80ca7c0,(%esp)
0x080490fd <+1277>: mov 0x68(%esp),%ebx
0x08049101 <+1281>: mov (%ebx),%ebp
0x08049103 <+1283>: mov %ebp,0x4(%esp)
0x08049107 <+1287>: lea 0x1b(%esp),%ebx
0x0804910b <+1291>: mov %ebx,0x8(%esp)
0x0804910f <+1295>: call 0x8063620 <runtime.chanrecv1>
0x08049114 <+1300>: mov 0x20(%esp),%edi
0x08049118 <+1304>: cmpb $0x0,0x1b(%esp)
0x0804911d <+1309>: je 0x8049127 <main.main+1319>
0x0804911f <+1311>: mov $0x1,%ecx
0x08049124 <+1316>: inc %edi
0x08049125 <+1317>: jmp 0x80490dd <main.main+1245>
0x08049127 <+1319>: xor %ecx,%ecx
0x08049129 <+1321>: jmp 0x8049124 <main.main+1316>
-- 0x0804912b <+1323>: cmp $0x0,%cl
0x0804912e <+1326>: je 0x8049263 <main.main+1635>
0x08049134 <+1332>: xor %ecx,%ecx
0x08049136 <+1334>: mov 0x94(%esp),%ebp
0x0804913d <+1341>: cmp %ecx,%ebp
0x0804913f <+1343>: jle 0x8049243 <main.main+1603>
0x08049145 <+1349>: mov %ecx,%edx
0x08049147 <+1351>: mov 0xa0(%esp),%ebx
0x0804914e <+1358>: mov %ecx,%eax
0x08049150 <+1360>: cmp $0xffffffff,%ebx
0x08049153 <+1363>: je 0x804925c <main.main+1628>
|
La línea indicada es la de la comparación, parece que tenemos que mantener %cl
a algo que no sea 0.
A ese bucle se llega a través del salto a +1238 que se
puede ver al principio del listado, esa instrucción es
| 0x080490d6 <+1238>: mov $0x1,%ecx
|
Así que al principio %ecx
es 1, alguna instrucción lo pone a 0, una es
| 0x08049127 <+1319>: xor %ecx,%ecx
0x08049129 <+1321>: jmp 0x8049124 <main.main+1316>
|
Y llega ahí desde
| 0x0804910f <+1295>: call 0x8063620 <runtime.chanrecv1>
0x08049114 <+1300>: mov 0x20(%esp),%edi
0x08049118 <+1304>: cmpb $0x0,0x1b(%esp)
0x0804911d <+1309>: je 0x8049127 <main.main+1319>
|
El salto es dependiente de lo que parece el resultado de lo que se recibe por
un canal que se deja en 0x1b(%esp), y yo lo siento, no se debuggear hilos que
se interconectan y cosas así :(
Pero hagamos un último esfuerzo, eso es un bucle, con un poco de suerte hará una
pasada por cada letra... con un poco de suerte dirá si cada letra es correcta o
no y podamos ir resolviendolo haciendo fuerza bruta letra a letra, algo que es
bastante más asequible que atacar 16 (256**16 posibilidades en el peor de los casos).
¿O no suena mejor buscar uno entre 4096 que uno entre 340282366920938463463374607431768211456?
Por suerte gdb se puede automatizar facilmente, por ejemplo, si ponemos esto en un archivo
break *0x08049118
run testtesttesttest
x/1c ($esp) + 0x1b
Y le decimos a gdb que lo ejecute...
| $ gdb -batch -x man ./inbincible
Breakpoint 1 at 0x8049118
[New LWP 30150]
[Switching to LWP 30150]
Breakpoint 1, 0x08049118 in main.main ()
0xf7edeef3: 0 '\000'
$
|
Tachán! podemos ver que pasa en la memoria del programa, veamos si podemos
scriptearlo para que pruebe todas las letras, primero tenemos que generar los
archivos de control para cada password y añadiremos unos cuantos “continues”
para que siga después del primer resultado
1
2
3
4
5
6
7
8
9
10
11
12
13 | CONTROLLER_NAME = 'manipulator'
def gen_ctrl(passwd):
f = open(CONTROLLER_NAME, 'w')
# Breakpoint
f.write("break *0x08049118\n")
f.write("run %s\n" % passwd)
for i in xrange(128):
f.write("x/1c ($esp) + 0x1b\n")
f.write("continue\n")
f.close()
|
Lo siguiente será lanzar el gdb, ya aprovechamos que estamos en python para
hacer el tratamiento que haga falta
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 | PROGNAME = 'inbincible'
import subprocess
def test(passwd):
# Generamos el archivo de control
gen_ctrl(passwd)
# Lanzamos el gdb y tomamos control de su stdout y stderr
p = subprocess.Popen(["gdb", PROGNAME, '-batch', '-x', CONTROLLER_NAME],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
print passwd
# Buscaremos en el stdout las líneas con el dump de la memoria
# que son las que siguen al aviso de que se llegó a un breakpoint
o = p.stdout.read()
nxt = False
for l in o.split('\n'):
if nxt:
print l
nxt = False
if 'Breakpoint 1, 0x08049118 in main.main ()' in l:
nxt = True
|
Bien, eso debería mostrar las líneas con la información que nos interesa
por ahora veamos que tal va y ya parsearemos y tal luego
| import string
CHARS = string.printable.strip()
for c in CHARS:
test(c * 16)
|
A ver, ¿algo dará 1 o toca debuggear hilos?
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 | $ python test.py
aaaaaaaaaaaaaaaa
0xf7edeef3: 0 '\000'
bbbbbbbbbbbbbbbb
0xf7edeef3: 0 '\000'
cccccccccccccccc
0xf7edeef3: 0 '\000'
dddddddddddddddd
0xf7edeef3: 0 '\000'
eeeeeeeeeeeeeeee
0xf7edeef3: 0 '\000'
ffffffffffffffff
0xf7edeef3: 0 '\000'
gggggggggggggggg
0xf7edeef3: 0 '\000'
hhhhhhhhhhhhhhhh
0xf7edeef3: 0 '\000'
iiiiiiiiiiiiiiii
0xf7edeef3: 0 '\000'
jjjjjjjjjjjjjjjj
0xf7edeef3: 0 '\000'
kkkkkkkkkkkkkkkk
0xf7edeef3: 0 '\000'
llllllllllllllll
0xf7edeef3: 0 '\000'
mmmmmmmmmmmmmmmm
0xf7edeef3: 0 '\000'
nnnnnnnnnnnnnnnn
0xf7edeef3: 0 '\000'
oooooooooooooooo
0xf7edeef3: 0 '\000'
pppppppppppppppp
0xf7edeef3: 0 '\000'
qqqqqqqqqqqqqqqq
0xf7edeef3: 0 '\000'
rrrrrrrrrrrrrrrr
0xf7edeef3: 0 '\000'
ssssssssssssssss
0xf7edeef3: 0 '\000'
tttttttttttttttt
0xf7edeef3: 0 '\000'
uuuuuuuuuuuuuuuu
0xf7edeef3: 0 '\000'
vvvvvvvvvvvvvvvv
0xf7edeef3: 0 '\000'
wwwwwwwwwwwwwwww
0xf7edeef3: 0 '\000'
xxxxxxxxxxxxxxxx
0xf7edeef3: 0 '\000'
yyyyyyyyyyyyyyyy
0xf7edeef3: 0 '\000'
zzzzzzzzzzzzzzzz
0xf7edeef3: 0 '\000'
AAAAAAAAAAAAAAAA
0xf7edeef3: 0 '\000'
BBBBBBBBBBBBBBBB
0xf7edeef3: 0 '\000'
CCCCCCCCCCCCCCCC
0xf7edeef3: 0 '\000'
DDDDDDDDDDDDDDDD
0xf7edeef3: 0 '\000'
EEEEEEEEEEEEEEEE
0xf7edeef3: 0 '\000'
FFFFFFFFFFFFFFFF
0xf7edeef3: 0 '\000'
GGGGGGGGGGGGGGGG
0xf7edeef3: 1 '\001'
0xf7edeef3: 0 '\000'
HHHHHHHHHHHHHHHH
0xf7edeef3: 0 '\000'
IIIIIIIIIIIIIIII
0xf7edeef3: 0 '\000'
JJJJJJJJJJJJJJJJ
0xf7edeef3: 0 '\000'
KKKKKKKKKKKKKKKK
0xf7edeef3: 0 '\000'
LLLLLLLLLLLLLLLL
0xf7edeef3: 0 '\000'
MMMMMMMMMMMMMMMM
0xf7edeef3: 0 '\000'
NNNNNNNNNNNNNNNN
0xf7edeef3: 0 '\000'
OOOOOOOOOOOOOOOO
0xf7edeef3: 0 '\000'
PPPPPPPPPPPPPPPP
0xf7edeef3: 0 '\000'
QQQQQQQQQQQQQQQQ
0xf7edeef3: 0 '\000'
RRRRRRRRRRRRRRRR
0xf7edeef3: 0 '\000'
SSSSSSSSSSSSSSSS
0xf7edeef3: 0 '\000'
TTTTTTTTTTTTTTTT
0xf7edeef3: 0 '\000'
UUUUUUUUUUUUUUUU
0xf7edeef3: 0 '\000'
VVVVVVVVVVVVVVVV
0xf7edeef3: 0 '\000'
WWWWWWWWWWWWWWWW
0xf7edeef3: 0 '\000'
XXXXXXXXXXXXXXXX
0xf7edeef3: 0 '\000'
YYYYYYYYYYYYYYYY
0xf7edeef3: 0 '\000'
ZZZZZZZZZZZZZZZZ
...
|
¡Sí!
| GGGGGGGGGGGGGGGG
0xf7edeef3: 1 '\001'
0xf7edeef3: 0 '\000'
|
Y no hace falta ni parsear si sigue pasando por ahí es que vamos bien, podemos
simplemente contar las veces que se pasa por el breakpoint :D, hacemos un par
de arreglos, en test()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | def test(passwd):
# Generamos el archivo de control
gen_ctrl(passwd)
# Lanzamos el gdb y tomamos control de su stdout y stderr
p = subprocess.Popen(["gdb", PROGNAME, '-batch', '-x', CONTROLLER_NAME],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# Buscaremos en el stdout las líneas con el dump de la memoria
# que son las que siguen al aviso de que se llegó a un breakpoint
o = p.stdout.read()
nxt = False
i = -1
for l in o.split('\n'):
if nxt:
i += 1
nxt = False
if 'Breakpoint 1, 0x08049118 in main.main ()' in l:
nxt = True
return i
|
En el bucle principal
| CHARS = string.printable.strip()
best = None
top = 0
for c in CHARS:
result = test(c * 16)
if result > top:
best = c
top = result
print "Best:", best
print "With" , top, "matches"
|
El resultado es el que habíamos encontrado antes, claro, la primera letra es:
$ python test.py
Best: G
With 1 matches
$
Ahora podemos hacer que busque el resto, si repetimos ese bucle para cada letra
guardando las anteriores...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | matches = []
for i in xrange(16):
best = None
top = 0
for c in CHARS:
result = test(''.join(matches) + c * (16 - len(matches)))
if result > top:
best = c
top = result
# ↓decoración, eliminar si se ve mal
print "Current:", ''.join(matches) + '\x1b[1m' + best + '\x1b[0m'
matches.append(best)
print "Best:", best
print "With" , top, "matches"
print "Resultado: ", ''.join(matches)
|
Y ahí va...
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 | $ python test.py
Current: G
Best: G
With 1 matches
Current: G0
Best: 0
With 2 matches
Current: G0w
Best: w
With 3 matches
Current: G0w1
Best: 1
With 4 matches
Current: G0w1n
Best: n
With 5 matches
Current: G0w1n!
Best: !
With 6 matches
Current: G0w1n!C
Best: C
With 7 matches
Current: G0w1n!C0
Best: 0
With 8 matches
Current: G0w1n!C0n
Best: n
With 9 matches
Current: G0w1n!C0ng
Best: g
With 10 matches
Current: G0w1n!C0ngr
Best: r
With 11 matches
Current: G0w1n!C0ngr4
Best: 4
With 12 matches
Current: G0w1n!C0ngr4t
Best: t
With 13 matches
Current: G0w1n!C0ngr4t5
Best: 5
With 14 matches
Current: G0w1n!C0ngr4t5!
Best: !
With 15 matches
Current: G0w1n!C0ngr4t5!0
Best: 0
With 15 matches
Resultado: G0w1n!C0ngr4t5!0
$
|
“G0w1n!C0ngr4t5!0”, lo probamos y ...
| $ ./inbincible 'G0w1n!C0ngr4t5!0'
Nope!
$
|
¿¡Qué!?
Bien, resulta que el último caracter no lo saca bien por que da igual para el
número de iteraciones (lo que tiene ahorrarse medio minuto contando líneas en
vez de buscar unos :P), si hacemos el bucle como debiera ser...
1
2
3
4
5
6
7
8
9
10
11
12 | i = 0
for l in o.split('\n'):
if nxt:
if '\\001' in l:
i += 1
nxt = False
if 'Breakpoint 1, 0x08049118 in main.main ()' in l:
nxt = True
return i
|
Acabamos con el resultado real
| $ python test.py
Current: G
Best: G
With 1 matches
...
Current: G0w1n!C0ngr4t5!!
Best: !
With 16 matches
Resultado: G0w1n!C0ngr4t5!!
$
|
Y ahora sí
| $ ./inbincible 'G0w1n!C0ngr4t5!!'
Yeah!
$
|