天堂之门(Heaven's Gate)逆向

发布时间 2023-12-02 23:05:05作者: yuhury

Heaven's Gate

原理及POC

通过在32位WoW进程中执行64位代码,实现静态反编译以及干扰对Win32Api的检测实现免杀。

详见[原创]天堂之门 (Heaven's Gate) C语言实现-软件逆向-看雪-安全社区|安全招聘|kanxue.com

常见的打开天堂之门的代码块

// convert x86 to x64
6A 33                    push 0x33   ; cs寄存器的新值
E8 00 00 00 00           call $+5     ;push 下一条指令的地址入栈,并继续执行下一条指令
83 04 24 05              add dword [esp], 5  ;栈顶的返回地址加5,指向retf的下一条指令
CB                       retf  ; 通过retf,程序返回到下一条指令继续执行,但cs 寄存器已经被修改为0x33, 执行的代码是64位
// convert x64 to x86
E8 00 00 00 00           call $+5
C7 44 24 04 23 00 00 00  mov dword [rsp + 4], 0x23
83 04 24 0D              add dword [rsp], 0xD
CB                       retf

前置知识

32位程序中调用64位程序的逆向方法

参考CTF中32位程序调用64位代码的逆向方法-安全客 - 安全资讯平台 (anquanke.com)深度好文,解答了在复现OddCode时遇到的种种odd问题的疑惑!

识别

retf是切换32位和64位的关键指令。

retf前有push 0x33(33h)类似的指令。

push    33h 
add     dword ptr [esp], 5 
retf
或者 
mov     dword ptr [eax], 33h 
leave
retf

retf后CS寄存器从0x23变为0x33。

程序中可能有进行支持64位的检查,如GWoC。

当一块可执行的内存,调试时无法识别汇编或者几步一跳时,有可能在是执行64位的代码。

32位代码调用函数的方式和64位代码有差异,32位程序大多通过入栈方式传参,64位程序一般用寄存器传参。

32位和64位的syscall的含义和参数有所不同。

静态

dump出64位代码后拖进ida64分析,在ida64中注意设置基址和源程序中相同便于分析。

动态

用ida64调试时,在进入64位代码所在区域前,在Edit->Segments->Edit segment处设置64位代码所在段为64位模式,可读可写可运行

天堂之门解题trick

根据call far、jmp far定位是天堂之门,找到切换64位模式的代码,然后对64位部分代码进行静态or动态分析。静态的话就是dump出64位部分代码,然后设置基址和段属性。动态就是windows下ida+windbg。

题目

OddCode

本题参考

2021年羊城杯官方Writeup公布(Reverse) (qq.com)

羊城杯_2021 OddCode 天堂之门 + unicorn反混淆 - TLSN - 博客园 (cnblogs.com)

[原创]羊城杯OddCode题解(unicorn模拟调试+求解)-CTF对抗-看雪-安全社区|安全招聘|kanxue.com

这题看起来好炫,跟着wp做做,然后就做了好久好久。用ida打开后发现有很多花指令干扰,但是略向下翻可发现此处远跳转,配合上下面的call指令序列可知此题属于heaven's gate,远跳转和call $+5指令序列分别是32位转64位和64位转32位的代码。ida对远跳转支持不好,可参考汇编中的jmp转移指令:jmp short、jmp near ptr、jmp far ptr-CSDN博客。在这里可看远跳转的机器码,小端序0033:00405313,即设置cs为0x33,地址为0x405310,也就是他下面一行call sub_401010指令。

可以从x32dbg的调试中看出远跳转是在设置cs为0x33

image

可以看出,程序切换到64位模式后跳转下一行,执行的是call sub_401010

image

执行完后经下列指令切换回32位模式。

image

最后根据sub401010的返回值判断flag正误,可知关键函数即sub401010

image

同时观察可知,跳转前将input放入esi,将一个可能是key的数组放入edi

image

image

下面就是要分析64位部分的代码,可以dump后静态分析,也可以动态分析。这个题dump出来后简单观察下就可以发现充满花指令混淆,实在是难以分析,只能动态调试下看看。

思路一:用unicorn来调试代码块,但是我一直搞不懂切换64位前寄存器的状态是怎么获得的,我的EIP指向0x2e1010时寄存器的值和搜到的这个思路的wp都不太一样,不知道是不是因为64/32位切换我没弄好。

from unicorn import *
from unicorn.x86_const import * 
ADDRESS = 0x2E1000          # 程序加载的地址,可通过ida,exeinfoPe,x32dbg调试等获得
INPUT_ADDRESS = 0x2E701D    # 输入的地址
KEY_ADDRESS = 0x2E705C      # 16字节key的地址
with open('OddCode.exe', 'rb') as file:    
	file.seek(0x400)#PE文件结构代码开始部分    
	X64_CODE = file.read(0x4269)    # 读取代码,除去32位程序剩下的所有代码
class Unidbg:     
	def __init__(self, flag):        
		mu = Uc(UC_ARCH_X86, UC_MODE_64)        # 基址为0x2E1000,分配16MB内存        
		mu.mem_map(ADDRESS, 0x1000000)        
		mu.mem_write(ADDRESS, X64_CODE)        
		mu.mem_write(INPUT_ADDRESS, flag)       # 随便写入一个flag        
		mu.mem_write(KEY_ADDRESS, b'\x90\xF0\x70\x7C\x52\x05\x91\x90\xAA\xDA\x8F\xFA\x7B\xBC\x79\x4D') 
        #跳转前存入的key
        # 初始化寄存器,寄存器的状态就是切换到64位模式之前的状态,可以通过动调得到        
		mu.reg_write(UC_X86_REG_RAX, 1)        
		mu.reg_write(UC_X86_REG_RBX, 0x51902D)        
		mu.reg_write(UC_X86_REG_RCX, 0xD86649D8)        
		mu.reg_write(UC_X86_REG_RDX, 0x2E701C)        
		mu.reg_write(UC_X86_REG_RSI, INPUT_ADDRESS)  # input参数        
		mu.reg_write(UC_X86_REG_RDI, KEY_ADDRESS)    # key参数       
		mu.reg_write(UC_X86_REG_RBP, 0x6FFBBC)        
		mu.reg_write(UC_X86_REG_RSP, 0x6FFBAC)       
        mu.reg_write(UC_X86_REG_RIP, 0x2E1010)        
        mu.hook_add(UC_HOOK_CODE, self.trace)        # hook代码执行,保存代码块执行轨迹        
        self.mu = mu        
        self.except_addr = 0        
        self.traces = []        # 用来保存代码块执行轨迹     
	def trace(self, mu, address, size, data):        
		if address != self.except_addr:            
			self.traces.append(address)        
			self.except_addr = address + size     
	def start(self):        
		try:            
			self.mu.emu_start(0x2E1010, -1)        
		except:            
		pass        print([hex(addr)for addr in self.traces]) Unidbg(b'SangFor{00000000000000000000000000000000}').start()

下面就可以用unicorn去hook访问input和key的代码块,同时借助unicorn还可以鉴别花指令和正常执行的指令,详见地球人的博客。后续观察出来了判断的逻辑直接爆破。

另外值得注意,在windows下调试时windbg对32/64位切换的支持较好,其他如ida、x32dbg等调试器在retf语句后都无法调试。

因为这点耽误了很多时间,比如ida虽可以通过断点进入64位部分,但是会一直报错不能调试。稍等研究下ida和windbg结合使用再更下。

思路二:官方wp里把64位部分dump出来重新编译成exe然后运行调试。通过观察寄存器的值发现的变换逻辑。但是这里不太了解dump出来之后那个masm格式怎么写的,再学下之后补上。

西湖论剑2023 Dual personality

挖坑尽快填上。这个题就是直接识别出64位的部分dump出来然后直接分析代码逻辑就好。