面试的时候问到一些问题,之前一直没接触过,这几天在学校安排的培训空闲时间,用upx压缩一个exe来分析脱壳行为
UPX 3.94w+x64dbg
工具:Source Insight
目录文件夹结构如下
\UPX-3.08-SRC
├─doc
└─src
├─filter
└─stub
├─scripts
├─src
│ ├─arch
│ │ ├─amd64
│ │ ├─arm
│ │ │ ├─v4a
│ │ │ ├─v4t
│ │ │ └─v5a
│ │ ├─i086
│ │ ├─i386
│ │ ├─m68k
│ │ │ ├─m68000
│ │ │ └─m68020
│ │ ├─mips
│ │ │ └─r3000
│ │ └─powerpc
│ │ └─32
│ ├─c
│ └─include
└─tools
├─armpe
└─sstrip
从src内的main.cpp开始
main函数之前做了一些判断,看起来是关于upx版本和一些待压缩文件的判断,最后进入到do_files(),紧接着进入到do_one_files()
接着做基本的文件属性检查
int r;
struct stat st;
memset(&st, 0, sizeof(st));
#if (HAVE_LSTAT)
r = lstat(iname,&st);
#else
r = stat(iname,&st);
#endif
if (r != 0)
throw FileNotFoundException(iname);
if (!(S_ISREG(st.st_mode)))
throwIOException("not a regular file -- skipped");
#if defined(__unix__)
// no special bits may be set
if ((st.st_mode & (S_ISUID | S_ISGID | S_ISVTX)) != 0)
throwIOException("file has special permissions -- skipped");
#endif
if (st.st_size <= 0)
throwIOException("empty file -- skipped");
if (st.st_size >= 1024*1024*1024)
throwIOException("file is too large -- skipped");
if ((st.st_mode & S_IWUSR) == 0)
{
bool skip = true;
if (opt->output_name)
skip = false;
else if (opt->to_stdout)
skip = false;
else if (opt->backup)
skip = false;
if (skip)
throwIOException("file is write protected -- skipped");
}
最后打开打开IO文件流,准备进行压缩
先实例化一个类PackMaster
PackMaster pm(&fi, opt);
if (opt->cmd == CMD_COMPRESS)
pm.pack(&fo);
调用关系如上,调用顺序如上
其中getPacker()会寻找适合当前输入文件类型的一个packer方法,最终返回一个Packer实例,进入到doPack()
doPacke()是pack()的包装
pack内进行compress()调用,查看调用关系如下,进入到lzma算法的压缩
=============================================================
upx加载时的脱壳过程
01058E30 | pushal |
01058E31 | mov esi,up.1058000 | esi points at upx1
01058E36 | lea edi,dword ptr ds:[esi-7000] | since edi points at upx0
01058E3C | push edi |
01058E3D | jmp up.1058E4A | start extracting data
这部分用ebx来做是否进行循环的判断,同时有两个部分,上半部分是单字节的恢复,下半部分是4字节的恢复
01058E4A | mov ebx,dword ptr ds:[esi] | get loop key here?
01058E4C | sub esi,FFFFFFFC | esi+=4
01058E4F | adc ebx,ebx | check whether continue| seems like just one byte patch here
01058E51 | jb up.1058E40 |
01058E53 | mov eax,1 |
01058E58 | add ebx,ebx |
01058E5A | jne up.1058E63 |
01058E5C | mov ebx,dword ptr ds:[esi] |
01058E5E | sub esi,FFFFFFFC |
01058E61 | adc ebx,ebx |
01058E63 | adc eax,eax |
01058E65 | add ebx,ebx |
01058E67 | jae up.1058E58 |
01058E69 | jne up.1058E74 |
01058E6B | mov ebx,dword ptr ds:[esi] |
01058E6D | sub esi,FFFFFFFC |
01058E70 | adc ebx,ebx |
01058E72 | jae up.1058E58 |
01058E74 | xor ecx,ecx |
01058E76 | sub eax,3 |
01058E79 | jb up.1058E88 |
01058E7B | shl eax,8 |
01058E7E | mov al,byte ptr ds:[esi] |
01058E80 | inc esi |
01058E81 | xor eax,FFFFFFFF |
01058E84 | je up.1058EFA |
01058E86 | mov ebp,eax |
01058E88 | add ebx,ebx |
01058E8A | jne up.1058E93 |
01058E8C | mov ebx,dword ptr ds:[esi] |
01058E8E | sub esi,FFFFFFFC |
01058E91 | adc ebx,ebx |
01058E93 | adc ecx,ecx |
01058E95 | add ebx,ebx |
01058E97 | jne up.1058EA0 |
01058E99 | mov ebx,dword ptr ds:[esi] |
01058E9B | sub esi,FFFFFFFC |
01058E9E | adc ebx,ebx |
01058EA0 | adc ecx,ecx |
01058EA2 | jne up.1058EC4 |
01058EA4 | inc ecx |
01058EA5 | add ebx,ebx |
01058EA7 | jne up.1058EB0 |
01058EA9 | mov ebx,dword ptr ds:[esi] |
01058EAB | sub esi,FFFFFFFC |
01058EAE | adc ebx,ebx |
01058EB0 | adc ecx,ecx |
01058EB2 | add ebx,ebx |
01058EB4 | jae up.1058EA5 |
01058EB6 | jne up.1058EC1 |
01058EB8 | mov ebx,dword ptr ds:[esi] |
01058EBA | sub esi,FFFFFFFC |
01058EBD | adc ebx,ebx |
01058EBF | jae up.1058EA5 |
01058EC1 | add ecx,2 |
01058EC4 | cmp ebp,FFFFF300 |
01058ECA | adc ecx,1 |
01058ECD | lea edx,dword ptr ds:[edi+ebp] |
01058ED0 | cmp ebp,FFFFFFFC |
01058ED3 | jbe up.1058EE4 |
01058ED5 | mov al,byte ptr ds:[edx] |
01058ED7 | inc edx |
01058ED8 | mov byte ptr ds:[edi],al |
01058EDA | inc edi |
01058EDB | dec ecx |
01058EDC | jne up.1058ED5 |
01058EDE | jmp up.1058E46 |
01058EE3 | nop |
01058EE4 | mov eax,dword ptr ds:[edx] |
01058EE6 | add edx,4 |
01058EE9 | mov dword ptr ds:[edi],eax | 4 bytes patch here
01058EEB | add edi,4 |
01058EEE | sub ecx,4 |
01058EF1 | ja up.1058EE4 |
01058EF3 | add edi,ecx |
01058EF5 | jmp up.1058E46 |
01058EFA | pop esi | finish extracting from UPX1 to UPX0
开始进行call 函数地址的修复
遍历在上一部分修复至upx0部分的字节,寻找E8/E9(call)指令,然后修改call 之后的偏移
01058EFB | mov edi,esi |
01058EFD | mov ecx,58 |
01058F02 | mov al,byte ptr ds:[edi] | start checking "call" func from upx0
01058F04 | inc edi |
01058F05 | sub al,E8 |
01058F07 | cmp al,1 |
01058F09 | ja up.1058F02 | loop to find E8/E9
01058F0B | cmp byte ptr ds:[edi],0 |
01058F0E | jne up.1058F02 |
01058F10 | mov eax,dword ptr ds:[edi] | get call fun offset(fake)
01058F12 | mov bl,byte ptr ds:[edi+4] |
01058F15 | shr ax,8 |
01058F19 | rol eax,10 |
01058F1C | xchg ah,al |
01058F1E | sub eax,edi |
01058F20 | sub bl,E8 |
01058F23 | add eax,esi | eax-edi+esi=eax-edi's offset from upx0
01058F25 | mov dword ptr ds:[edi],eax | call func offset fix over
01058F27 | add edi,5 |
01058F2A | mov al,bl |
01058F2C | loop up.1058F07 |
01058F2E | lea edi,dword ptr ds:[esi+6000] |
01058F34 | mov eax,dword ptr ds:[edi] |
01058F36 | or eax,eax |
01058F38 | je up.1058F76 | fix over
开始填充函数地址
使用了Loadlibrary() GetProcAddress(),进行指定函数的地址载入,并填充到指定地址
01058F4A | call dword ptr ds:[esi+92B8] | LoadLibraryA
01058F50 | xchg eax,ebp | ebp take kernel32.dll base addr & other dll base addr
01058F51 | mov al,byte ptr ds:[edi] |
01058F53 | inc edi |
01058F54 | or al,al |
01058F56 | je up.1058F34 |
01058F58 | mov ecx,edi |
01058F5A | push edi |
01058F5B | dec eax |
01058F5C | repne scasb al,byte ptr es:[edi] | move edi to next string offset (func name string)
01058F5E | push ebp |
01058F5F | call dword ptr ds:[esi+92C0] |
01058F65 | or eax,eax |
01058F67 | je up.1058F70 |
01058F69 | mov dword ptr ds:[ebx],eax | fix the func addr to upx0+2000
01058F6B | add ebx,4 |
01058F6E | jmp up.1058F51 | fix address to upx0+1000
01058F70 | call dword ptr ds:[esi+92BC] |
01058F76 | add edi,4 | func addr fix over
(还差最后一步,未完待补充)