DASCTF 2024暑期挑战赛 Strangeprograme

最后更新于 2024-07-24 2438 字 预计阅读时间: 11 分钟


PE32位,进入ida分析找到关键函数

输入flag和给出的字符串进行比较,但是给出的是假的flag,应该是对该程序的IAT表进行的hook操作,改成其他函数,好久没看PE文件头,复习一下。

PE文件结构

  • DOS头(DOS Header)
  • DOS Stub
  • NT头(NT Header)
    • PE标识(Signature)
    • 文件头(File Header)
    • 可选头(OptionHeader)
  • 区段头(Section Header)

用010打开该程序先看DOS头,DOS头中只有两个属性重要e_magic e_lfanew分别记录了固定的魔术和到NT头的偏移

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE头
    WORD   e_magic;                     // 魔数(Magic number),固定MZ
    //WORD   e_cblp;                      // 文件最后一页的字节数
    //WORD   e_cp;                        // 文件中的页数
    //WORD   e_crlc;                      // 重定位项数目
    //WORD   e_cparhdr;                   // 头部大小,以段落(16字节)为单位
    //WORD   e_minalloc;                  // 程序所需的最小额外段数
    //WORD   e_maxalloc;                  // 程序所需的最大额外段数
    //WORD   e_ss;                        // 初始(相对)SS值
    //WORD   e_sp;                        // 初始SP值
    //WORD   e_csum;                      // 校验和
    //WORD   e_ip;                        // 初始IP值
    //WORD   e_cs;                        // 初始(相对)CS值
    //WORD   e_lfarlc;                    // 重定位表的文件地址
    //WORD   e_ovno;                      // 覆盖编号
    //WORD   e_res[4];                    // 保留字
    //WORD   e_oemid;                     // OEM标识符(用于e_oeminfo)
    //WORD   e_oeminfo;                   // OEM信息(由e_oemid指定)
    //WORD   e_res2[10];                  // 保留字
    LONG   e_lfanew;                    // 新EXE头的文件地址(PE头的偏移量)
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

DOS Stub里包含着一个简单的dos程序来显示消息和终止程序,表明该程序不能在dos系统下运行

NT头 e_lfanew记录的0xE0偏移处,包含三个部分

  • PE标识(PE签名,Signature)
  • 标准文件头(COFF头,Common Object File Format Header)
  • 可选头(Optional Header)

PE标识为0x45500000(PE )

COFF头包括以下部分,其中Characteristics和SizeOfOptionalHeader最为重要,分别记录着该程序的属性,可选头的大小。

typedef struct _IMAGE_FILE_HEADER {
    WORD  Machine;              // 指定目标机器类型
    WORD  NumberOfSections;     // 文件中的节数
    //DWORD TimeDateStamp;        // 文件创建的时间戳
    //DWORD PointerToSymbolTable; // 指向符号表的指针(通常为0)
    //DWORD NumberOfSymbols;      // 符号表中的符号数(通常为0)
    WORD  SizeOfOptionalHeader; // 可选头的大小
    WORD  Characteristics;      // 文件的属性标志
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

在Characteristics里有以下16种属性

本题程序如下

可选头

typedef struct _IMAGE_OPTIONAL_HEADER {
    WORD    Magic;                       // 标识文件类型,0x10B表示PE32,0x20B标识PE64
//    BYTE    MajorLinkerVersion;          // 链接器的主版本号
//    BYTE    MinorLinkerVersion;          // 链接器的次版本号
//    DWORD   SizeOfCode;                  // 所有代码节的总大小
//    DWORD   SizeOfInitializedData;       // 所有已初始化数据节的总大小
//    DWORD   SizeOfUninitializedData;     // 所有未初始化数据节的总大小
    DWORD   AddressOfEntryPoint;         // 程序入口点的地址(RVA)OEP
//    DWORD   BaseOfCode;                  // 代码节的起始地址(RVA)
//    DWORD   BaseOfData;                  // 数据节的起始地址(RVA)
    DWORD   ImageBase;                   // 首选的加载地址
    DWORD   SectionAlignment;            // 内存对齐大小
    DWORD   FileAlignment;               // 文件对齐大小
//    WORD    MajorOperatingSystemVersion; // 操作系统的主版本号
//    WORD    MinorOperatingSystemVersion; // 操作系统的次版本号
//    WORD    MajorImageVersion;           // 映像文件的主版本号
//    WORD    MinorImageVersion;           // 映像文件的次版本号
//    WORD    MajorSubsystemVersion;       // 子系统的主版本号
//    WORD    MinorSubsystemVersion;       // 子系统的次版本号
//    DWORD   Win32VersionValue;           // 保留字段,应为0
    DWORD   SizeOfImage;                 // 文件在内存中的大小,按照SectionAlignment对齐后
    DWORD   SizeOfHeaders;               // 所有头和节表(区段头)的总大小,按照FileAlignment对齐后
//    DWORD   CheckSum;                    // 校验和
//    WORD    Subsystem;                   // 子系统类型
//    WORD    DllCharacteristics;          // DLL的特性
//    DWORD   SizeOfStackReserve;          // 保留的栈大小
//    DWORD   SizeOfStackCommit;           // 初始提交的栈大小
//    DWORD   SizeOfHeapReserve;           // 保留的堆大小
//    DWORD   SizeOfHeapCommit;            // 初始提交的堆大小
//    DWORD   LoaderFlags;                 // 加载器标志,应为0
    DWORD   NumberOfRvaAndSizes;         // 数据目录的数量
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 数据目录数组
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

其中在DataDirectory数组中包含以下各个数据类型,其中IAT位于DataDirectory[1](导入表)中

加载多少个dll就有多少个IAT,在导入表中中包含以下成员

在程序还未执行时INT存放导入函数的信息,IAT未被初始化,程序加载后,程序根据INT表中的信息寻找到dll中函数的地址存放在IAT中且不会检查,因此可以执行IAT hook操作。

区段头

区段头中存放各个Sections的信息,我们可以根据VirtualAddress和PointerToRawData寻找IAT表的位置

typedef struct _IMAGE_SECTION_HEADER {
    BYTE  Name[IMAGE_SIZEOF_SHORT_NAME];    // 节的名称,通常是一个8字节长的字符串,如“.text”、“.data”等
//    union {
//        DWORD PhysicalAddress;              // 物理地址,不常用
        DWORD VirtualSize;                  // 节在内存中的实际大小
//    } Misc;
    DWORD VirtualAddress;                   // 区段在内存中的偏移位值
//    DWORD SizeOfRawData;                    // 区段在文件中对齐后的大小,文件对齐(FileAlignment)后的大小
    DWORD PointerToRawData;                 // 区段在文件中的偏移值
//    DWORD PointerToRelocations;             // 重定位信息表在文件中的位置偏移,通常为0
//    DWORD PointerToLinenumbers;             // 行号信息在文件中的位置偏移,调试信息相关,通常为0
//    WORD  NumberOfRelocations;              // 重定位项的数量
//    WORD  NumberOfLinenumbers;              // 行号信息的数量
    DWORD Characteristics;                  // 节的属性标志,描述节的特性(可执行、可读、可写等)
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; 

接下来寻找本题的IAT,在NT头中寻找到imagebase=0x40000和DataDirectory[1]的位置得到导入表的RVA=0x232b4h接下来我们要找导入表在哪个区段中

来到区段表根据每个成员的VirtualAddress和VirtualSize属性判断导入表在哪

显然23000+1600>232b4位于.idata段中,导入表和该段被加载到内存后具有相同的基址,因此我们只需要两个RVA相减得232b4=23000=2b4,再加上PointerToRawData字段(10E00)得到导入表在硬盘中的偏移(RAW)=110b4

每五个成员是一个导入表,查看第一个导入表的INT,其RVA=23318,也位于.idata段中,RAW为23318-23000+10E00=11118

每一个代表一个函数,第一个RVA=235cc,RAW=5cc+10E00=113cc,isdebuggerpresent函数的INT字段

IAT的RVA=23000,RAW=0+10E00=10E00,未初始化

程序加载后IAT位于imagebase+RVA=423000,已被填充对应函数的地址

回到本题中的IAT hook,IAT hook可以分为有本程序执行或者通过加载dll文件来实现题目所给的dll没有什么异常名字的dll故怀疑在程序中实现

打开区段表发现有.dasctf奇怪的段,进入发现其含有大量的数据未被识别怀疑有SMC

对其进行交叉引用进入sub_413D50函数

对其分析得到

进入smc发现其对.dasctf段进行smc每个字节都异或0xff

回到dasctf段进行手动smc得到

v10获取到本程序的句柄,通过+3c获取到dos头的e_lfanew字段获取NT头地址(v8),v7是pe可选头的地址,通过i来获取到导入表地址,i+5遍历所有导入表的i[4](IAT)地址,v4各个导入表的INT表,通过str1字段找到memcmp的IAT表地址并return给sub_413D50的v2进入IAT hook

进入IAT hook函数,修改传入memcmp的IAT表的属性变为可读可写后将传入的函数写入IAT表(*lpAddress = a2;)

修改的函数为

__int64 __cdecl sub_41D250(char *Str)
{
  __int64 v1; // rax
  __int64 v3; // [esp-8h] [ebp-24Ch]
  int j; // [esp+D0h] [ebp-174h]
  size_t i; // [esp+F4h] [ebp-150h]
  char *v6; // [esp+100h] [ebp-144h]
  int v7; // [esp+124h] [ebp-120h] BYREF
  int v8; // [esp+128h] [ebp-11Ch]
  int v9; // [esp+12Ch] [ebp-118h]
  int v10; // [esp+130h] [ebp-114h]
  char v11[260]; // [esp+13Ch] [ebp-108h] BYREF
  int savedregs; // [esp+244h] [ebp+0h] BYREF

  GetCurrentThreadId_0();
  v11[0] = -7;
  v11[1] = 77;
  v11[2] = 43;
  v11[3] = -68;
  v11[4] = 19;
  v11[5] = -35;
  v11[6] = 19;
  v11[7] = 98;
  v11[8] = -55;
  v11[9] = -4;
  v11[10] = -1;
  v11[11] = -119;
  v11[12] = 125;
  v11[13] = 79;
  v11[14] = -55;
  v11[15] = 15;
  v11[16] = 99;
  v11[17] = 29;
  v11[18] = 109;
  v11[19] = 82;
  v11[20] = 80;
  v11[21] = -3;
  v11[22] = 65;
  v11[23] = -29;
  v11[24] = 51;
  v11[25] = 118;
  v11[26] = 40;
  v11[27] = -105;
  v11[28] = 56;
  v11[29] = 54;
  v11[30] = -7;
  v11[31] = 107;
  v11[32] = -112;
  v11[33] = 57;
  v11[34] = 20;
  v11[35] = -125;
  v11[36] = 44;
  v11[37] = -30;
  v11[38] = 44;
  v11[39] = 31;
  memset(&v11[40], 0, 216);
  v7 = 0;
  v8 = 0;
  v9 = 0;
  v10 = 0;
  if ( j_strlen(Str) == 40 )
  {
    v6 = Str + 4;
    v7 = *Str;
    v8 = *(Str + 1);
    tea(&v7, &unk_422100);
    *Str = v7;
    *(Str + 1) = v8;
    for ( i = 2; i < j_strlen(Str) >> 2; i += 2 )
    {
      tea(&v7, &unk_422100);
      *Str = v7;
      *v6 = v8;
      *&Str[4 * i] ^= *Str;
      *&Str[4 * i + 4] ^= *v6;
    }
    for ( j = 0; j < 40; ++j )
    {
      HIDWORD(v1) = j;
      if ( Str[j] != v11[j] )
      {
        LODWORD(v1) = 1;
        goto LABEL_12;
      }
    }
    LODWORD(v1) = 0;
  }
  else
  {
    LODWORD(v1) = 1;
  }
LABEL_12:
  v3 = v1;
  sub_41130C(&savedregs, &unk_41D5CC);
  return v3;
}

输入四十个字符进行tea加密后用str[4 * i]和str[4 * i+4]异或str[0],str[1]后在tea加密其str[0],str[1]在进行异或得出exp

from ctypes import c_uint32


def tea_decrypt(v, k):
    de=[]
    for x in range(0,len(v),2):
        v6 = c_uint32(v[x])

        v5 = c_uint32(v[x+1])

        delta = 0x9e3779b9

        v4 = c_uint32(0x9e3779b9*16)

        for i in range(16):
            v4.value -= delta
            v5.value -= (k[3] + (v6.value >> 5)) ^ (v4.value + v6.value) ^ (k[2] + 16 * v6.value)
            v6.value -= (k[1] + (v5.value>> 5)) ^ (v4.value + v5.value) ^ (k[0] + 16 * v5.value)
        de.append(v6.value)
        de.append(v5.value)
    return de




v11=[0xBC2B4DF9,0x6213DD13,0x89FFFCC9,0x0FC94F7D,0x526D1D63,0xE341FD50,0x97287633,0x6BF93638,0x83143990,0x1F2CE22C]
v12=[0xBC2B4DF9,0x6213DD13]
key=[0x12345678,0x09101112,0x13141516,0x15161718]
for y in range(len(v11)-1,2,-2):
    # print(y)
    v11[y-1]^=v12[0]
    v11[y] ^= v12[1]
    v12=tea_decrypt(v12,key)
v12=tea_decrypt(v12,key)
v11[0],v11[1]=v12[0],v12[1]
a=""
for x in v11:
    a+=hex(x)
print(a)
a=""
c="43534144497b465430485434495f6b6f6f3053356e75466f495f796e746f6e733f3f74697d3f3f3f"
for x in range(0,len(c),8):
    b=c[x:x+8]
    print(b)
    for x in range(len(b),-1,-2):
        a+=b[x-2:x]
a=bytes.fromhex(a)
for x in range(len(a)):
    print(chr(a[x]),end="")
此作者没有提供个人介绍。
最后更新于 2024-07-24