Nous avons eu l’occasion de participer au Midnight Flag CTF qui s’est déroulé dans la nuit du 15 au 16 avril.
Ce write-up abordera le challenge Basic Go Rev
proposé par Stinky
.
Le fichier
Une fois que nous avons téléchargé et décompressé l’archive zip nous obtenons un fichier nommé main
:
1
2
|
$ file main
main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=Dr3jOcC2drZkbVJK9eeY/aYcRCkAGnayJomBFlmZ9/7DOepaXw66VTewiqSf2c/WR20PqZ8xUmvXwS3-IeR, not stripped
|
Analyse statique
Etant donné que mes compétences en reverse sont proches du néant, je fais ce que je sais faire de mieux, c’est à dire un bon gros string
sur le binaire :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
$ strings main
[...]
runtime.reflectOffs
runtime.vdsoLinuxVersion
runtime.vdsoSymbolKeys
runtime.vdsoGettimeofdaySym
runtime.vdsoClockgettimeSym
go.itab.*errors.errorString,error
go.itab.*internal/reflectlite.rtype,internal/reflectlite.Type
go.itab.runtime.errorString,error
_cgo_init
_cgo_thread_start
_cgo_notify_runtime_init_done
_cgo_callers
_cgo_yield
_cgo_mmap
_cgo_munmap
_cgo_sigaction
runtime.mainPC
runtime.buildVersion.str
runtime.modinfo.str
type.*
runtime.textsectionmap
|
On observe dans les strings la présupposée fonction main.main
.
Pour y voir plus clair, j’ouvre le binaire avec IDA
. On peut observer que le flag semble être généré à la volé, les différents caractères sont concaténés dans une string à la fin de main_main
:
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
|
if ( (unsigned __int64)&v2 <= *(_QWORD *)(v0 + 16) )
runtime_morestack_noctxt();
((void (*)(void))loc_4555D4)();
sub_4556FA();
v6 = strconv_FormatInt();
v5 = strconv_FormatInt();
v4 = strconv_FormatInt();
v3 = strconv_FormatInt();
strconv_FormatInt();
v1 = ((__int64 (*)(void))loc_455636)();
v7 = v39;
v8 = v40;
v9 = v6;
v10 = 10LL;
v11 = v43;
v12 = v44;
v13 = v5;
v14 = 10LL;
v15 = v47;
v16 = v48;
v17 = v39;
v18 = v40;
v19 = v4;
v20 = 10LL;
v21 = v45;
v22 = v46;
v23 = v3;
v24 = 10LL;
v25 = v37;
v26 = v38;
v27 = v47;
v28 = v48;
v29 = v1;
v30 = 10LL;
v31 = v39;
v32 = v40;
v33 = v41;
v34 = v42;
v35 = v49;
v36 = v50;
runtime_concatstrings();
|
Dans runtime_concatstrings()
on peut observer le retour d’un résultat, probablement le flag :
Analyse dynamique
Maintenant que l’on sait ou chercher, nous alons utiliser GDB
pour obtenir le flag lors de sa génération.
Pour vérifier mon affirmation précédente, je liste les fonctions dans gdb avec info functions
:
1
2
3
4
5
6
7
8
|
(gdb) info functions
[...]
0x00000000004580a0 errors.init
0x0000000000458120 strconv.FormatInt
0x0000000000458220 strconv.formatBits
0x0000000000458740 strconv.init
0x0000000000458820 main.main
0x0000000000458b4b runtime.etext
|
On retrouve bien notre fonction main
à l’adresse 0x0000000000458820
. Afin d’y voir plus claire on désassemble la fonction :
1
2
3
4
5
6
7
8
9
10
11
12
13
|
(gdb) disass 0x0000000000458820
[...]
0x0000000000458b17 <+759>: lea 0x40(%rsp),%rax
0x0000000000458b1c <+764>: lea 0xd0(%rsp),%rbx
0x0000000000458b24 <+772>: mov $0x13,%ecx
0x0000000000458b29 <+777>: mov %rcx,%rdi
0x0000000000458b2c <+780>: call 0x445f20 <runtime.concatstrings>
0x0000000000458b31 <+785>: mov 0x3a0(%rsp),%rbp
0x0000000000458b39 <+793>: add $0x3a8,%rsp
0x0000000000458b40 <+800>: ret
0x0000000000458b41 <+801>: call 0x4549a0 <runtime.morestack_noctxt.abi0>
0x0000000000458b46 <+806>: jmp 0x458820 <main.main>
End of assembler dump.
|
L’instruction ret
correspond au retour du résultat que nous avons vu dans IDA
juste avant.
On met alors un breakpoint
sur cette adresse et on éxecute le binaire :
1
2
3
|
(gdb) b *0x0000000000458b40
Breakpoint 1 at 0x458b40
(gdb) run
|
L’execution se stoppe bien à notre breakpoint, on regarde alors du côté des registres :
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
|
Thread 1 "main" hit Breakpoint 1, 0x0000000000458b40 in main.main ()
(gdb) info registers
rax 0xc000186c10 824635321360
rbx 0x17 23
rcx 0xc000186c26 824635321382
rdx 0x13 19
rsi 0xc000186c27 824635321383
rdi 0x13 19
rbp 0xc000186fd0 0xc000186fd0
rsp 0xc000186f78 0xc000186f78
r8 0xc000186dc0 824635321792
r9 0x9 9
r10 0x1 1
r11 0x0 0
r12 0x4663b4 4613044
r13 0x8 8
r14 0xc0000061a0 824633745824
r15 0x7fffd0c94500 140736696239360
rip 0x458b40 0x458b40 <main.main+800>
eflags 0x206 [ PF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
|
On peut alors afficher le contenu se situant à l’adresse présente dans le registre rax
:
1
2
|
(gdb) x/s 0xc000186c10
0xc000186c10: "MCTF{g0l4ng_4_m1dn1ght}"
|
On peut alors valider le challenge avec le flag suivant : MCTF{g0l4ng_4_m1dn1ght}
.
Conclusion
Le challenge permet d’aborder du reverse sans pour autant que ça soit son domaine de prédilection, cela le rend appréciable et cela m’a surtout permis de faire des points en étant rincé en reverse.