2020网鼎杯玄武组_babyvm
2020-05-26 17:43:31 Author: bbs.pediy.com(查看原文) 阅读量:489 收藏

这个组,是朋友赛后让我看的这个题,个人并没有参赛,当时也没给做出来,后续断断续续的的调试,工作,好久好久才做出来,贼鸡儿菜,打吉尔比赛。

基本流程分析

IDA分析,发现没有符号,有点函数跟的很深具体也不知到什么作用,这是结合动态调试,看看函数的输入输出,可以大概知道函数做了什么,并把函数根据功能进行重命名

这里再正确flag的上面有一个函数,显然即使check,传入的就是str->大写hex的值

下面进入check分析

分析check时,这里还是通过动态调试,确定了几个函数

  1. 偏移D410 -->getmd5,传入一个input,和一个buffer最终会计算根据input计算处md5存入buffer
  1. 偏移D290-->gettable,这个函数还是输入的是input,和一个table缓冲区
  1. 偏移CF00-->jisuan
  1. 偏移CD50-->vm 这里传入就是三个jisuan调用而产生的3个值
  1. 偏移CCB0

    结果表的值:

    [0x0c5d83690,0x1f7902cc,0x978162ea,0x2fae3d15,0x1932a96c,0xceebfe91,0x4222669,0xaff6af42]

    一共是8*4个字节

总结一下

下面把chek中的基本的函数就分析大概流程分析完了,下面总结一下check的基本流程:

  1. 根据用户输入的信息计算出md5,记作input_md5(后面方便引用)
  2. 根据用户输入计算出来一个100byte的表,记作input_table
  3. 算出input_table的md5,记作table_md5(这里后面也没有比较什么的,真是绝了,爆破吧)
  4. 进入一个循环,根据开始,和结束条件,可以知道总共循环4次,并且会调用三个jisuan,分别传入的是input,input_md5,input_table,先把input_md5的4个字节放入v9,再进入sub_140075159出入计算(记作vm),计算的结果拼接v9,这么一算得到的v9格式就是(input_md5,vm,input_md5+4,vm+4,....,input_md5+12,vm+12)总共8*4个字节,刚和和结果的表对应

算法还原

vm流程算法

check的流程分析结束,下面就要还原算法了,

  1. flag为去掉格式为32个字符长度,最开始将32个字符转为hex值放进内存,算出MD5需要满足下表

根据结果表,提出来md5正确的输入

[0x0c5d83690,0x978162ea,0x1932a96c,0x4222669]

MD5如何保证固定?难道要撞嘛?

  1. flag为去掉格式为32个字符长度,不仅要满足上述条件,还要满足vm运算结果的匹配

{0x1F7902CC,0x2FAE3D15,0x0CEEBFE91,0x0AFF6AF42}

先分析vm的流程,首先要知道传入vm中的表的结构,这里只能动态调试,把之前的反调试patch掉,开始调试,再array的地方下断点,观察array的内存变化

array的结构如下

opcode表如下,并不会根据输入的不同而不同,基本上固定的操作路径,vm计算产生的中间值其实就是放到array中,和array+8的指针中的

最后9ea4eff588保存的内容如下,第一个是用户输入,第二个给是用户输入产生的MD5,第三个是用户输入得到的100byte的md5

知道了传入vm的array的结构,下面分析vm的流程,人肉调试还原

总体的结构是这样的,吐了。。。。。

根据下面的分析,每次寻址是*(array + (array + 20)),可以知道*array就是opcode,*(array+20)则是偏移量,由此可知,opcode的寻址方式,可以发现每个case中让array移动都是操作*(array+20)

我的方法是,再每个case的偏移加上注释,动态调试一边关注array的变化,结合静态分析推断处每个case的作用,再在x64debug的对应位置做好标注,进行跟踪就很容易看出来规律了。

下面给一些重要的流程函数标记出来,然后跟踪找规律

下面重点的按照处理流程顺序还原几个重要的case

  1. case 17: 有个函数,主要做的操作就是先右移在左移,每次移动产生的结果再在一起或运算得出结果,记作r2l(input,i),这里的i经过跟踪发现是递减的7~0
   unsigned int r2l(unsigned int *input,unsigned int i){//i = 7~0 共执行了八轮 。整体3个函数共执行了2论

       unsigned int tmp;
       unsigned int tmp2;
       tmp = *input >> i;
       i=0x20-i;
       tmp2 = *input << i;
       return tmp | tmp2
   }
  1. case 12: 有个函数,主要做的操作就是上一次‘r2l’产生的结果与固定的值,0x9e3779b9进行异或,将结构保存到array结构中
  1. case18:有个函数,主要做的操作就是先做移在右移,每次移动产生的结果再在一起或运算得出结果,记作l2r(input,i),这里的i已经是固定值了,就是6
   unsigned int l2r(unsigned int *input,unsigned int i){ //i=6
       unsigned int tmp;
       unsigned int tmp2;
       tmp = *input << i;
       i=0x20-i;
       tmp2 = *input >> i;

       return tmp | tmp2;
   }

4.下面再次一直运行可以看到其他的case并没有操作存在与array中的结果,而都是操作的操作表的索引,或者其他的,一直调式运行分析发现法律,一直在调用上面的3个case,并更新着结果值,分析还原整体的流程,小流程做了8轮(0~7)大流程做了两轮

   result = input
   for(i=0;i<2;i++)
           {
               for(j=7;j>=0;j--)
               {
                   result = r2l(result,j);
                   result = xor(result);
                   result = l2r(result,6);
               }
           }

继续往下跟

  1. case 16:执行或操作,具体的数值关注eax,ecx对应哪一个步骤产生的值
  1. case 15:执行或操作,具体数值管eax,ecx,确定对应那一步的计算
  1. case 19: 执行按位取反操作,将上述步骤产生的值按位取反

这里跟了很多次,因为每一次位运算产生的结果会参与到不同流程的计算,这里要十分仔细,我当时就是拿着纸笔一条一条的记录数据。

最终的操作如下:

获取步骤4产生的值(记作re)

~((re | md5) ^ (md5_table & md5) ^ (re & md5_table) ^ (re & md5_table & md5) ^ (re | md5_table | md5))

根据标注,其实追踪也能发现规律。

总结一下

  1. sub_140075159是vm流程的计算,传入的就是输入,md5,以及md5_table
  1. 进入sub_140075159,首先初始化一个array结构,再将array传入vm函数进行vm流程的计算

  2. 还原vm流程,先进行r2l,xor,l2r共2*8==16轮

  3. 还原vm流程,获取r2l,xor,l2r共2*8==16轮的结果(记作re),j进行下面的计算

    这里经过大佬的指点可以这样化简,原来想的是爆破

    (re | md5) ^ (md5_table & md5) ^ (re & md5_table) ^ (re & md5_table & md5) ^ (re | md5_table | md5)

    re == A

    md5 == B

    md5_table == C

    (A|B) ^ (C&B) ^ (A&C) ^ (A & C & B) ^ (A | C | B) == A ^ B ^ C

  4. 最终产生的就是vm的运算结果

    (input_md5,vm,input_md5+4,vm+4,....,input_md5+12,vm+12)

    vm的结果因该是:

    {0x1F7902CC,0x2FAE3D15,0x0CEEBFE91,0x0AFF6AF42}

    input_md5的结果因该是

    {0x0c5d83690,0x978162ea,0x1932a96c,0x4222669}

解密思路

已经有了md5的正确结果,以及input的正确结果,但是md5_table还不知道

下面分析一下md5_table的产生:

  1. table的产生gettable

  2. 100比特的表进入md5 就获取了md5_table,由于table有有限个可能,那么md5_table也就有有限个可能

    那么就可以编码求出100个md5_table然后参与解密运算中

接下来用枚举出来的md5_table和vm正确的结果,md5的正确结果返推出input。但是这会产生多个input,因为不一定满足md5,这是再次比较md5筛选出真正的input

#include <windows.h>
#include <stdio.h>
byte table[100] = { 0 };
BOOL getMd5(BYTE *pbData, DWORD dwDataLen, BYTE *md5, DWORD &err)
{
    HCRYPTPROV hProv;
    if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
    {
        err = GetLastError();
        return FALSE;
    }
    HCRYPTHASH hHash;
    if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash))
    {
        err = GetLastError();
        CryptReleaseContext(hProv, 0);
        return FALSE;
    }
    if (!CryptHashData(hHash, pbData, dwDataLen, 0))
    {
        err = GetLastError();
        CryptDestroyHash(hHash);
        CryptReleaseContext(hProv, 0);
        return FALSE;
    }
    DWORD dwSize;
    DWORD dwLen = sizeof(dwSize);
    CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)(&dwSize), &dwLen, 0);
    BYTE* pHash = new BYTE[dwSize];
    dwLen = dwSize;
    CryptGetHashParam(hHash, HP_HASHVAL, pHash, &dwLen, 0);
    for (DWORD i = 0; i < dwLen; i++) {
        md5[i] = pHash[i];
    }

    delete[] pHash;
    CryptDestroyHash(hHash);
    CryptReleaseContext(hProv, 0);
    return TRUE;
}

void getTable(byte mod) {
    byte v8 = mod;
    byte v10;
    for (int k = 0; k < 100; k++) {
        v10 = 0;
        v8 ^= 0xc3;
        *(byte*)(table + k) = v8;
        for (int l = 0; l < 8; l++) {
            v10 ^= (v8 >> l) & 1;
        }
        v8 = v10 | (2 * v8);
    }
}
int main()
{
    unsigned    int vm[] = { 0x1F7902CC ,0x2FAE3D15 ,0xCEEBFE91 ,0xAFF6AF42 };
    unsigned int md5[] = { 0xC5D83690 ,0x978162EA ,0x1932A96C ,0x4222669 };
    BYTE md5_byte[20] = { 0x90,0x36,0xD8,0xC5,0xEA,0x62,0x81,0x97,0x6C,0xA9,0x32,0x19,0x69,0x26,0x22,0x04 };
    byte md5_table[16];
    unsigned int input;
    byte input_src[16] = { 0 };
    DWORD err;
    for (int mod = 0; mod < 100; mod++) {//枚举模数
        getTable(mod);
        getMd5(table, 100,md5_table, err);
        for (int p = 0; p < 4; p++) {
            input = ~vm[p];
            input = input ^ md5[p] ^ *((int*)md5_table+p);
            for (int k = 0; k < 2; k++) {
                for (int l = 0; l < 8; l++) {
                    input = ((input >> (6)) | (input << (0x20 - 6)));
                    input = input ^ 0x9E3779B9;
                    input = ((input << (l)) | (input >> (0x20 - l)));
                }
            }
            //小端序
            input_src[p * 4 + 3] = (byte)(input >> 24) & 0xff;
            input_src[p * 4 + 2] = (byte)(input >> 16) & 0xff;
            input_src[p * 4 + 1] = (byte)(input >> 8) & 0xff;
            input_src[p * 4 + 0] = (byte)(input >> 0) & 0xff;
        }

        byte md5_2[16];
        bool flag = true;
        getMd5(input_src, 16, md5_2, err);
        for (int i = 0; i < 16; i++) {
            if (md5_2[i] != md5_byte[i]) {
                flag = false;
                break;
            }
            if (flag) {
                printf("flag{%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n", input_src[0], input_src[1], input_src[2], input_src[3], input_src[4], input_src[5], input_src[6], input_src[7], input_src[8], input_src[9], input_src[10], input_src[11], input_src[12], input_src[13], input_src[14], input_src[15]);
            }
        }


    }
    return 0;
}

[推荐]看雪工具下载站,全新登场!(Android、Web、漏洞分析还未更新)

最后于 1天前 被Craft_A编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-259714.htm
如有侵权请联系:admin#unsafe.sh