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,否则没办法进行加壳压缩
Linux逆向之加壳&脱壳
这里可以看到upx的用法,一般来说./upx test,即可快速把test加壳并且直接修改test源文件
如果加上-d参数表示进行脱壳,参数-1 ~ -9表示压缩的等级,1为最低级压缩同时也是最快的,而9表示最高等级压缩同时比较耗时,参数-k则表示保留原程序的备份
upx加壳之后可以看到程序能正常运行
Linux逆向之加壳&脱壳
加壳到底改变了源程序的哪些东西?
可以通过readelf –hlS --w test来查看elf文件在加壳前的情况
Linux逆向之加壳&脱壳
而加壳后:
Linux逆向之加壳&脱壳
对比发现,upx压缩之后的elf头部信息,段表信息全都发生了变化,把大量的有关elf的信息全部去除了,只留下一个upx壳自定义的程序入口执行地址
但这种加壳毕竟是直接用工具一键加壳的,同样的用该工具很容易就能被脱壳,并且很容易被发现是upx壳
Linux逆向之加壳&脱壳
可以看到,这里用checksec和strings都能轻易识别出upx的壳
因此为了让upx加壳过的程序没那么轻易被识别,我们把程序扔进010editor里面进行修改,把相关字符串和UPX关键词都给patch掉,然后再来康康
Linux逆向之加壳&脱壳
这时会发现,不但检测不到是upx壳,而且upx也没办法进行脱壳
这时就需要我们进行手动脱壳了,加壳的本质就是把原来的程序的数据全部压缩加密了,在静态文件中无法分析,随着程序的执行,运行时会将代码释放到内存中
我这里用的方法是用ida远程调试test程序,找到upx自解壳后的 OEP,再把内存给dump出来,就可以实现手动脱壳了
首先ida远程连接Ubuntu的中的test需要一个 linux_server64 远程服务器,这个在ida目录的dbgsrv文件夹下就有
将其复制到Ubuntu中,然后运行它
接着来到ida端,选择调试器:Remote Linux Debugger
Linux逆向之加壳&脱壳
以上的文件路径和ip地址均为远程端中的
设置完成后在start函数中下个断点,然后开始调试
Linux逆向之加壳&脱壳
进入调试界面后,可以看到程序的运行情况
Linux逆向之加壳&脱壳
然后一路f8步过执行,其中会遇到很多跳转循环,根据向下执行的原则不断跳过循环,最后来到这里jmp r13
Linux逆向之加壳&脱壳
会jmp到另外一个段上面去,这个段的名称这里显示的是test,这是因为这个段经过前面的mmap,mprotect等系统调用自己生成的一个段空间地址
Linux逆向之加壳&脱壳
到了这个新的段以后,遇到第一个call的时候就f7进入,然后继续f8一点点走下去
Linux逆向之加壳&脱壳
走到这的时候会发现有三个向上循环执行的语句,仍然按照向下跳过执行的方式去跳过这三个循环
Linux逆向之加壳&脱壳
跳过了这三个循环,就可以一路f8,最后会来到一个jmp语句,这里即将回到OEP,f7一下
Linux逆向之加壳&脱壳
继续f7
Linux逆向之加壳&脱壳
可以看到,执行的地址又回到了0x400890,这其实就是最开始未进行加壳的程序的起始函数 __start
他的第一个参数rdi指向的正是main函数
Linux逆向之加壳&脱壳
这个时候我们就找到了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,可以发现能够清楚的看清整个程序的反编译逻辑
Linux逆向之加壳&脱壳
复制回Ubuntu运行也仍然没有问题
Linux逆向之加壳&脱壳
有兴趣的研究一下upx实现源码的,可以康康下面的链接
参考链接 https://github.com/upx/upx
https://bbs.pediy.com/thread-255519.htm
源码分析相关:
https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=19345
|