自己用汇编写一个HelloWorld,用UPX2.92B压缩后,IDA载入分析
入口点如下:

代码:
UPX1:00406120 start proc near
UPX1:00406120
UPX1:00406120 var_AC= byte ptr -0ACh
UPX1:00406120
UPX1:00406120     pusha
UPX1:00406121     mov esi, offset dword_406000        ; upx1 Section UPX1区段,入口点所在,保存着被压缩程序的信息和压缩后的代码
UPX1:00406121                                         ; 接下来的解压代码要做的事就是找到被压缩程序的代码并把他们还原回去
UPX1:00406126     lea edi, [esi-5000h]                ; UPX0->VirtualSize == 5000h ,得到首区段的起始地址,被压缩的程序将恢复到这个区段
UPX1:0040612C     push edi                            ; 保存第一个区段的RVA
UPX1:0040612D     or  ebp, 0FFFFFFFFh
UPX1:00406130     jmp short loc_406142                ; 开始解压代码
UPX1:00406132 ; ---------------------------------------------------------------------------
UPX1:00406132     nop
下面就是壳的解压代码了,转成C代码并不太难,主要是一些关于标志位的运算有点绕人,如下面一段:
00406184   .  8B1E          mov     ebx, dword ptr ds:[esi]
00406186   .  83EE FC       sub     esi, -4
00406189   .  11DB          adc     ebx, ebx
sub esi,-4会改变CF标志位,而接下来就有一句跟标志位有关的加法运算,而且这一段在解压代码中多次出现
我定义了一个unsigned __int64 tmp变量
tmp=esi;
tmp-=(unsigned int)-4;
int nCF=((tmp >> 32) & 1);
这样就可以得到CF标志位的值了
除了这一段,其他都是基本的逻辑,应该没什么问题

下面是壳的Shell代码中对于CALL指令的修正:
代码:
UPX1:004061F2 loc_4061F2:                             ; CODE XREF: start+5Cj
UPX1:004061F2     pop esi                             ; esi==UPX0->VirtualAddress+ImageBase
UPX1:004061F3     mov edi, esi                        ; 准备开始做call指令修正
UPX1:004061F5     mov ecx, 3                          ; ecx做计数器,要查找3个E8,且后一个字节为00
UPX1:004061FA
UPX1:004061FA loc_4061FA:                             ; CODE XREF: start+E1j
UPX1:004061FA                                         ; start+E6j
UPX1:004061FA     mov al, [edi]
UPX1:004061FC     inc edi
UPX1:004061FD     sub al, 0E8h
UPX1:004061FF
UPX1:004061FF loc_4061FF:                             ; CODE XREF: start+104j
UPX1:004061FF     cmp al, 1
UPX1:00406201     ja  short loc_4061FA
UPX1:00406203     cmp byte ptr [edi], 0
UPX1:00406206     jnz short loc_4061FA
UPX1:00406208     mov eax, [edi]
UPX1:0040620A     mov bl, [edi+4]
UPX1:0040620D     shr ax, 8
UPX1:00406211     rol eax, 10h
UPX1:00406214     xchg al, ah                         ; 将eax的高位换到al
UPX1:00406216     sub eax, edi
UPX1:00406218     sub bl, 0E8h
UPX1:0040621B     add eax, esi                        ; 算出偏移,修正call的偏移量
UPX1:0040621D     mov [edi], eax                      ; 修复公式为,E8后的第4个字节,减去E8下一字节所在地址,
UPX1:0040621D                                         ; 再加上区段RVA,算出偏移量
UPX1:0040621F     add edi, 5
UPX1:00406222     mov al, bl
UPX1:00406224     loop loc_4061FF
接着是修复原PE的IAT:
代码:
UPX1:00406226     lea edi, [esi+4000h]                ; esi+4000保存了被压缩文件的导入函数名和PE头结构
UPX1:0040622C
UPX1:0040622C loc_40622C:                             ; CODE XREF: start+12Ej
UPX1:0040622C     mov eax, [edi]                      ; 得到第一个DWORD,保存了偏移用来计算地址
UPX1:0040622E     or  eax, eax
UPX1:00406230     jz  short loc_40626E                ; 导入函数是否全部完成
UPX1:00406232     mov ebx, [edi+4]                    ; 得到第二个DWORD,用这个DWORD加上区段RVA得到被压缩文件
UPX1:00406232                                         ; 的IAT,后面得到的函数地址将写入这里
UPX1:00406235     lea eax, [eax+esi+6000h]            ; 定位到自身导入表的DLL名
UPX1:0040623C     add ebx, esi
UPX1:0040623E     push eax                            ; lpFileName
UPX1:0040623F     add edi, 8                          ; 偏移8个字节保存了IMAGE_IMPORT_BY_NAME
UPX1:00406242     call dword ptr [esi+603Ch]          ; call LoadLibraryA
UPX1:00406248     xchg eax, ebp                       ; ebp保存得到的HMODULE
UPX1:00406249
UPX1:00406249 loc_406249:                             ; CODE XREF: start+146j
UPX1:00406249     mov al, [edi]
UPX1:0040624B     inc edi                             ; 跳过Hint,来到Name
UPX1:0040624C     or  al, al
UPX1:0040624E     jz  short loc_40622C                ; 得到第一个DWORD,保存了偏移用来计算地址
UPX1:00406250     mov ecx, edi
UPX1:00406252     push edi                            ; lpProcName
UPX1:00406253     dec eax
UPX1:00406254     repne scasb                         ; 取得下一个函数名的地址
UPX1:00406256     push ebp                            ; hModule
UPX1:00406257     call dword ptr [esi+6040h]          ; call GetProcAddress
UPX1:0040625D     or  eax, eax                        ; 检查函数是否成功
UPX1:0040625F     jz  short loc_406268                ; 不成功跳向ExitProcess
UPX1:00406261     mov [ebx], eax                      ; 将函数地址写入被压缩文件的IAT
UPX1:00406263     add ebx, 4                          ; +4,准备写下一个
UPX1:00406266     jmp short loc_406249
UPX1:00406268 ; ---------------------------------------------------------------------------
UPX1:00406268
UPX1:00406268 loc_406268:                             ; CODE XREF: start+13Fj
UPX1:00406268     call dword ptr [esi+6050h]          ; call ExitProcess
UPX1:0040626E
UPX1:0040626E loc_40626E:                             ; CODE XREF: start+110j
UPX1:0040626E     mov ebp, [esi+6044h]
UPX1:00406274     lea edi, [esi-1000h]
UPX1:0040627A     mov ebx, 1000h
UPX1:0040627F     push eax
UPX1:00406280     push esp
UPX1:00406281     push 4
UPX1:00406283     push ebx
UPX1:00406284     push edi
UPX1:00406285     call ebp                            ; call VirtualProtct 修改Header为可写
UPX1:00406285                                         ; 用来修正区段Characteristics
UPX1:00406287     lea eax, [edi+1CFh]
UPX1:0040628D     and byte ptr [eax], 7Fh
UPX1:00406290     and byte ptr [eax+28h], 7Fh
UPX1:00406294     pop eax
UPX1:00406295     push eax
UPX1:00406296     push esp
UPX1:00406297     push eax
UPX1:00406298     push ebx
UPX1:00406299     push edi
UPX1:0040629A     call ebp                            ; 还原后壳的工作完成,做完收尾和清扫工作后跳向OEP
UPX1:0040629C     pop eax
UPX1:0040629D     popa
UPX1:0040629E     lea eax, [esp+2Ch+var_AC]
UPX1:004062A2
UPX1:004062A2 loc_4062A2:                             ; CODE XREF: start+186j
UPX1:004062A2     push 0
UPX1:004062A4     cmp esp, eax
UPX1:004062A6     jnz short loc_4062A2
UPX1:004062A8     sub esp, 0FFFFFF80h
UPX1:004062AB     jmp near ptr dword_401000
静态脱壳机的编写思路:
1.将整个压缩PE根据每个区段的VirtualAddress映射到自己申请的堆空间
2.根据壳的Shell代码自身的解压算法来解码被压缩的PE
3.修正OEP和各区段的SizeOfRawData和PointerToRawData
4.将堆空间中的代码和数据重新组装成一个PE文件,即为脱壳后的文件

附件是静态脱壳机源代码
上传的附件 UnpackUPX.rar