吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7255|回复: 42
收起左侧

[调试逆向] 天堂之门-调试器的末路

  [复制链接]
fxqn101 发表于 2024-3-20 14:45

不知道你有没有发现,在我们电脑的C盘里的windows文件夹下有一个SysWoW64,看起来像是一个开发者的感叹,他的功能是保存在64位电脑上仍需要运行的32位程序或者依赖。
这就引出了一个在动态调试层面恐怕是最有难度的逆向反调试--天堂之门(Heaven's Gate)
这一篇文章已经写了如何在C语言中使用天堂之门技术
https://bbs.kanxue.com/thread-270153.htm
所以在此我不会过多赘述实现机制,而是在反汇编层面进行解析

一、实现原理

天堂之门的实现主要依靠操作系统提供的在不同位数CPU进行跨架构的指令调用,这使得32位和64位的指令环境可以放在同一个程序中,但目前的调试器几乎没有能够跨架构的,所以程序能够标准的在操作系统中进行操作,但是在调试器中,当走到跨架构的指令时,就会因为指令无法识别而跳飞。
而在代码层面实现之后,在反汇编层面会有一些比较明显的指令,其中分为从64位跳转到32位,该种指令如下:
jmp far 33:地址
或者

push 0x33
call $+5
add dword [esp], 5
retf

我们稍微了解一下这些指令
首先jmp far和jmp的区别是jmp far会比jmp多执行一个指令,即在修改ip的值之外还会修改CS的值,修改的值就是far后面跟的数字,在硬编码层面表现如下:
EA 77 3F 41 00 33 00
可以看见,这个指令就是在jmp一个地址的硬编码后面加上四字节的数字,这个数字就是CS修改为多少,32位寄存器CS值是0x22,64位CS寄存器值是0x33。
在ida中,修改的值不会直接显示,我们需要设置option->general->Number of opcode bytes,将后面的框中的数字改为9或者其他值(这个数字的意思是每一行能够同时指令的几个硬编码)
下图是实例
image.png
image2.png

这样就能在ida里面看见每条指令对应的硬编码了。
一旦理解了jmp far指令,push 0x33call $+5 和add dword [esp], 5 这三条指令理解起来就比较方便了

push 0x33 在栈中压入0x33,作为CS寄存器的新值。
call $+5 下一条指令的地址入栈,并继续执行下一条指令
add dword [esp], 5 栈顶的返回地址加5,指向retf的下一条指令
retf, 返回到下一条指令继续执行,同时会pop ippop cs

32位转成64位的如下

call $+5
mov dword [rsp + 4], 0x23
add dword [rsp], 0xDretf

原理和上面的一样,利用retf将CS寄存器的值修改,这样就可以达到在程序实现32位代码和64位代码之间的转换。

二、例题

1、西湖论剑2023 Dual personality

下载附件,打开IDA,首先就发现一片数据和标红
image3.png
在上面还调用了其他的函数,有一个函数是用来修改内存的,调试查看修改了什么
image4.png
从f5反汇编之后得到的代码来看,这段代码不仅修改了返回地址,还修改了开辟出来的地址,返回地址,即call这个函数的下一个指令的地址在被修改之后变成了如下
image5.png
这里是IDA反汇编错误,根据硬编码来看,应该是
jmp far 33:0x4011d0
所以这里天堂之门真正跳转的地址是0x4011d0,而4011d0也是我们刚才修改过内存的地址,我们直接G跳过去发现这段地址ida在乱编,显然没识别出来是什么指令
image6.png
因为一开始用的是IDA32位,现在转到64位的环境无法反汇编,所以我们需要对程序的十六进制修改
image7.png
红色框住的是PE结构的magic魔术字
说明文件类型:10B 32位下的PE文件     20B 64位下的PE文件
修改魔术字为20B,然后放到64位IDA中,修改基址为0x40000,会自动修复基址,之后直接G跳转到11d0
image8.png
image9.png
看上去就很正确了,这里有个反调试,gs:60h是PEBeginDebugging反调试位,如果检测到正在调试,该位为1,反之为0,这段代码在没有调试的情况下会把0x5DF966AE赋值给dword_407058。
之后跳转到0x1E0000,从这里在跳转回去减去0x21524111得到一个加秘密钥
image10.png
image11.png
得到key之后跳转到下面进行第一次加密
image12.png
很容易分析并逆向出解密脚本

for (int i = 0; i < 8; ++i){
    DWORD bak = ans[i];
ans[i] = (ans[i] - key) & 0xFFFFFFFF;
key ^= bak;
}

BYTE *p = (BYTE *)ans;
for(int i=0;i<0x20;++i)
printf("%c", *(p+i));

相同的跳转总的有3个
第二个
image13.png
到40700地址上的值,直接返回去看32位程序的40700
image14.png
跳转到了401200,而我们刚才就在64位的程序上看见了401200,P生成函数进行f5,就可以看见加密函数
这里也进行了PEBeginDebugging反调试,检测到调试器和没有检测到调试器的加密会不同,如下
image15.png
第三个对应的跳转到401290,到64位上反汇编如下
image16.png
所以总的解密代码如下

def xor_reverse(data, key):
    for i in range(len(data)):
        data[i] ^= key
    return data

def rol_reverse(data, offset):
    tmp_arr=[]
    for i in range(4):
        tmp_arr.append((data[i*2+1] << 32) + data[i*2])
    for i in range(4):
        tmp_arr[i] = ((tmp_arr[i] >> offset[i]) | ((tmp_arr[i] << (64-offset[i])) & 0xffffffffffffffff)) & 0xffffffffffffffff
    for i in range(4):
        data[2*i] = tmp_arr[i] & 0xffffffff
        data[2*i+1] = tmp_arr[i] >> 32
    return data

def deffusion_reverse(data, factor):
    for i in range(len(data)):
        tmp = data[i]
        data[i] = (data[i] - factor) & 0xffffffff
        factor ^= tmp
    return data

def main():
    cmp_data = [0xE20F4FAA, 0x549941E4, 0x7E842B2C, 0x788B8FBC, 0x5E8873D3, 0x708547AE, 0xCE09B331, 0xCA0DF513]
    final_key = 0x4a827704
    rol_offset = [0xc, 0x22, 0x38, 0xe]
    diffusion_factor = 0x3CA7259D
    flag_arr = xor_reverse(cmp_data, final_key)
    flag_arr = rol_reverse(flag_arr, rol_offset)
    flag_arr = deffusion_reverse(flag_arr, diffusion_factor)
    print('DASCTF{', end='')
    for i in flag_arr:
        print(int.to_bytes(i, 4, 'little').decode(),end='')
    print('}')

if __name__ == "__main__":
    main()
#DASCTF{6cc1e44811647d38a15017e389b3f704}

灵感、解密脚本来源 https://kamasammohana.github.io/

2、DubheCTF 2024

从西湖论剑的题型可以看出,天堂之门这个反调试技术单独使用还是比较容易看得出来,还需要结合其他混淆和反调试技术一起使用,由于跨架构指令不能直接动调,所以逆向难度会极大的上升。
这个题就采用了SEH异常处理反调试+天堂之门的手法,隐藏掉天堂之门的入口指令和没有进入天堂之门前的一次加密,极大的提升了题目的难度。
打开附件,在主函数上有一个很明显的SEH异常处理函数
如果我们这个时候进行f5反编译,那么会是这样
image17.png
但是根据汇编层面的观察,不可能只有这么点代码,往上面观察,可以看出程序一开始就加载了异常处理句柄
image18.png
在接收了字符串之后马上写入了一个了try块,保存完关键地址后就int 3造成中断异常
image19.png
异常之后当然是进行异常处理,而回调函数地址就是被push进栈中的loc_4140d7,这个地址被引用到异常处理结构体中
image20.png
我们跳转到4140d7观察,看到很多数据,做题时以为是移动返回地址,复现时看了出题人的出题笔记才知道是花指令
下个硬断单步跟,但是首先要把其他隐藏在程序里面的花指令去掉,使用AntiDebuggerSeeker查就行
image21.png
image22.png
这一处是加载ntdll,直接把eax改成0
image23.png
这一处是要把调用ZwSetInformationThread反调试的调用号给去掉,改成0
image24.png
保存整个程序,利用OD进行调试,打开OD,f9到0x4140D7,快捷键Alt+O打开调试选项面板,在异常中去掉勾选忽略(传递给程序)以下异常:INT 3中断
image25.png
这样int 3中断之后不会跳飞,这个时候再下一个断点到异常处理回调函数4140d7上,就可以单步调试代码了
image26.png
调试几步后发现压入了一些数,0x32在后面可以知道是循环次数,0x5B4B9F9E很明显是一个常数,这个时候就可以猜测是TEA族
image27.png
image28.png
这里是将输入按照四字节分组压入栈
image29.png
image30.png
左移5位
image31.png
左移2位
image32.png
进行异或
image33.png
看到这里就已经知道是XXtea了,到这里需要找到密钥key,重新打开IDA,在密文下面翻得到
image34.png
由于栈展开之后会回调两次异常处理函数,所以这里的xxtea也会执行两次
到这里总算算是把入天堂之门前的加密代码解析成功,之后回调函数回来时执行except块,也就是
image35.png
这里就是典型的门了,直接转x32Dbg,打开sharpOD,把能勾选的反反调试全勾上
image36.png
然后一路调试到jmp far这个指令,直接跳转进jmp far后面的地址
image37.png
将该地址用scylla dump下来放进ida就可以看到门后的加密了
image38.png
可以看出是crc算法
所以加密算法是两次xxtea+一次crc,直接解密就行,exp如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
#define DELTA 0x5B4B9F9E
void crcbreak(uint32_t* flagbuf, int key) {
    for (int i = 0; i < 12; i++) {
        uint32_t enc_num = *(flagbuf + i);
        for (int j = 0; j < 32; j++) {
            if (enc_num & 1)
            {
                enc_num ^= key;
                enc_num /= 2;
                enc_num |= 1 << 31;
            }
            else enc_num /= 2;
        }
        //printf("0x%X, ", enc_num);
        //0x9E549543, 0x5E7CB348, 0xD9A84A2F, 0x85EB99DE, 0xB6825884, 0xC4F74EA1, 0x22B1828A, 0x290D7296, 0x198EE473, 0x9655B529, 0x38AC196A, 0x192B6236,
        *(flagbuf + i) = enc_num;
    }
}

void btea(uint32_t* v, int n, uint32_t const key[4])
{
    uint32_t y, z, sum;
    unsigned p, rounds, e;
    //加密
    if (n > 1)
    {
        rounds = 6 + 52 / n;
        sum = 0;
        z = v[n - 1];
        do
        {
            sum += DELTA;
            e = (sum >> 2) & 3;
            for (p = 0; p < n - 1; p++)
            {
                y = v[p + 1];
                z = v[p] += MX;
            }
            y = v[0];
            z = v[n - 1] += MX;
        } while (--rounds);
    }
    //解密
    else if (n < -1)
    {
        n = -n;
        rounds = 0x32;
        sum = rounds  * ((~DELTA) + 1);
        y = v[0];
        do
        {
            e = (sum >> 2) & 3;
            for (p = n - 1; p > 0; p--)
            {
                z = v[p - 1];
                y = v[p] -= MX;
            }
            z = v[n - 1];
            y = v[0] -= MX;
            sum -= ((~DELTA) + 1);
        } while (--rounds);
    }
}

int main() {
    uint32_t flagbuf[] = {
        0xA790FAD6, 0xE8C8A277, 0xCF0384FA, 0x2E6C7FD7, 0x6D33968B, 0x5B57C227, 0x653CA65E, 0x85C6F1FC,
        0xE1F32577, 0xD4D7AE76, 0x3FAF6DC4, 0x0D599D8C
        };
    int key = 0x84A6972F;
    uint32_t const k[4] = { 0x6B0E7A6B, 0xD13011EE, 0xA7E12C6D, 0xC199ACA6 };
    crcbreak(flagbuf, key);
    btea(flagbuf, -12, k);
    btea(flagbuf, -12, k);
    puts((const char *)flagbuf);
    printf("\n\n");
    return 0;
    //DubheCTF{82e1e3f8-85fe469f-8499dd48-466a9d60}
}

免费评分

参与人数 27威望 +2 吾爱币 +124 热心值 +24 收起 理由
5omggx + 1 + 1 用心讨论,共获提升!
iTMZhang + 1 + 1 用心讨论,共获提升!
Chenda1 + 1 + 1 热心回复!
pojie20230721 + 1 + 1 我很赞同!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
janken + 1 + 1 热心回复!
AK471 + 1 用心讨论,共获提升!
hmg668 + 1 鼓励转贴优秀软件安全工具和文档!
allspark + 1 + 1 用心讨论,共获提升!
lxb3333111 + 1 + 1 我很赞同!
XG123114 + 1 鼓励转贴优秀软件安全工具和文档!
Ak47_2016 + 1 + 1 谢谢@Thanks!
gaosld + 1 + 1 谢谢@Thanks!
dandna + 1 热心回复!
yp17792351859 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
willJ + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
owouwu + 1 + 1 用心讨论,共获提升!
BIGSMATER + 1 + 1 用心讨论,共获提升!
skiss + 1 + 1 谢谢@Thanks!
koyun88 + 1 + 1 谢谢@Thanks!
jj9 + 1 热心回复!
hefan8 + 1 用心讨论,共获提升!
nsdsyssy + 1 + 1 谢谢@Thanks!
天地英雄 + 1 + 1 用心讨论,共获提升!
xxxlsy + 1 + 1 我很赞同!
hanlaoshi + 1 + 1 谢谢@Thanks!
狄人3 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

blackfrey 发表于 2024-3-20 18:43
gmg2719 发表于 2024-3-20 17:44
如果找一个32位的操作系统,在上面调试32位的程序的话。是否就可以简单的多了。

那如果程序有64位的部分,不是直接歇菜?
有些程序不是说全部都是32位的,而是主体是64位,不过有些库比较经典比较老,这些库是32位的。
gmg2719 发表于 2024-3-20 17:44
如果找一个32位的操作系统,在上面调试32位的程序的话。是否就可以简单的多了。
hanlaoshi 发表于 2024-3-20 17:49
yjy66630 发表于 2024-3-20 17:59
看着好厉害啊
PJ997272250 发表于 2024-3-20 18:10
看着好厉害啊
ephemere 发表于 2024-3-21 08:14
大佬厉害,进来要经常学习,向大佬看起
gmg2719 发表于 2024-3-21 08:18
blackfrey 发表于 2024-3-20 18:43
那如果程序有64位的部分,不是直接歇菜?
有些程序不是说全部都是32位的,而是主体是64位,不过有些库比 ...

了解了,也就是属于64+32混合型的。
jj9 发表于 2024-3-21 11:45
看着好厉害
xiaoweng 发表于 2024-3-21 17:52
不明觉厉,感谢楼主分享的给力文章
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-14 14:50

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表