由一道ctf题目 对一个另类upx壳的一次探究
2020-03-22 12:35:33 Author: bbs.pediy.com(查看原文) 阅读量:453 收藏

新手的第一帖,献丑了,
有错误的地方师傅们在评论区直说就好,

最开始的起因是一个攻防世界的题目,这个题目使用的upx加壳,题目为easyre-150。bin文件附件里也有。

如果直接upx脱壳的话,题目基本没有啥难度,但是在题目做完以后我去翻看这个原本加壳后的文件发现很有趣。
一个稍不一般的upx-loader

于是便去简单的了解了一下。

目录

直接脱壳的方法

首先直接看到文件die查壳查到是upx_easyre150的壳,

然后直接脱壳,看到文件:

image-20200316220100932

在动调的时候我们可以轻松改变,走向got key的位置,

然后会进入函数lol, 

image-20200316220207201

大概十个这样子,注意判定跳转前的两句:

.text:080486B0                 mov     [ebp+var_C], 0
.text:080486B7                 cmp     [ebp+var_C], 1
.text:080486BB                 jnz     short loc_80486D3

这个其实永远不会进入到另一侧的语句运行,我们动调的时候改动下, 就可以看到flag,

当然,很坑的格式:要套上RCTF{},

image-20200316220602489

就是一个很简单的题目,

下面我们去考虑下手动脱壳的方法?

知识点:

在此之前,我们先写一下这一部分出现的知识点:

proc文件

linux 下的proc文件体系实际上并不是文件,而是我们在运行中的进程,每个进程都会产生一个由其运行的pid为文件名的文件, 其中,/proc/[pid]/目录下的文件:

  • cwd :

    这是一个符号链接,指向这个进程的运行目录。

  • exe :

    这也是个软链接, 指向这个进程对应的可执行文件。

  • fd :

    这是一个目录,里面包含进程打开的文件的文件描述符,并且这些描述符都是软链接,指向实际文件。

  • maps:

    这个文件包含当前进程在虚拟内存中的空间分布已经对应的地址,访问权限。

syscall

主要写一下里面出现的系统调用作用和调用号

32位 linux 程序系统调用 int 80

  • sys_open: eax=5, 打开文件, 并返回一个该文件的文件描述符。
  • sys_lseek: eax=19, 返回文件读写指针距离文件开头的字节大小。
  • sys_unlink: eax=10, 删除文件链接的意思,
  • sys_ftruncate:eax=93, 将文件截断,
  • sys_execve:eax=11,加载和启动新的程序
  • sys_munmap: eax=91, 映射内存

动调,手动处理壳:

那么就开始处理:

进入ida看到其实函数极少, 动调以后大致可以了解他们的内容, 这里简单进行了一下命名:

image-20200319105703388

start函数:

image-20200319105622157

这里就是函数入口处了,看起来没啥好看的,动调以后也会发现其实就没啥,进入main函数:

main:

main函数比较大,主要分成几个部分:

open '/proc/[pid]/exe'

image-20200319112400523

首先是获取pid, 然后组成一个"/proc/[pid]/exe"的一个字符串,打开这个文件,

image-20200319113543827

我们上述提到过,这个地址对应是一个链接,指向这个程序自身的绝对地址,

image-20200319113614971

简单点说就是打开自己这个程序本身。

同样我们也可以在 /proc/[pid]/fd/中看到

image-20200319113727241

open "AAAAAAAACDMVJO1A4NH"

后面的位置主要在两个循环中,处理了一个字符串:

image-20200319112801664

image-20200319113945492

将其处理为:"AAAAAAAACDMVJO1A4NH"

然后打开这个名字的文件,我们可以在/proc/[pid]/fd/中看到:

image-20200319114135638

注意两个文件的权限,

程序自身为r-x, 而新建的文件为-wx

但是我们同样也可以查到,这个是程序自身新建的一个文件,其内容为空:

image-20200319114238157

同时注意右上角时间标志,刚刚建立的文件,

以下使用elf代表文件本身,然后file代表这个新建立的文件

reada

程序内的一个函数,主要作用就是讲fd 的len个长度的内容写入到addr中,该是比较好理解,

image-20200319115208896

loader

在其后进入一个循环,这里除了一下系统调用以外主要是高亮起来的一个较复杂的函数,不出所料应该就是upx loader

大致如下:

image-20200319115902492

先是efl文件复制到一块地址空间,然后从这个addr进入loader去解压缩,最后写入到file中,

image-20200319115704069

我们查看:

image-20200319120107418

同样注意时间,这个是刚刚运行完write后写入的,

execve

应该是运行一次循环,然后就会跳出了, 进入判定,

image-20200319113129451

if语句中需要我们注意的是sys_execve函数,改变了运行的程序,

动调的时候会有问题:

image-20200319120612627

这个if语句是进不去的, 要验证的是"! ", 而实际运行后的位置是"UPX!", 这个是UPX压缩的标志, 我们直接去手动该下,让他进入if语句中运行:

if语句中,先是关闭两个文件,然后又打开了file文件的读取,如下示:

image-20200319121057288

但是这次是r-x权限了

然后复制了一段数据,使用sys_unlink,

这里我们要知道,unlink不会直接关闭一个文件, 会先检查下文件是否还在读写,当这个文件不进行读写的时候,再将其关闭,

于是我们的fd文件夹:

image-20200319121518044

然后执行sys_execve

注意这里的参数已经改为了:

image-20200319121705153

即,把切换运行的elf文件为我们的file文件,

然后我们执行:

image-20200319121816895

并且可以看到我们的动调的位置:

image-20200319121907725

已经是在运行我们的脱壳后的文件了。

接下来就是直接去查看那个AAAA..的文件,然后会发现和我们脱壳完了以后的文件是一致的。

另外要注意的, 这个unlink得调用,使得这个文件在函数运行完成后就会被删除,
妙啊

由于第二种方式的脱壳并不是我们常见的upx脱壳方式,我也觉得很是怪异,于是去进一步的看了一下upx,

包括以下:

die是如何查到upx的壳的?

一个正常的以前手动脱的壳应该是啥样?

upx加壳,这个加载器loader,是如何产生出来的?

die查壳的脚本

从die中找到的upx那个查壳的脚本:

// DIE's signature file

init("packer","UPX");

function getUPXOptions(nOffset)
{
    var nMethod=ELF.readByte(nOffset+2);
    var nLevel=ELF.readByte(nOffset+3);
    var sCompression="";
    switch(nMethod) // From http://sourceforge.net/p/upx/code/ci/default/tree/src/conf.h
    {
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:
    case 8:
    case 9:
    case 10: sCompression="NRV"; break;
    case 14: sCompression="LZMA"; break;
    case 15: sCompression="zlib"; break;
    }

    if(sCompression!="")
    {
        sOptions=sOptions.append(sCompression);
        if(nLevel==8)
        {
            sOptions=sOptions.append("best");
        }
        else
        {
            sOptions=sOptions.append("brute");
        }
    }
}

function detect(bShowType,bShowVersion,bShowOptions)
{
    var nSize=ELF.getSize();
    if(ELF.compare("'UPX!'",nSize-0x24))
    {
        getUPXOptions(nSize-0x20);
        bDetected=1;
    }
    else if(ELF.compareEP("E8........EB0E5A585997608A542420E9........60"))
    {
        sVersion="3.X";
        bDetected=1;
    }

    var nOffset=ELF.findString(0,nSize,"$Id: UPX");
    if(nOffset!=-1)
    {
        sVersion=ELF.getString(nOffset+9,4);
        bDetected=1;
    }

    return result(bShowType,bShowVersion,bShowOptions);
}

一个.sq后缀的文件,我也没怎么查到是要用啥语法高亮, 看着非常像c, 就用了c的高亮吧

啧, 括号分行写, 我觉得是c

然后看起来像是,用了个查找字符串 找那个"UPX"标志,然后还有个一大堆的, 但是"e8" ,应该是call的字节码吧,中间省略,后面估计也是字节码,应该是是为了查那个loader,

然后拿出010, 把这个题目文件的"UPX!"标志给去掉,

然后die查壳:

image-20200319123434545

没有查出来UPX了,

成功,那么这样的话, 题目就必须动调,而且还看不出来有壳, 难度就上升很多很多。

标准的一个upx加壳程序的 loader

另外我们看一下一个标准使用upx的程序应该有的loader,

我们随手拿来一个简单的文件进行加壳:

image-20200319124023916

然后看一下:

如果我们多看几个应该是差不多的结构,都是这些东西,

image-20200319124351214

说明, 这个loader是一个固定生成的, 只是call指令会有偏移,会重新计算,

这个时候,我们继续去使用010 将其中的"UPX!" 标志去除掉。

载入die, 发现仍然可以判断出来是upx,  估计是前面我们看到分析中间另一部分的那些"e8"之类的,的确是在查找loader,

这也证明了我们这个题目并不是标准的一个upx直接进行压缩的,估计应该是出题人自己写的,

另外那个我们动调中间判断失败也是由于出现upx标志,可能题目原本应该是没有这个标志的,应该是被改过吧,

简略翻看源码 loader的位置

另外upx是一个开源工具,在github我们可以找到他的源码,

另外17年2月有篇文章:i春秋-Tangerine-upx源码分析

大佬简单讲述了整个流程和最后产生loader的位置,

里面的一部分位置和现在的版本有些许差别,但是整体流程都是一致的,感兴趣的朋友可以看看。

目前我只是跟着这个文章的思路捋了一遍整个的流程,等下一步继续分析再写相关的文章吧。

主要的loader已经预设好在文件upx/src/stub/amd64-linux.elf-entry.h中,

image-20200319125717151

然后除了对于call指令进行矫正之类的, opcode和这里的数据基本一致,这里就不再赘述,

其实仔细的调试了下这个题目了解到了不少和linux内的一些东西,
且最后也发现了这个题目的前面的加载部分,应该是一个作者自己写的, 如果我们讲其中的upx标志去掉,还是一个比较有难度的题目,
而且我们也可以使用这样的框架,讲其中的loader函数部修改,实现另外一种加壳, 自己做出来一个稍微有难度的题目。

2020安全开发者峰会(2020 SDC)议题征集 中国.北京 7月!

最后于 1天前 被令则编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-258222.htm
如有侵权请联系:admin#unsafe.sh