Pwnable.tw start
程序链接:https://pwnable.tw/static/chall/start
0x01 检查保护情况
不得不说,checksec这个工作看似简单,用用现成工具就行,但这决定了我们之后漏洞利用的方式,是否栈代码执行,还是ROP。
最好多用几个工具进行检查,兼听则明。比如这个程序用peda检查就开启了NX,但实际上并没有。所以理想的话,把shellcode布置到栈上就可以了!
1 | $ checksec ./start |
RELRO(Relocation Read Only):尽量使存储区域只读
0x02 漏洞分析
用IDA逆向分析,汇编代码
1 | 保存现场环境esp、_exit |
INT 80h 系统调用方法
系统调用的过程可以总结如下:
1. 执行用户程序(如:fork)
2. 根据glibc中的函数实现,取得系统调用号并执行int $0x80产生中断。
3. 进行地址空间的转换和堆栈的切换,执行SAVE_ALL。(进行内核模式)
4. 进行中断处理,根据系统调用表调用内核函数。
5. 执行内核函数。
6. 执行RESTORE_ALL并返回用户模式
Linux 32位的系统调用时通过int 80h来实现的,eax寄存器中为调用的功能号,ebx、ecx、edx、esi等等寄存器则依次为参数。
1 | #define __NR_exit 1 |
第一个系统调用:
将esp开始的14h字节数据写入标准输出(文件描述符1),即输出”Let’s start the CTF:“
name | eax | ebx | ecx | edx |
---|---|---|---|---|
sys_write | 0x04 | unsigned int fd = 1 | const char __user *buf = esp | size_t count =14h |
— | — | — | — | — |
第二个系统调用:
从标准输入读取3ch字节到栈空间
name | eax | ebx | ecx | edx |
---|---|---|---|---|
sys_read | 0x03 | unsigned int fd = 1 | char __user *buf = esp | size_t count = 3ch |
— | — | — | — | — |
栈变化情况
- 程序执行到0804808F:sys_write
输出14h字节数据:Let’s start the CTF:
1 | +-----------------+ <---- |
- 08048097: sys_read
read函数最多可以读取3ch字节,超出了分配的空间,可以用来覆盖ret_addr和esp。经调试验证,20字节后覆盖ret,24字节后覆盖esp。
1 | gdb-peda$ pattern search |
1 | +-----------------+ <---- |
0x03 漏洞利用
利用思路
现在EIP已经在我们的掌控之中了,关键是如何跳转到布置的shell code中。一般来说,首先会去找JMP ESP指令,这样就能让shellcode获得执行。但这段汇编代码没有,可以利用的只有read和write。如果可以write出Saved ESP的地址,然后覆盖掉offset _exit,就能成功shell。
- 泄露Saved ESP
1 | start = p.recvuntil(':') //等待write执行完毕 |
debug过程:
1 | [DEBUG] Received 0x14 bytes: |
- 覆盖RET
此时程序已经泄露出之前的Saved_esp,栈的情况已经摸清了,然后程序继续执行read,注意read完 add esp, 14h后再ret,因此,ret_addr在esp+14h的地方。
1 | payload = 'a'*0x14 + p32(saved_esp + 20) + shellcode |
- shellcode
shellcode同样可以用系统调用的方式执行execve(“/bin/sh”,NULL,NULL)
name | eax | ebx | ecx | edx | esi |
---|---|---|---|---|---|
sys_execve | 0x0b | char __user * | char user *user * | char user *user * | struct pt_regs |
— | — | — | — | — | — |
其中,该程序是 32 位,所以我们需要使得
- 系统调用号,即 eax 应该为 0xb
- 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
- 第二个参数,即 ecx 应该为 0
- 第三个参数,即 edx 应该为 0
1 | xor eax,eax //清空eax |
1 | shellcode=''' |
使用asm(shellcode)来进行汇编,可以使用context来指定cpu类型以及操作系统,如context(arch = ‘amd64’ , os = ‘linux’, log_level=”debug”)
Catch THE FLAG
1 | from pwn import * |
1 | python ./start.py |
##
REF
pwntools使用
http://brieflyx.me/2015/python-module/pwntools-intro/
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/basic-rop-zh/
https://tianstcht.github.io/pwntools%E7%9A%84%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7/