参考链接:手动去upx特征_upx -d-CSDN博客
参考链接:linux 下 upx 脱壳笔记
参考链接:三叶草二进制招新培训
参考链接:如何用x64dbg UPX手动脱壳(64位)
upx壳简介
upx壳是一种压缩壳,在CTF比赛中比较常见。
针对upx壳有专业的脱壳工具。
当然我们也可以直接手脱,手动脱壳我们就需要找到加壳程序的OEP然后dump文件内存。
OEP原始程序入口点。EP(Entry Point),意即程序的入口点。而OEP是程序的原始入口点,一个正常的程序只有EP,只有入口点被修改的程序(加壳等),才会拥有OEP。
工具脱壳
UPX(Ultimate Packer for eXecutables)是一个开源的可执行文件压缩器,用于减小可执行文件的大小,同时保持其功能。UPX 支持多种操作系统,包括 Linux、Windows 和 macOS。
upx工具使用请参考这篇文章。
upx工具使用:【逆向】UPX工具使用及加壳_upx.exe-CSDN博客
常规upx工具脱壳
直接使用 upx -d 进行脱壳
#exe文件
upx -d demo.exe
#elf文件
upx -d demo
例题
[SWPUCTF 2023 秋季新生赛]UPX
分析
exeinfo查看发现upx加壳。
脱壳
直接使用upx -d进行脱壳。
可以看到脱壳成功。
exeinfo查看确认一下
确认脱壳成功
ida打开查看反编译代码,发现flag
upx魔改壳
常规upx壳只要利用upx工具直接执行upx -d命令即可脱壳。
不过只要做一些简单的修改就可以让upx工具失效。
这种魔改壳在CTF中很常见。
1.修改区段名
我们先来查看一下未修改前的文件信息。
可以看到图中显示了upx1和3.91 upx这两个特征标识。
然后查看一下加壳后的区段窗口,发现只有三个区段。最明显的特征upx0和upx1.
我们只需要修改它,就可以让upx工具无法脱壳。
我们利用二进制编辑工具将upx改成rpx
尝试脱壳
结果:
可以看到失败了。
只要改回原样就可以进行脱壳。
2.修改标识
可以看到3.96.UPX!特征码。我们可以修改3.96版本号开始24个字节的内容。都不会对程序运行产生影响。
修改标识之后,upx工具就无法脱壳。不过可以利用UPX Unpacker for Dummies工具进行脱壳也可以直接手脱。
后面我们会讲如何手动脱壳。
例题
[LitCTF 2024]hello_upx
分析
exe文件,查壳发现存在upx壳。
直接使用脱壳机,脱壳失败。
提示信息文件被修改,用010_Editor打开
发现有四处地方被修改
这里我们列出正常upx加壳文件的区段信息进行对比
UPX0和UPX1是加UPX壳后的两个区段名。其中UPX1区段包含了需要解压的数据块。
.rsrc是程序资源信息区段名,这个区段含有原资源段的完整头部以及图标、Manifest、版本等未被压缩的资源,当然还有UPX自身需要的导入信息等(如果程序自身不含资源段,加壳后就是UPX2)
分析修改
1. upx0被改成了小写
2. upx1被改成了小写
3. upx2被改成了小写
4. upx!被改成了小写
恢复
将upx0、upx1、upx2和upx!全部修改为大写
脱壳
可以看到脱壳成功。
分析代码逻辑
exp
根据代码逻辑构造exp
data=[0x4C,0x68,0x72,0x40,0x50,0x41,0x75,0x70,0x2B,0x63,0x59,0x25,0x61,0x58,0x51,0x65,0x20,0x4E,0x5A,0x1E,0x60,0x4E,0x5E,0x4F,0x65]
flag=""for i inrange(len(data)):
flag+=chr((data[i]+i))print(flag)#LitCTF{w3lc0me_t0_l1tctf}
手脱
exe
自己写的程序加壳,手脱。
入口不是pushad,只能一步一步单步步过。
pushad意味着upx壳解压缩代码的入口。
64位程序中没有puahad,而是用几个push汇编代码替代。32位程序中存在pushad。
遵循单步定律,向下跳转允许实现,向上跳转不允许实现。
向下的红色小箭头就是向下的跳转,线为红色即是跳转成立,线为白色就是跳转不成立。
同理向上的红色小箭头就是向上的跳转。
一直单步步过,直到发现这样的多个push指令为止。
根据esp定律下断点寻找OEP
f8单步执行一下,让rsp发送变化。
查看寄存器窗口,发现rsp产生变化
右击rsp,选中在栈中转到。
右击栈顶,选中断点,选择硬件访问断点,选择4字节。
设置硬件断点后f9运行。
之后发现下面有一个比较大幅度的jmp跳转,并且已经显示文件特征。
判断这个特征会跳转到OEP。
选中jmp指令,然后f4。
之后直接f8跳到oep
接下来dump文件
点击dump之后将文件保存。
但是这样dump后的文件是无法执行的,所以我们要修复文件。
修复文件
先点击 IAT Autosearch,再点击 Get Imports ,在 Imports 列表中右键delete删掉带有红叉的。
之后点击 Fix Dump 选中之前的Dump文件,修复成功。
选中之前dump的文件修复既可。
脱壳之后运行程序测试。
成功执行程序,dump文件成功。
exeinfo确认脱壳
发现仍然保留特征信息
ida打开确认
确认已脱壳
elf
elf脱壳的过程
寻找OEP
目标:找到原始程序入口地址(OEP)
从启动函数start开始设置断点,一步一步单步步过。
发现ret指令直接F4然后F7,直到发现endbr64指令(即源程序代码开头)。
endbr64是elf程序开头的汇编指令,即是OEP
然后查看程序代码,发现将函数地址送入rdi
判断函数地址为main函数地址
判断这是源程序代码,进入
c将数据解释为代码,p创建函数
f5反编译,得到main函数伪代码
dump内存
根据OEP利用脚本dump内存到文件,并修复运行。
例题
自己写一段代码编译加壳。
#include<stdio.h>intmain()1{char buf[10]={0};puts("input:");read(0,buf,10);printf("%s",buf);return0;}
编译,这里需要静态编译,要不然文件太小加不了壳。
加壳,可以看到加壳成功
exeinfo扫一下,可以看到显示upx壳
寻找oep
ida打开文件
直接在启动函数位置下断点,然后动态调试。
下翻,翻到ret指令直接f4运行到这里,然后f8。
重复以上过程,如果没找到ret指令就执行最近的jmp跳转。
当看到下面这个汇编中断代码时我们就快到OEP了。
之后f4运行到ret位置,之后f8单步。
跳转到OEP
看起来都是数据,按快捷键c将数据解释为代码。
之后看到汇编代码endbr64,elf64位程序的入口。
是由它来调用初始化程序,进而调用main函数。
这里被传地址给rdi的byte_401775就是原程序main函数。
进入main函数查看
按c解释为代码,然后在endbr64处按快捷键p创建函数。
之后就可以f5反编译了。
main函数反编译代码
CTF中一般就可以在这里分析程序代码解题了。
不过接下来我们要学习如何dump文件。
dump内存到文件
先回到OEP这里。
确保eip执向endbr64汇编代码。
然后,alt+f7快捷键打开并运行脚本。
dump64位程序选用64位文件(idc脚本代码下面有)。
执行,等待执行完毕。
执行完毕
dump文件的存放路径在脚本中设置。
执行文件
可执行,则dump文件成功。
exeinfo扫一下,确认是否脱壳。
显示无壳,则脱壳成功。
idc dump内存文件代码
64位程序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("G:\\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);
}
}
32位程序dump内存代码
#include <idc.idc>
#define PT_LOAD 1
#define PT_DYNAMIC 2
static main(void)
{
auto ImageBase,StartImg,EndImg; //基址 08048000
auto e_phoff;
auto e_phnum,p_offset; //paddr 0xc 地址,pmemsz ox14大小,p_offset 0x4
auto i,dumpfile;
ImageBase=0x08048000;
StartImg=0x08048000;
EndImg=0x0;
Message("%8x\n",Dword(ImageBase));
if (Dword(ImageBase)==0x7f454c46 || Dword(ImageBase)==0x464c457f )
{
if(dumpfile=fopen("G:\\dumpfile","wb"))//这里可以更改路径
{
e_phoff=ImageBase+Word(ImageBase+0x1c);
e_phnum=Word(ImageBase+0x2c);
for(i=0;i<e_phnum;i++)
{
if (Dword(e_phoff)==PT_LOAD || Dword(e_phoff)==PT_DYNAMIC)
{ p_offset=Dword(e_phoff+0x4);
StartImg=Dword(e_phoff+0xc);
EndImg=Dword(e_phoff+0xc)+Dword(e_phoff+0x14);
dump(dumpfile,StartImg,EndImg,p_offset);
Message("dump LOAD%d ok.\n",i);
}
e_phoff=e_phoff+0x20;
}
fseek(dumpfile,0x30,0);
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);
}
}
版权归原作者 南行* 所有, 如有侵权,请联系我们删除。