首页
社区
课程
招聘
[原创][新手向] 使用AFL来fuzz upx
发表于: 2019-1-28 17:14 25702

[原创][新手向] 使用AFL来fuzz upx

2019-1-28 17:14
25702

AFL可以对有源码和无源码的程序进行fuzz。对有源码的程序Fuzz的原理,简单来说即是在程序编译时,向汇编代码中插入自己的指令,从而在程序运行时,计算覆盖率。当把样本喂给程序来Fuzz时,如果AFL发现程序执行了新的路径,就把当前的样本保存在Queue中,基于这个新的样本来继续Fuzz。

下载和安装AFL:

安装完成后,默认在/usr/local/bin/目录下,修改.bashrc加入环境变量export PATH=/usr/local/bin:$PATH后即可使用。

这里以一个demo实践一下afl的用法,首先编写一个需要fuzz的程序,让AFL去fuzz,查看fuzz的效果。源码:

编译代码:

首先验证一下程序的功能完好:打开一个内容为afl!的文件时,程序退出了。

image-20181225153050613

接下来我们创建in和out目录,在in目录中随机生成一些数据,这些样本用作AFL的输入。

image-20181225153635294

使用AFL开始fuzz,指定输入目录in和输出目录out,@@用于替换输入的参数。

运行后,AFL的fuzz界面如下:

image-20181225151615247

我们可以看到得到了两个crash,内容见下面的图片。

image-20181225151734234

第一个afl!明显符合我们的预期,第二个经过调试得知是由于溢出导致的。(为了不影响行为流畅,分析过程请参考文末栈溢出分析。)

了解了基本用法之后,选择一些实际的,易出crash的软件来fuzz。这里以upx为例。

upx是一个不同可执行文件的打包软件,最新版本截至26 Aug 2018是3.95。

ubuntu源中的upx通过apt-cache showpkg upx看到版本是3.9.1的,我们直接下载git中的最新版本来安装。

在ubuntu16.04的docker中下载最新版本的和编译安装,步骤如下:

编译和安装的脚本如下:

编译完成后,查看upx的版本如下:

image-20181225162108885

在fuzz之前,为了实现更多的的代码覆盖率,我们需要收集一些不同的binary来放到afl的输入目录。AFL的样本选择对于Fuzz的效果至关重要,如果样本太大会导致AFLfuzz时速度很慢,最好小于1KB。

fuzz之前设置相关的参数:

开始fuzz:

image-20181225171517993

不到半小时不到出了5个crash(后续还出了很多crash,但是经过分析后发现大部分类型相同):

image-20181225185807754

用upx打开发现可以稳定复现:

image-20181225174104204

剩下的几个crash gdb挂上去后,发现都是同一类型的crash。因此挑一个来分析一下。运行后发现存在OOB read:

image-20181225190738392

image-20181225190806943

栈回溯一下看一下调用链:

image-20181225190624003

RSI的值是哪里来的呢?回溯分析一下上层的函数,发现RSI = R9+R14*4:

image-20181225193836696

继续看汇编可能有点枯燥了,好在是有源码的,调用链都有了,直接上源码分析吧。

源码在upx/src/p_lx_elf.c文件中,crash时的调用链如下:PackLinuxElf32help1->elf_lookup->get_te32->getle32,深入的看一下PackLinuxElf32help1和elf_lookup部分的代码来看一下OOB read的原因。

upx程序由于需要将ELF文件压缩,因此程序会将ELF文件映射到内存空间后再解析。在PackLinuxElf32help1函数中获取了dynstr,dynsym,gashtab,hashtab的地址后,调用elf_lookup来查找JNI_OnLoad的符号地址:

在elf_lookup函数源码如下,根据栈回溯信息可以知道crash的点在unsigned const w = get_te32(&bitmask[(n_bitmask -1) & (h>>5)]);。在第二个if判断分支中,n_bitmask是从gashtab中获取的,bitmask是&gashtab+0x10,因此严重怀疑n_bitmask这个从ELF的gashtab中获取的值导致的OOB read,具体调试来看一下。

在jni_onload_sym = elf_lookup("JNI_OnLoad");设置一下断点:

image-20181225195254318

跟进elf_lookup函数,gashtab的地址保存在rdi,是0x7ffff7fd8114。获取bitmask的值,保存在r9中,执行后r9的值是0x7ffff7fd8124:

image-20181226141132028

继续执行我们可以看到获取buckets时,n_bitmask的值为0xe9cd4b0c:

image-20181226141547194

n_bitmask的值之前猜测是从ELF文件中获得的,也就可能是AFL随机生成的。我们在ELF文件中搜索相应的值,可以发现映射到内存空间中的gashtab,验证了我们之前的想法。

image-20181226142351566

继续跟进,执行到源码中计算crash时的参数的地方unsigned const w = get_te32(&bitmask[(n_bitmask -1) & (h>>5)]);

image-20181226150624396

计算后,RSI的值就已经OOB的的了(如下图所示),如果继续执行就会发生复现crash时的越界读了,这就不重复粘图片了。

image-20181226151114641

分析出了crash的原因后,发现越界读似乎并不能达到什么效果。首先由于ELF中的gashtab可控导致bitmask可控,以当前程序为例,任意地址读的范围如下:

程序的虚拟地址空间如下。ELF文件被映射到图中0x7ffff7fc9000的区域,那么后面的内容,构造一下gashtab都是可以越界读的。

image-20181226160535160

在源码中,越界读之后的数据还是保存在局部变量中的,读了之后也并不能输出,更不用说越界写和控制PC寄存器了。所以只算是一个DoS吧。

后续我开了几十个AFL来对upx进行fuzz,收获了几百个crash,编写相关的脚本后对crash进行精简后,人工分析得到的crash大多还是不能利用,因此对于upx的fuzz暂时告一段落了。

补充一下第二部分中的栈溢出分析分析过程:

运行程序后,crash在这里:

image-20181225152741742

通过栈回溯信息可以看到,程序发生了栈溢出:

image-20181225152817190

 
 
 
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2019-4-5 21:35 被心许雪编辑 ,原因:
收藏
免费 7
支持
分享
最新回复 (12)
雪    币: 10868
活跃值: (3282)
能力值: (RANK:520 )
在线值:
发帖
回帖
粉丝
2
感谢分享!
2019-1-28 17:50
0
雪    币: 351
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
2019-1-29 10:57
0
雪    币: 403
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
2019-1-29 11:13
0
雪    币: 17
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
楼主,问一下,如果我想fuzz一个so文件呢,只有一个二进制的so文件,如何fuzz呢
2019-3-17 20:10
0
雪    币: 4366
活跃值: (353)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
6
小熊ppt 楼主,问一下,如果我想fuzz一个so文件呢,只有一个二进制的so文件,如何fuzz呢
抱歉,没fuzz过so。你可以调研分享一下。
对于库函数,有源码的话可以试试libfuzzer。

最后于 2019-4-5 21:13 被心许雪编辑 ,原因:
2019-4-5 21:11
0
雪    币: 13067
活跃值: (5728)
能力值: ( LV5,RANK:77 )
在线值:
发帖
回帖
粉丝
qux
7
楼主验证演示程序的功能时,应该是写错了。
因为演示程序是读参数作为输入,楼主那样的写法只是把文件名读了进去。
最后于 2019-4-14 19:36 被qux编辑 ,原因: 知道了错误原因
2019-4-10 00:01
0
雪    币: 13067
活跃值: (5728)
能力值: ( LV5,RANK:77 )
在线值:
发帖
回帖
粉丝
qux
8

貌似楼主把demo的源码搞错了
通过查看https://stfpeak.github.io/2017/06/11/Finding-bugs-using-AFL/的demo源码,结合本文章的demo源码,综合出一个可以达到效果的源码版本,需要的小伙伴请自取

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <string.h> 
#include <signal.h>


void vuln(char *buf) {
    int n = 0;
    if(buf[0] == 'a') n++;
    if(buf[1] == 'f') n++;
    if(buf[2] == 'l') n++;
    if(buf[3] == '!') n++;

    if(n == 4) {
    printf("awesome!\n");
        raise(SIGSEGV);
    }else{
        printf("wrong!\n");
    }
}


int main(int argc, char *argv[])
{
    char buf[40] = {0};
    FILE *input = NULL;
    input = fopen(argv[1], "r");
    if (input != 0)
    {
        fscanf(input, "%s", &buf);
        printf("buf is %s\n", buf);
        vuln(buf);
        fclose(input);
    }
    else
    {
        printf("bad file!");
    }
    return 0;
}
2019-4-17 23:59
0
雪    币: 111
活跃值: (726)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
1wc
9
小熊ppt 楼主,问一下,如果我想fuzz一个so文件呢,只有一个二进制的so文件,如何fuzz呢
用afl的qemu模式进行fuzz即可,但是需要先对.so文件进行逆向并编写harness,可以参考这篇对CS:GO进行fuzz的文章https://phoenhex.re/2018-08-26/csgo-fuzzing-bsp。
2019-7-3 20:13
0
雪    币: 8715
活跃值: (8619)
能力值: ( LV13,RANK:570 )
在线值:
发帖
回帖
粉丝
10
666,感谢分享!
2019-7-9 15:02
0
雪    币: 104
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
请问AFL产生的crash文件如何用GDB进行复现,找了好多博客都没有
2019-10-23 19:42
0
雪    币: 4366
活跃值: (353)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
12
让利润奔跑 请问AFL产生的crash文件如何用GDB进行复现,找了好多博客都没有
用gdb启动起程序,在程序运行之前设置好程序需要的参数(具体的命令搜索一下即可),之后run起来即可。
2020-4-19 13:02
0
雪    币: 603
活跃值: (376)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
13

能够再说得详细点吗?‘设置好程序需要的参数(具体的命令搜索一下即可)’是什么意思?可以的话,麻烦给个具体的例子

用gdb启动起程序,在程序运行之前设置好程序需要的参数(具体的命令搜索一下即可),之后run起来即可。

最后于 2020-8-7 17:17 被绥术编辑 ,原因:
2020-8-7 17:16
0
游客
登录 | 注册 方可回帖
返回
//