西湖论剑2023 Dual personality 复现

西湖论剑2023 Dual personality 复现

一点小知识:天堂之门
天堂之门是一种利用Windows的WoW机制,在32位的进程中执行64位程序代码,达到反检测的目的(反静动态分析)。
那么什么是WoW?他的全称是Windows 32 on Windows 64。众所周知,win64平台向下兼容,可以在x64平台上运行x86程序,而Windows之所以可以做到这点,就是因为使用了wow作为翻译层。

而天堂之门就是利用了这个机制,在32位程序中插入64位程序代码,让逆向分析工具不能正常分析成功。同理如果我们想要让他能够被正常分析,我们也只需要找到程序中被插入的64位代码,然后将其dump出来单独分析即可。当CS = 0x23,执行32位程序;CS = 0x33,执行64位程序。天堂之门技术通过修改cs的值实现在32位程序种调用64位的代码,导致调试器出错并限制逆向人员的动静态分析。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/m0_52164435/article/details/129540631

那么这个程序是如何实现天堂之门技术的呢,在里面我们可以发现sub_401120这个函数,而我们用ida32打开并动调,执行完这个函数后,会发现程序变得非常诡异,完全不知道接下来是怎么运行的,其实这时已经进入了64位程序并执行了,但在ida32或x86dbg中都是完全无法追踪这个过程。

可以看到,在执行sub_401120前push了 0x4011D0,而这个东西就是第一个64位的指令函数地址。

在这里插入图片描述

这个函数sub_401120执行完后,按c键重新识别语句,会发现机器码变成:

4013E8        EA D0 11 40 00 : jmp    far ptr loc_4014FE + 2            (这里应该是jmp far ptr sub_4011D0??但在32位ida里识别不出来)

4013E8        33 00

而 jmp far ptr xxxx的特性:


jmp far ptr xxxx

    而且执行该jmp指令后,会修改cs,ip寄存器。 ip变为EA后的前8位,在这里即0x004011D0;cs变为EA后的后4位,这里是0x0033

而我们可以看到在新生成的指令中有0x33的字样,可以得知就是这里jmp到401120后,把cs寄存器的值改成了0x33,进入了64位程序执行模式,也就是这题的万恶之源。

接下来开始正式对题目进行流程分析:

ida打开,首先输入flag,然后经过一个sub_401120函数,通过将cs寄存器变为0x33,就可在32位程序里执行64位指令,经过这个函数后,后面的指令发生了改变,这应该是一个jmp far ptr sub_4011D0的指令。

在这里插入图片描述

将0x4011D0处指令dump下来,然后用ida64打开。

在这里插入图片描述

看看这个程序的逻辑,第一句代码应该是mov rax, gs:60h,就是获取到64位下的PEB结构,但不知道我这里为什么会显示成这样。

那么后面就是将检测是否正在调试的值存到 ds:40705Ch 处,如果检测到这个值不为0,即正在调试中,则直接跳回;如果为0,即不在调试中,则将0x5DF966AE存放在0x407058h处,以此达成反调试的效果。

在这里插入图片描述

然后跳回到cs:[0x40700]处,即转到0x1E000处,然后又跳回0x4013EF处。
在这里插入图片描述

在这里插入图片描述

此时将0x407058处的值拿出来,应该是0x5DF966AE,再进行接下来的运算,经过简单的分析。

第一部分的加密伪代码可表示为

uint32_t enc[8];    //四字节
int delta = 0x5DF966AE;
delta -= 0x21524111;
for(int i=0;i<8;i++)
{
    enc[i] += delta;
    delta ^= enc[i]; 
}

接着往下分析,看到call fword ptr byte_0x40700C,也就是call far ptr 0x401200,注意到byte_0x40700C后面有个0x33,也就是说又进入了64位的程序。

在这里插入图片描述

在这里插入图片描述

而0x401200处值就在dword_4011D0之中,为0xEC8B4855,再回到之前dump出来的代码,会发现这就是这个sub_30函数的开头,按p构建其函数,查看伪代码。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这个加密函数的逻辑也很简单,通过之前的分析即可得知0x40705C是存放检测调试的值,如果其值为1,即被调试中,则执行上面的假加密;否则则执行下面的真加密。

第二部分加密的伪代码:

uint64_t _ROL8_(uint64_t num,int value) //循环向左位移
{
    uint64_t a, b;
    value %= 64;
    a = num << value;
    b = num >> (64-value);
    return a | b;
}
uint64_t enc[4];   //八字节
enc[0] = _ROL8_(enc[0], 12);
enc[1] = _ROL8_(enc[1], 34);
enc[2] = _ROL8_(enc[2], 56);
enc[3] = _ROL8_(enc[3], 14);

接着往下分析,又看到调用了sub_401120,下面又是一个跟第一处加密一样的套路,跳转到sub_401290,同样dump下来用ida64打开。

在这里插入图片描述

在这里插入图片描述

查看程序,发现从发现从0x407014开始每四个字节进行了一系列运算,这四个数并不是密文部分,他们是可以在进入sub_401290前截取到的,再结合下面的分析可知道这四个数是key,初始值分别为0x9D,0x44,0x37,0xB5。在这里插入图片描述

因此第三部分加密伪代码为

uint32_t key[4] = {0x9D, 0x44, 0x37, 0xB5};
key[0] &= key[1];
key[1] |= key[2];
key[2] ^= key[3];
key[3] = ~key[3];

回到源程序继续分析,下面这部分仍然是64位的代码,ida32中无法识别,于是再次dump下来到ida64中查看(从0x40146F开始)。
在这里插入图片描述

在这里插入图片描述

可见引用了0x407014,即key指针,并进行了异或操作,而0x407060处正是存放密文的部分,因此这里就是个简单的单字节异或加密

第四部分的伪代码如下:

unsigned char enc[32]; //单字节
for(int i = 0;i < 32;i++)
{
    enc ^= key[i % 4];
}

知道了加密流程后,那么exp就很容易得出来了,但这里涉及到一点数据类型的转换,需要稍微花点功夫

#include<iostream>
#include<stdint.h>
using namespace std;
unsigned char enc1[] =
{
  0xAA, 0x4F, 0x0F, 0xE2, 0xE4, 0x41, 0x99, 0x54, 0x2C, 0x2B, 
  0x84, 0x7E, 0xBC, 0x8F, 0x8B, 0x78, 0xD3, 0x73, 0x88, 0x5E, 
  0xAE, 0x47, 0x85, 0x70, 0x31, 0xB3, 0x09, 0xCE, 0x13, 0xF5, 
  0x0D, 0xCA
};

uint64_t _ROR8_(uint64_t num,int value) //旋转循环位移
{
    uint64_t a, b;
    value %= 64;
    a = num >> value;
    b = num << (64-value);
    return a | b;
}

int main()
{

    uint32_t key[4] = {0x9D, 0x44, 0x37, 0xB5};
    key[0] &= key[1];
    key[1] |= key[2];
    key[2] ^= key[3];
    key[3] = ~key[3];

    for(int i=0;i<32;i++)
    {
        enc1[i] ^= key[i%4];
    }

    uint64_t enc2[4];
    for(int i=0;i<4;i++)
    {
        enc2[i] = *((uint64_t*)&enc1[i*8]);    //单字节转8字节 
    }

    enc2[0] = _ROR8_(enc2[0],12);
    enc2[1] = _ROR8_(enc2[1],34);
    enc2[2] = _ROR8_(enc2[2],56);
    enc2[3] = _ROR8_(enc2[3],14);


    uint32_t enc3[8];
    int delta = 0x5DF966AE,tmp;
    delta -= 0x21524111;
    for(int i=0;i<8;i++)
    {
        enc3[i]=*(((uint32_t *)enc2)+i);  //8字节转4字节 
        tmp = enc3[i];
        enc3[i] -= delta;
        delta = delta ^ tmp; 
    }

    unsigned char a;
    for(int i=0;i<8;i++)
    {
        for(int k=0;k<4;k++)    
        {
            a = *(((unsigned char *)&enc3[i])+k);  //4字节转单字节 
            printf("%c",a);
        }

    }

}

后记:
在这里插入图片描述

这道题从接触到完成复现前前后后快用了一个月,我记得当时做的时候快把我给搞崩溃了,没见过连动调也如此诡异的。

后来看到题解后有种恍然大悟的感觉,原来是用了天堂之门技术啊,但想着也就这样了,只是我不知道而已,没有好好地复现。

直到我昨天开始试着做,深刻领悟到什么叫“看着容易,做起来难”。这题并不只是难在天堂之门技术,还有很多我不熟悉的基础知识,这也让我有了要认真复现以前做过的题的想法,因为很多东西不是看看就会的,必须要亲手实践,这样才能让更多知识刻在脑子里。

漫长的复现计划要开始了

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。在编写C程序时,需要注意变量的声明和定义、指针的使用、内存的分配与释放等问题。C语言中常用的数据结构包括: 1. 数组:一种存储同类型数据的结构,可以进行索引访问和修改。 2. 链表:一种存储不同类型数据的结构,每个节点包含数据和指向下一个节点的指针。 3. 栈:一种后进先出(LIFO)的数据结构,可以通过压入(push)和弹出(pop)操作进行数据的存储和取出。 4. 队列:一种先进先出(FIFO)的数据结构,可以通过入队(enqueue)和出队(dequeue)操作进行数据的存储和取出。 5. 树:一种存储具有父子关系的数据结构,可以通过中序遍历、前序遍历和后序遍历等方式进行数据的访问和修改。 6. 图:一种存储具有节点和边关系的数据结构,可以通过广度优先搜索、深度优先搜索等方式进行数据的访问和修改。 这些数据结构在C语言中都有相应的实方式,可以应用于各种不同的场景。C语言中的各种数据结构都有其优缺点,下面列举一些常见的数据结构的优缺点: 数组: 优点:访问和修改元素的速度非常快,适用于需要频繁读取和修改数据的场合。 缺点:数组的长度是固定的,不适合存储大小不固定的动态数据,另外数组在内存中是连续分配的,当数组较大时可能会导致内存碎片化。 链表: 优点:可以方便地插入和删除元素,适用于需要频繁插入和删除数据的场合。 缺点:访问和修改元素的速度相对较慢,因为需要遍历链表找到指定的节点。 栈: 优点:后进先出(LIFO)的特性使得栈在处理递归和括号匹配等问题时非常方便。 缺点:栈的空间有限,当数据量较大时可能会导致栈溢出。 队列: 优点:先进先出(FIFO)的特性使得

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值