Contents

🕵️ Midnight Flag CTF : Basic Go Rev

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.

drawing

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 :

1
return result;

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.

drawing