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