天堂之门
一、天堂之门基础知识
天堂之门(Heaven's Gate) 是一种在32位WoW64进程中执行64位代码以及直接调用64位WIN32 API函数的技术。
天堂之门自带反调试,使IDA不能正常解析
CS段寄存器存放的是段描述符在GDT的索引,CS为0x33在GDT表对应的段描述符为64位,CS为0x23在GDT表对应的段描述符为32位,天堂之门就是通过修改cs段寄存器的值来完成x32与x64之间的切换的
天堂之门64位所用到的api需要我们重写,以达到调用64位函数的目的...
转移指令Mov是不能修改cs段寄存器的,我们可以借助CALL 或 retf来实现cs段的切换
远跳转 CALL FAR(跳转不提权) ,其跳转前后的栈帧图如下:
注意,x64跳回的时候要用 _emit 0x48 _emit 0xCB ,即 retfq
对此,我们可以编写一个简单的程序来实现天堂之门的切换
二、简单代码实现
//编译器: VC6++ //工具: keystone // Heaven_s Gate.cpp : Defines the entry point for the console application. // #include <stdio.h> #include <windows.h> #include <malloc.h> void Debase64(char* str, int len, char decodeStr[]) { char base64[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; char ascill[129]; int k = 0; for (int ii = 0; ii < 64; ii++) { ascill[base64[ii]] = k++; } int decodeStrlen = len / 4 * 3 + 1; k = 0; for (int i = 0; i < len; i++) { decodeStr[k++] = (ascill[str[i]] << 2) | (ascill[str[++i]] >> 4); if (str[i + 1] == '=') { break; } decodeStr[k++] = (ascill[str[i]] << 4) | (ascill[str[++i]] >> 2); if (str[i + 1] == '=') { break; } decodeStr[k++] = (ascill[str[i]] << 6) | (ascill[str[++i]]); } decodeStr[k] = '\0'; } int main(int argc, char* argv[]) { char ttt[] = "sGVsbG8gV29lbGQ="; char flag[256] = { 0 }; printf("进入x64\n"); _asm { //push 0x33 //x64 对应的cs段寄存器// //push switchX32ToX64 //retf //远跳返回 _emit 0x6A _emit 0x33 _emit 0x68 _emit 0x7b _emit 0x13 _emit 0x40 _emit 0x00 _emit 0xCB } ttt[0] = 't'; switchX32ToX64: ttt[0] = 'S'; __asm { //在x64位下执行这一句的话,需要使用硬编码// 我们使用keystne获取//68 1C 20 42// _emit 0x6A _emit 0x23 __emit 0x68 _emit 0x88 _emit 0x13 _emit 0x40 _emit 0x00 _emit 0x48 _emit 0xCB //retfq中的q表示qword,即返回到64位的地址 x64dbg翻译为 RET FAR } switchX64ToX32: printf("切换为32位\n"); Debase64(ttt, strlen(ttt), flag); printf("%s\n", flag); getchar(); return 0; }
我们利用天堂之门反调试的特性在切换为64位后修改密文,使其能够在之后的32位中解密为 Hello Woeld,IDA打开如下:
可以看到,在伪代码层,密文的修改工作被隐藏的很好
三、CTF实战
在ctf中,天堂之门的使用往往伴随着大量的异常、混淆、反调试,使程序很难通过IDA分析,我们一般可以借助dump的方法,使64位代码在64位IDA中显示出来,或使用windbg硬刚汇编,逆向神器Ghidra 可能也是一种不错的选择,比较困难的题有KCTF 2022 危机四伏
CTF Show 月饼杯2 EasyTea
这道题目就是利用了天堂之门技术,在32位程序下跑64位魔改xtea加密算法,不过没有异常和其他的混淆,并且提示了tea加密,题目还算简单
程序中两处远跳形成了32位到64位的切换
我使用IDApython脚本把64位函数dump出来放到了64位IDA里
import idc data = [ 0x48,0x89,0x4c,0x24,0x8,0x55,0x57,0x48,0x81,0xec,0xa8,0x1,0x0,0x0,0x48,0x8d, 0x6c,0x24,0x20,0x48,0x8b,0xfc,0xb9,0x6a,0x0,0x0,0x0,0xb8,0xcc,0xcc,0xcc,0xcc, 0xf3,0xab,0x48,0x8b,0x8c,0x24,0xc8,0x1,0x0,0x0,0x90,0x90,0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,0x90,0x90,0xc7,0x45,0x8,0x66,0x0,0x0,0x0,0xc7,0x45,0xc, 0x6c,0x0,0x0,0x0,0xc7,0x45,0x10,0x61,0x0,0x0,0x0,0xc7,0x45,0x14,0x67,0x0, 0x0,0x0,0xc7,0x45,0x18,0x69,0x0,0x0,0x0,0xc7,0x45,0x1c,0x73,0x0,0x0,0x0, 0xc7,0x45,0x20,0x6d,0x0,0x0,0x0,0xc7,0x45,0x24,0x65,0x0,0x0,0x0,0xb8,0x4, 0x0,0x0,0x0,0x48,0x6b,0xc0,0x7,0x48,0x8b,0x8d,0xa0,0x1,0x0,0x0,0x8b,0x4, 0x1,0x89,0x45,0x44,0xc7,0x45,0x64,0x0,0x0,0x0,0x0,0xc7,0x85,0x84,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0xeb,0xe,0x8b,0x85,0x84,0x0,0x0,0x0,0xff,0xc0,0x89, 0x85,0x84,0x0,0x0,0x0,0x83,0xbd,0x84,0x0,0x0,0x0,0x20,0xf,0x8d,0x44,0x1, 0x0,0x0,0x8b,0x45,0x64,0x5,0x45,0x11,0x48,0x88,0x89,0x45,0x64,0xc7,0x85,0xa4, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xeb,0xe,0x8b,0x85,0xa4,0x0,0x0,0x0,0xff, 0xc0,0x89,0x85,0xa4,0x0,0x0,0x0,0x83,0xbd,0xa4,0x0,0x0,0x0,0x7,0xf,0x8d, 0x8a,0x0,0x0,0x0,0x48,0x63,0x85,0xa4,0x0,0x0,0x0,0x8b,0x8d,0xa4,0x0,0x0, 0x0,0xff,0xc1,0x48,0x63,0xc9,0x48,0x8b,0x95,0xa0,0x1,0x0,0x0,0x8b,0xc,0x8a, 0xc1,0xe1,0x4,0x8b,0x95,0xa4,0x0,0x0,0x0,0xff,0xc2,0x48,0x63,0xd2,0x4c,0x8b, 0x85,0xa0,0x1,0x0,0x0,0x41,0x8b,0x14,0x90,0xc1,0xfa,0x5,0x33,0xca,0x8b,0x95, 0xa4,0x0,0x0,0x0,0xff,0xc2,0x48,0x63,0xd2,0x4c,0x8b,0x85,0xa0,0x1,0x0,0x0, 0x41,0x3,0xc,0x90,0x8b,0x55,0x64,0x83,0xe2,0x7,0x8b,0xd2,0x8b,0x54,0x95,0x8, 0x44,0x8b,0x45,0x64,0x44,0x3,0xc2,0x41,0x8b,0xd0,0x33,0xca,0x48,0x8b,0x95,0xa0, 0x1,0x0,0x0,0x3,0xc,0x82,0x8b,0xc1,0x48,0x63,0x8d,0xa4,0x0,0x0,0x0,0x48, 0x8b,0x95,0xa0,0x1,0x0,0x0,0x89,0x4,0x8a,0xe9,0x5b,0xff,0xff,0xff,0xb8,0x4, 0x0,0x0,0x0,0x48,0x6b,0xc0,0x7,0xb9,0x4,0x0,0x0,0x0,0x48,0x6b,0xc9,0x0, 0x48,0x8b,0x95,0xa0,0x1,0x0,0x0,0x8b,0xc,0xa,0xc1,0xe1,0x4,0xba,0x4,0x0, 0x0,0x0,0x48,0x6b,0xd2,0x0,0x4c,0x8b,0x85,0xa0,0x1,0x0,0x0,0x41,0x8b,0x14, 0x10,0xc1,0xfa,0x5,0x33,0xca,0xba,0x4,0x0,0x0,0x0,0x48,0x6b,0xd2,0x0,0x4c, 0x8b,0x85,0xa0,0x1,0x0,0x0,0x41,0x3,0xc,0x10,0x8b,0x55,0x64,0x83,0xe2,0x7, 0x8b,0xd2,0x8b,0x54,0x95,0x8,0x44,0x8b,0x45,0x64,0x44,0x3,0xc2,0x41,0x8b,0xd0, 0x33,0xca,0x48,0x8b,0x95,0xa0,0x1,0x0,0x0,0x3,0xc,0x2,0x8b,0xc1,0xb9,0x4, 0x0,0x0,0x0,0x48,0x6b,0xc9,0x7,0x48,0x8b,0x95,0xa0,0x1,0x0,0x0,0x89,0x4, 0xa,0xe9,0xa1,0xfe,0xff,0xff,0x48,0x8d,0x4d,0xe0,0x90,0x90,0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,0x90,0x90,0x48,0x8d,0xa5,0x88,0x1,0x0,0x0,0x5f,0x5d ,0xc3 ] begin = 0x0007FF6E6A71F50 end = 0x0007FF6E6A71F50 + 0x0427C5F - 0x0427A50 for addr in range(begin,end): idc.patch_byte(addr,data[addr-begin]) begin = 0x0007FF6E6A71F50 end = 0x0007FF6E6A71F50 + 0x0427C5F - 0x0427A50 for i in range(begin,end): idc.create_insn(i)
解密流程十分清晰
解密脚本:
#include <stdio.h> int map[8]={ 0xB5ABA743, 0x4C5B3EE0, 0xB70AEB14, 0x6946BC13, 0x906089C4, 0x5B9F98F0, 0x0964B652, 0x78920976 }; void DeTea(int* v) { int sum=0; for(int i=0;i<32;i++) sum+=0x88481145; int key[] = { 0x66, 0x6c, 0x61, 0x67, 0x69, 0x73, 0x6d, 0x65 }; unsigned int tmp = v[7]; for (int j = 0; j < 32; j++) { v[7] -= (((v[0] << 4) ^ (v[0] >> 5)) + v[0]) ^ (sum + key[sum &7]); for (int i = 6; i >-1; i--) { v[i] -= (((v[i + 1] << 4) ^ (v[i + 1] >> 5)) + v[i + 1]) ^ (sum + key[sum & 7]); } sum -= 0x88481145; } } int main(){ DeTea(map); for(int i = 0;i<=7;i++){ printf("%x\n",map[i]); } return 0; } //5f616554 //34333231 //32315f35 //5f353433 //5f736579 //67616c66 //5f73695f //79736165