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="")

Comments NOTHING