Dual personality

天堂之门

在开始分析这个题之前,先介绍一个知识点:天堂之门
天堂之门是一种利用Windows的WoW机制,在32位的进程中执行64位程序代码,达到反检测的目的(反静动态分析)。
那么什么是WoW?他的全称是Windows 32 on Windows 64。众所周知,win64平台向下兼容,可以在x64平台上运行x86程序,而Windows之所以可以做到这点,就是因为使用了wow作为翻译层。
而天堂之门就是利用了这个机制,在32位程序中插入64位程序代码,让逆向分析工具不能正常分析成功。同理如果我们想要让他能够被正常分析,我们也只需要找到程序中被插入的64位代码,然后将其dump出来单独分析即可。

题目分析

重新回到这个题目,其中需要重点分析的函数是
Pasted image 20230212195111
这个函数的功能很简单,接收两个参数,一个是地址,我们将其称为add,另一个是长度,我们将其称为len。然后将调用函数的后len个字节的机器码修改为0xEA + add + 0x33 。并将未修改前的指令拷贝至新申请的内存中,然后再追加一条跳转回原地址的指令。
Pasted image 20230212201205
这个过程中,很明显关键的步骤是修改的机器码,其中有一个关键:0x33 。再看这个指令0xEA是jmp的机器码,在ida上面修改完之后会发现这是一个远跳
Pasted image 20230212202433
jmp far ptr 该指令会同时修改cs和ip的值,即将cs修改为0x33,ip修改为407060 。
在x86程序中,cs段的值为0x23,而在x64程序中cs的值为0x33 。
所以这段代码的作用其实就是跳转到指定地址后使用x64格式去执行该地址的代码。在ida中无法直接分析这段程序,我们可以直接将这段内存dump下来,然后重新使用ida64打开分析
![[Pasted image 20230213093430.png]]
这段代码首先读取了gs段的第62字节位置的数据,并将其存入40705c处。然后检测此值是否为0,如果不为0则跳转。如果没跳转,407058位置的变量会被赋值为5df966ae,然后返回407000中所存的地址处,如果跳转了,则直接返回407000中所存的地址处。
在x64环境下,gs寄存器存储了一些一些关于进程和线程环境的关键信息 例如:

gs:[0x30]                 TEB  线程信息块,保存线程信息的基本数据结构
gs:[0x40]                 Pid  进程id
gs:[0x48]                 Tid  线程id
gs:[0x60]                 PEB  进程信息块

而题目中使用的就是gs:[0x60],PEB进程信息块。这个结构的结构体在win xp版本中如下:

typedef struct _PEB { // Size: 0x1D8
    000h    UCHAR           InheritedAddressSpace;
    001h    UCHAR           ReadImageFileExecOptions;
    002h    UCHAR           BeingDebugged;              //Debug运行标志
    003h    UCHAR           SpareBool;
    004h    HANDLE          Mutant;
    008h    HINSTANCE       ImageBaseAddress;           //程序加载的基地址
    00Ch    struct _PEB_LDR_DATA    *Ldr                //Ptr32 _PEB_LDR_DATA
    010h    struct _RTL_USER_PROCESS_PARAMETERS  *ProcessParameters;
    014h    ULONG           SubSystemData;
    018h    HANDLE          DefaultHeap;
    01Ch    KSPIN_LOCK      FastPebLock;
    020h    ULONG           FastPebLockRoutine;
    024h    ULONG           FastPebUnlockRoutine;
    028h    ULONG           EnvironmentUpdateCount;
    02Ch    ULONG           KernelCallbackTable;
    030h    LARGE_INTEGER   SystemReserved;
    038h    struct _PEB_FREE_BLOCK  *FreeList
    03Ch    ULONG           TlsExpansionCounter;
    040h    ULONG           TlsBitmap;
    044h    LARGE_INTEGER   TlsBitmapBits;
    04Ch    ULONG           ReadOnlySharedMemoryBase;
    050h    ULONG           ReadOnlySharedMemoryHeap;
    054h    ULONG           ReadOnlyStaticServerData;
    058h    ULONG           AnsiCodePageData;
    05Ch    ULONG           OemCodePageData;
    060h    ULONG           UnicodeCaseTableData;
    064h    ULONG           NumberOfProcessors;
    068h    LARGE_INTEGER   NtGlobalFlag;               // Address of a local copy
    070h    LARGE_INTEGER   CriticalSectionTimeout;
    078h    ULONG           HeapSegmentReserve;
    07Ch    ULONG           HeapSegmentCommit;
    080h    ULONG           HeapDeCommitTotalFreeThreshold;
    084h    ULONG           HeapDeCommitFreeBlockThreshold;
    088h    ULONG           NumberOfHeaps;
    08Ch    ULONG           MaximumNumberOfHeaps;
    090h    ULONG           ProcessHeaps;
    094h    ULONG           GdiSharedHandleTable;
    098h    ULONG           ProcessStarterHelper;
    09Ch    ULONG           GdiDCAttributeList;
    0A0h    KSPIN_LOCK      LoaderLock;
    0A4h    ULONG           OSMajorVersion;
    0A8h    ULONG           OSMinorVersion;
    0ACh    USHORT          OSBuildNumber;
    0AEh    USHORT          OSCSDVersion;
    0B0h    ULONG           OSPlatformId;
    0B4h    ULONG           ImageSubsystem;
    0B8h    ULONG           ImageSubsystemMajorVersion;
    0BCh    ULONG           ImageSubsystemMinorVersion;
    0C0h    ULONG           ImageProcessAffinityMask;
    0C4h    ULONG           GdiHandleBuffer[0x22];
    14Ch    ULONG           PostProcessInitRoutine;
    150h    ULONG           TlsExpansionBitmap;
    154h    UCHAR           TlsExpansionBitmapBits[0x80];
    1D4h    ULONG           SessionId;
    1d8h AppCompatFlags   : _ULARGE_INTEGER
    1e0h AppCompatFlagsUser : _ULARGE_INTEGER
    1e8h pShimData        : Ptr32 Void
    1ech AppCompatInfo    : Ptr32 Void
    1f0h CSDVersion       : _UNICODE_STRING
    1f8h ActivationContextData : Ptr32 Void
    1fch ProcessAssemblyStorageMap :Ptr32 Void
    200h SystemDefaultActivationContextData : Ptr32 Void
    204h SystemAssemblyStorageMap : Ptr32 Void
    208h MinimumStackCommit : Uint4B
} PEB, *PPEB;

win 7版本有所增加但关键部分大体相同,不再展示。在这个题目中我们主要关注偏移为2位置的BeingDebugged参数,当此参数为1时则表示该程序正在被调试器调试,否则值为0 。
所以在本题中mov rax,gs:[60h] mov al,[rax+2]的操作即为取出BeingDebugged参数,以便在后续判断程序是否在被调试。

注:在x86环境下,使用fs:[30h] 字段取出PEB结构

继续回到题目,在了解了以上信息之后,就能够理解这段代码的功能:检测程序是否在被调试,如果在调试就直接返回,否则为407058h处的变量赋值。最后返回407000中存储的地址处,并调整为32位模式执行。而在sub_401120这个函数中,这个地址的值指向了新开辟出的内存。新开辟出的内存我们已经分析出来了。所以接下来的操作便是执行未被修改前的指令并跳转回原地址继续执行程序。
然后继续向下分析,注意这里的call fword ptr,这是一个远跳,会同时修改cs和ip的值,而byte_40700c中存储的值中cs的位置是0x0033所以,在这里又进入了64位程序。
Pasted image 20230215150639
将这个地址的指令dump出来,很显然,这里是将flag进行加密的位置,如果正在进行调试则用if分支进行加密,否则使用else分支进行加密,所以正确的加密是else分支中的部分
Pasted image 20230215151326
该函数执行结束后通过retf跳转回原位置,并将cs还原为0x23,转为32位。
然后下面通过sub_401120实现第三次跳转,转为64位程序执行,dump出其中的字节码
Pasted image 20230215152055
在这个函数中,对加密使用的key进行了处理,最后直接跳转到了sub_401120中保存的未修改前的代码,然后重新跳转回主函数继续执行,注意,由于这次没有切换回32位模式,所以主函数下面的代码全部都是64位格式指令。这也是下图位置ida分析出错的原因。
Pasted image 20230213105639
dec eax这里开始,下面的部分全部都是x64格式的指令,并不是存在花指令,想要分析还需要将其dump出来。

Pasted image 20230215153154

这个函数直接分析汇编可以看出他是一个循环,一共循环32(0x20)次,每次都将407014指向的字符串与407060指向的字符串取出一位进行异或,注意这里有一个div的操作,余数保存在dx中,所以这段代码的功能也就是 flag^key[i%4]

现在,整个程序还没有分析清楚的加密只剩下了一段

.text:004013E8 83 C4 08                      add     esp, 8
.text:004013EB 85 C0                         test    eax, eax
.text:004013ED 74 05                         jz      short loc_4013F4
.text:004013ED
.text:004013EF A1 58 70 40 00                mov     eax, dword_407058
.text:004013EF
.text:004013F4
.text:004013F4                               loc_4013F4:                             ; CODE XREF: .text:004013ED↑j
.text:004013F4 2D 11 41 52 21                sub     eax, 21524111h
.text:004013F9 A3 58 70 40 00                mov     dword_407058, eax
.text:004013FE C7 45 F4 60 70 40 00          mov     dword ptr [ebp-0Ch], offset unk_407060
.text:00401405 C7 45 E8 00 00 00 00          mov     dword ptr [ebp-18h], 0
.text:0040140C EB 09                         jmp     short loc_401417
.text:0040140C
.text:0040140E                               ; ---------------------------------------------------------------------------
.text:0040140E
.text:0040140E                               loc_40140E:                             ; CODE XREF: .text:0040144A↓j
.text:0040140E 8B 45 E8                      mov     eax, [ebp-18h]
.text:00401411 83 C0 01                      add     eax, 1
.text:00401414 89 45 E8                      mov     [ebp-18h], eax
.text:00401414
.text:00401417
.text:00401417                               loc_401417:                             ; CODE XREF: .text:0040140C↑j
.text:00401417 83 7D E8 08                   cmp     dword ptr [ebp-18h], 8
.text:0040141B 7D 2F                         jge     short loc_40144C
.text:0040141B
.text:0040141D 8B 45 E8                      mov     eax, [ebp-18h]
.text:00401420 8B 4D F4                      mov     ecx, [ebp-0Ch]
.text:00401423 8B 14 81                      mov     edx, [ecx+eax*4]
.text:00401426 03 15 58 70 40 00             add     edx, dword_407058
.text:0040142C 8B 45 E8                      mov     eax, [ebp-18h]
.text:0040142F 8B 4D F4                      mov     ecx, [ebp-0Ch]
.text:00401432 89 14 81                      mov     [ecx+eax*4], edx
.text:00401435 8B 45 E8                      mov     eax, [ebp-18h]
.text:00401438 8B 4D F4                      mov     ecx, [ebp-0Ch]
.text:0040143B 8B 15 58 70 40 00             mov     edx, dword_407058
.text:00401441 33 14 81                      xor     edx, [ecx+eax*4]
.text:00401444 89 15 58 70 40 00             mov     dword_407058, edx
.text:0040144A EB C2                         jmp     short loc_40140E

这段代码并不复杂,设dword_407058中的值为x,则上式等价于

for(int i = 0; i < 8; i ++)
{
    flag[i] += x;
    x ^= flag[i]
}

综上所述,我们可以还原出整个加密逻辑

#include<iostream>
using namesapce std;
#define rol(x,i) ((x<<i)|(x>>(64-i)))
#define ror(x,i) ((x>>i)|(x<<(64-i)))
int main()
{
    string flag = "******************************";
    int x = 0x5DF966AE;
    x -= 0x21524111;
    for(int i = 0; i < 8; i ++)
    {
        flag[i] += x;
        x ^= flag[i];
    }
    unsigned long long* pQwordFlag = (unsigned long long*)flag;
    rol(pQwordFlag,12);
    rol(pQwordFlag,34);
    rol(pQwordFlag,56);
    rol(pQwordFlag,14);

    int key[] = { 0x9D, 0x44, 0x37, 0xB5 };
    key[0] &= key[1];
    key[1] |= key[2];
    key[2] ^= key[3];
    key[3] = ~key[3];
    for(int i = 0; i < 32; i ++)
    {
        flag[i] ^= key[i%4];
    }
}

所以我们可以写出解密脚本

for (int i = 0; i < 32; i++) 
{  
    flag[i] ^= key[i % 4]; 
} 

pQwordFlag = (unsigned long long*)flag; 
*(pQwordFlag + 0) = (*(pQwordFlag + 0) >> 12) | (*(pQwordFlag + 0) << (64 - 12));
*(pQwordFlag + 1) = (*(pQwordFlag + 1) >> 34) | (*(pQwordFlag + 1) << (64 - 34)); 
*(pQwordFlag + 2) = (*(pQwordFlag + 2) >> 56) | (*(pQwordFlag + 2) << (64 - 56));
*(pQwordFlag + 3) = (*(pQwordFlag + 3) >> 14) | (*(pQwordFlag + 3) << (64 - 14));
delta = 0x5df966ae; 
delta += 0xdeadbeef;  //0x3CA7259D 
for (int i = 0; i < 8; i++) 
{  
    DWORD tmp = pDwordFlag[i] ^ delta;  
    pDwordFlag[i] -= delta;  
    delta = tmp; 
} 
printf("flag:DASCTF{%s}", flag);