Linux中比较常见的壳是UPX,常用这个开源软件进行一键upx加壳和脱壳

这里做一个简单演示

#include <stdio.h>
void fun1()
{
    puts("fun1:hello,world!");
    sleep(1);
}
void fun2()
{
    puts("fun2:hello,world!");
    sleep(1);
}

int main(int argc, char const *argv[])
{
    puts("main:hello,world!");
    fun1();
    fun2();

    return 0;
}

gcc ./test.c -o test --static

得到一个静态编译的test可执行文件,这里之所以要用静态编译,是因为upx加壳需要使得文件大小大于40kb,否则没办法进行加壳压缩

这里可以看到upx的用法,一般来说./upx test,即可快速把test加壳并且直接修改test源文件

如果加上-d参数表示进行脱壳,参数-1 ~ -9表示压缩的等级,1为最低级压缩同时也是最快的,而9表示最高等级压缩同时比较耗时,参数-k则表示保留原程序的备份

upx加壳之后可以看到程序能正常运行

加壳到底改变了源程序的哪些东西?

可以通过readelf –hlS --w test来查看elf文件在加壳前的情况

而加壳后:

对比发现,upx压缩之后的elf头部信息,段表信息全都发生了变化,把大量的有关elf的信息全部去除了,只留下一个upx壳自定义的程序入口执行地址

但这种加壳毕竟是直接用工具一键加壳的,同样的用该工具很容易就能被脱壳,并且很容易被发现是upx壳

可以看到,这里用checksec和strings都能轻易识别出upx的壳

因此为了让upx加壳过的程序没那么轻易被识别,我们把程序扔进010editor里面进行修改,把相关字符串和UPX关键词都给patch掉,然后再来康康

这时会发现,不但检测不到是upx壳,而且upx也没办法进行脱壳

这时就需要我们进行手动脱壳了,加壳的本质就是把原来的程序的数据全部压缩加密了,在静态文件中无法分析,随着程序的执行,运行时会将代码释放到内存中

我这里用的方法是用ida远程调试test程序,找到upx自解壳后的 OEP,再把内存给dump出来,就可以实现手动脱壳了

首先ida远程连接Ubuntu的中的test需要一个 linux_server64 远程服务器,这个在ida目录的dbgsrv文件夹下就有

将其复制到Ubuntu中,然后运行它

接着来到ida端,选择调试器:Remote Linux Debugger

以上的文件路径和ip地址均为远程端中的

设置完成后在start函数中下个断点,然后开始调试

进入调试界面后,可以看到程序的运行情况

然后一路f8步过执行,其中会遇到很多跳转循环,根据向下执行的原则不断跳过循环,最后来到这里jmp r13

会jmp到另外一个段上面去,这个段的名称这里显示的是test,这是因为这个段经过前面的mmap,mprotect等系统调用自己生成的一个段空间地址

到了这个新的段以后,遇到第一个call的时候就f7进入,然后继续f8一点点走下去

走到这的时候会发现有三个向上循环执行的语句,仍然按照向下跳过执行的方式去跳过这三个循环

跳过了这三个循环,就可以一路f8,最后会来到一个jmp语句,这里即将回到OEP,f7一下

继续f7

可以看到,执行的地址又回到了0x400890,这其实就是最开始未进行加壳的程序的起始函数 __start

他的第一个参数rdi指向的正是main函数

这个时候我们就找到了OEP了,可以通过ida加载idc脚本的方式把当前的内存给dump出来

脚本如下

#include <idc.idc>
#define PT_LOAD              1
#define PT_DYNAMIC           2
static main(void)
{
         auto ImageBase,StartImg,EndImg;
         auto e_phoff;
         auto e_phnum,p_offset;
         auto i,dumpfile;
         ImageBase=0x400000;
         StartImg=0x400000;
         EndImg=0x0;
         if (Dword(ImageBase)==0x7f454c46 || Dword(ImageBase)==0x464c457f )
  {
    if(dumpfile=fopen("dumpfile","wb"))
    {
      e_phoff=ImageBase+Qword(ImageBase+0x20);
      Message("e_phoff = 0x%x\n", e_phoff);
      e_phnum=Word(ImageBase+0x38);
      Message("e_phnum = 0x%x\n", e_phnum);
      for(i=0;i<e_phnum;i++)
      {
         if (Dword(e_phoff)==PT_LOAD || Dword(e_phoff)==PT_DYNAMIC)
                         {
                                 p_offset=Qword(e_phoff+0x8);
                                 StartImg=Qword(e_phoff+0x10);
                                 EndImg=StartImg+Qword(e_phoff+0x28);
                                 Message("start = 0x%x, end = 0x%x, offset = 0x%x\n", StartImg, EndImg, p_offset);
                                 dump(dumpfile,StartImg,EndImg,p_offset);
                                 Message("dump segment %d ok.\n",i);
                         }
         e_phoff=e_phoff+0x38;
      }

      fseek(dumpfile,0x3c,0);
      fputc(0x00,dumpfile);
      fputc(0x00,dumpfile);
      fputc(0x00,dumpfile);
      fputc(0x00,dumpfile);

      fseek(dumpfile,0x28,0);
      fputc(0x00,dumpfile);
      fputc(0x00,dumpfile);
      fputc(0x00,dumpfile);
      fputc(0x00,dumpfile);
      fputc(0x00,dumpfile);
      fputc(0x00,dumpfile);
      fputc(0x00,dumpfile);
      fputc(0x00,dumpfile);

      fclose(dumpfile);
        }else Message("dump err.");
 }
}
static dump(dumpfile,startimg,endimg,offset)
{
        auto i;
        auto size;
        size=endimg-startimg;
        fseek(dumpfile,offset,0);
        for ( i=0; i < size; i=i+1 )
        {
        fputc(Byte(startimg+i),dumpfile);
        }
}

执行完这个脚本后,用ida打开dumpfile,可以发现能够清楚的看清整个程序的反编译逻辑

复制回Ubuntu运行也仍然没有问题

有兴趣的研究一下upx实现源码的,可以康康下面的链接

参考链接

https://github.com/upx/upx
https://bbs.pediy.com/thread-255519.htm

源码分析相关:
https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=19345

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖