一个ReverseMe的算法分析

来源:互联网 发布:中学生网络成瘾 编辑:程序博客网 时间:2024/06/04 19:20

这个ReverseMe是我一年前写的,不过现在源代码丢了而且怎么写的也忘了。正好昨天逛一个论坛的时候看到了这个ReverseMe,就顺便下载玩了玩(也算是重温了一下),于是就有了这篇文章。
因为篇幅有限 我就写写关键的地方。
0x0 寻找算法地址
直接来到main函数(0x004019B2)处。
程序首先获取ntdll!ZwContinue函数的地址,然后保存到0x00417F7C处。

004019BE    68 DC504100     push    004150DC                         ; NtContinue004019C3    68 E8504100     push    004150E8                         ; n004019C8    33F6            xor     esi, esi004019CA    33DB            xor     ebx, ebx004019CC    C705 787F4100 0>mov     dword ptr [0x417F78], 00412000004019D6    FF15 04304100   call    dword ptr [<&KERNEL32.GetModuleH>; kernel32.GetModuleHandleW004019DC    50              push    eax004019DD    FF15 0C304100   call    dword ptr [<&KERNEL32.GetProcAdd>; kernel32.GetProcAddress004019E3    68 FC504100     push    004150FC                         ; E004019E8    A3 7C7F4100     mov     dword ptr [0x417F7C], eax

继续往下走,发现程序设置了CONTEXT结构体然后调用ZwContinue函数。

00401ABB    8D05 3D1B4000   lea     eax, dword ptr [0x401B3D]        ; 返回地址00401AC1    50              push    eax00401AC2    9C              pushfd00401AC3    8F45 FC         pop     dword ptr [ebp-0x4]00401AC6    8B45 FC         mov     eax, dword ptr [ebp-0x4]00401AC9    8945 F0         mov     dword ptr [ebp-0x10], eax00401ACC    8965 FC         mov     dword ptr [ebp-0x4], esp00401ACF    8B45 FC         mov     eax, dword ptr [ebp-0x4]00401AD2    8945 F4         mov     dword ptr [ebp-0xC], eax00401AD5    896D FC         mov     dword ptr [ebp-0x4], ebp00401AD8    8B45 FC         mov     eax, dword ptr [ebp-0x4]00401ADB    C785 30FFFFFF 0>mov     dword ptr [ebp-0xD0], 0x10007    ; CONTEXT_FULL00401AE5    8945 E4         mov     dword ptr [ebp-0x1C], eax00401AE8    A1 547F4100     mov     eax, dword ptr [0x417F54]00401AED    8945 E8         mov     dword ptr [ebp-0x18], eax        ; Eip00401AF0    16              push    ss00401AF1    8F45 FC         pop     dword ptr [ebp-0x4]00401AF4    8B45 FC         mov     eax, dword ptr [ebp-0x4]00401AF7    8945 F8         mov     dword ptr [ebp-0x8], eax00401AFA    0E              push    cs00401AFB    8F45 FC         pop     dword ptr [ebp-0x4]00401AFE    8B45 FC         mov     eax, dword ptr [ebp-0x4]00401B01    8945 EC         mov     dword ptr [ebp-0x14], eax00401B04    1E              push    ds00401B05    8F45 FC         pop     dword ptr [ebp-0x4]00401B08    8B45 FC         mov     eax, dword ptr [ebp-0x4]00401B0B    8945 C8         mov     dword ptr [ebp-0x38], eax00401B0E    06              push    es00401B0F    8F45 FC         pop     dword ptr [ebp-0x4]00401B12    8B45 FC         mov     eax, dword ptr [ebp-0x4]00401B15    8945 C4         mov     dword ptr [ebp-0x3C], eax00401B18    0FA0            push    fs00401B1A    8F45 FC         pop     dword ptr [ebp-0x4]00401B1D    8B45 FC         mov     eax, dword ptr [ebp-0x4]00401B20    8945 C0         mov     dword ptr [ebp-0x40], eax00401B23    0FA8            push    gs00401B25    8F45 FC         pop     dword ptr [ebp-0x4]00401B28    8B45 FC         mov     eax, dword ptr [ebp-0x4]00401B2B    6A 00           push    0x000401B2D    8945 BC         mov     dword ptr [ebp-0x44], eax00401B30    8D85 30FFFFFF   lea     eax, dword ptr [ebp-0xD0]00401B36    50              push    eax00401B37    FF15 7C7F4100   call    dword ptr [0x417F7C]             ; ntdll.ZwContinue00401B3D    68 3C514100     push    0041513C                         ; pause00401B42    E8 A9000000     call    00401BF0

在Eip(0x7781656D)下断,然后F9,分析后容易发现:这个函数也是一个跳转函数,其中配置CONTEXT结构体并转移的代码如下:

778165E3    A1 547F4100     mov     eax, dword ptr [0x417F54]778165E8    0105 387B4100   add     dword ptr [0x417B38], eax778165EE    16              push    ss778165EF    8F05 4C7B4100   pop     dword ptr [0x417B4C]778165F5    A1 4C7B4100     mov     eax, dword ptr [0x417B4C]778165FA    A3 487B4100     mov     dword ptr [0x417B48], eax778165FF    0E              push    cs77816600    8F05 4C7B4100   pop     dword ptr [0x417B4C]77816606    A1 4C7B4100     mov     eax, dword ptr [0x417B4C]7781660B    A3 3C7B4100     mov     dword ptr [0x417B3C], eax77816610    1E              push    ds77816611    8F05 4C7B4100   pop     dword ptr [0x417B4C]77816617    A1 4C7B4100     mov     eax, dword ptr [0x417B4C]7781661C    A3 187B4100     mov     dword ptr [0x417B18], eax77816621    06              push    es77816622    8F05 4C7B4100   pop     dword ptr [0x417B4C]77816628    A1 4C7B4100     mov     eax, dword ptr [0x417B4C]7781662D    A3 147B4100     mov     dword ptr [0x417B14], eax77816632    0FA0            push    fs77816634    8F05 4C7B4100   pop     dword ptr [0x417B4C]7781663A    A1 4C7B4100     mov     eax, dword ptr [0x417B4C]7781663F    A3 107B4100     mov     dword ptr [0x417B10], eax77816644    0FA8            push    gs77816646    8F05 4C7B4100   pop     dword ptr [0x417B4C]7781664C    A1 4C7B4100     mov     eax, dword ptr [0x417B4C]77816651    6A 00           push    0x077816653    68 807A4100     push    0x417A8077816658    A3 0C7B4100     mov     dword ptr [0x417B0C], eax7781665D    FF15 7C7F4100   call    dword ptr [0x417F7C]             ; ntdll.ZwContinue77816663    90              nop77816664    FF05 507F4100   inc     dword ptr [0x417F50]

在0x7781665D处下断,拦截每次的Eip值。
拦截以后发现几个有用的函数:

0x00401B6D 接受用户名(存放在0x00417D50处)
0x00401B8B 接受注册码(存放在0x00417B50处)
0x7781668F(函数地址可变) 检查用户名是否是12个字节
0x778166BE(函数地址可变) 检查注册码是否是12个字节
0x00412057 算法部分
0x4123A9 返回结果

0x1 分析算法
来到0x00412057处,简单看看代码,发现一堆push call pop之类的指令,这里我使用IDA的F5插件来分析。
这里写图片描述
其中的EncryptData:
这里写图片描述

这里仅仅调用了两个函数:strlen和sub_401000。
我们目前需要做的就是分析出函数sub_401000是干什么的。
IDA进入401000处,发现代码很简短。
这里写图片描述
这段代码很简短,就是not not and,如果用一条指令来描述就是nor指令。
其中,有四个指令可以直接被nor模拟。

not(a) = nor(a,a)and(a,b) = nor(nor(a,a),nor(b,b)) = nor(not(a),not(b))or(a,b) = nor(nor(a,b),nor(a,b))xor(a,b) = nor(nor(nor(a,a),nor(b,b)),nor(a,b)) = nor(and(a,b),nor(a,b))

根据这个关系 我们尝试将这段算法给改写成not and or xor的形式

HRESULT __stdcall Decrypt(PINFORMATIONCARD_CRYPTO_HANDLE hCrypto, BOOL fOAEP, DWORD cbInData, PBYTE pInData, DWORD *pcbOutData, PBYTE *ppOutData){  HRESULT result; // eax@1  unsigned int i; // esi@1  int ByteOfSerial1; // ST28_4@2  int ByteOfUsername; // ST28_4@2  int ByteOfSerial; // ebx@2  int ByteOfUsername1; // edi@2  int v12; // ST18_4@2  int v13; // ST14_4@2  int v14; // eax@2  int v15; // eax@2  int v16; // ST18_4@2  int v17; // ST14_4@2  int v18; // ST10_4@2  int v19; // eax@2  int v20; // eax@2  int v21; // eax@2  int v22; // ST18_4@2  int v23; // eax@2  int v24; // eax@2  int v25; // ST18_4@2  int v26; // ST14_4@2  int v27; // ST10_4@2  int v28; // eax@2  int v29; // eax@2  int v30; // ST14_4@2  int v31; // ST10_4@2  int v32; // ST0C_4@2  int v33; // eax@2  int v34; // eax@2  int v35; // eax@2  int v36; // ST14_4@2  int v37; // eax@2  int v38; // eax@2  int v39; // eax@2  int v40; // ST18_4@2  int v41; // ST14_4@2  int v42; // ST10_4@2  int v43; // eax@2  int v44; // eax@2  int v45; // ST14_4@2  int v46; // ST10_4@2  int v47; // ST0C_4@2  int v48; // eax@2  int v49; // eax@2  int v50; // eax@2  int v51; // ST14_4@2  int v52; // eax@2  int v53; // eax@2  int v54; // ST14_4@2  int v55; // ST10_4@2  int v56; // ST0C_4@2  int v57; // eax@2  int v58; // eax@2  int v59; // ST10_4@2  int v60; // ST0C_4@2  int v61; // ST08_4@2  int v62; // eax@2  int v63; // eax@2  int v64; // eax@2  int v65; // ST10_4@2  int v66; // eax@2  int v67; // eax@2  int v68; // eax@2  int v69; // eax@2  char v70; // al@2  int v71; // edi@2  int v72; // ebx@2  int v73; // ST18_4@2  int v74; // ST14_4@2  int v75; // eax@2  int v76; // eax@2  // 初始化加密数据  *(_DWORD *)&SuccessfulData = EncryptData[0];  *((_DWORD *)&SuccessfulData + 1) = EncryptData[1];  *((_DWORD *)&SuccessfulData + 2) = EncryptData[2];  *(&SuccessfulData + 12) = LOBYTE(EncryptData[3]);  result = 0;  i = 0;  if ( strlen(UserName) != 0 )  {    do    {      ByteOfSerial1 = Serial[i];                // Serial[i]      ByteOfUsername = UserName[i];             // UserName[i]      ByteOfSerial = Serial[i];                 // Serial[i]      ByteOfUsername1 = UserName[i];            // UserName[i]      v12 = nor(ByteOfUsername, ByteOfSerial);      v13 = nor(ByteOfSerial, ByteOfSerial);      v14 = nor(ByteOfUsername1, ByteOfUsername1);      v15 = nor(v14, v13);      v16 = nor(v15, v12);                      // v10 = UserName[i] xor Serial[i]      v17 = nor(UserName[i], Serial[i]);      v18 = nor(ByteOfSerial, ByteOfSerial);      v19 = nor(ByteOfUsername1, ByteOfUsername1);      v20 = nor(v19, v18);      v21 = nor(v20, v17);                      // v15 = UserName[i] xor Serial[i]      v22 = nor(v21, v16);                      // v16 = not(UserName[i] xor Serial[i])      v23 = nor(ByteOfUsername1, ByteOfUsername1);      v24 = nor(v23, v22);                      // v18 = UserName[i] and v15      v25 = nor(Serial[i], v24);                // v19 = nor(Serial[i],UserName[i] and (UserName[i] xor Serial[i]))      v26 = nor(UserName[i], Serial[i]);      v27 = nor(ByteOfSerial, ByteOfSerial);      v28 = nor(ByteOfUsername1, ByteOfUsername1);      v29 = nor(v28, v27);      v30 = nor(v29, v26);                      // v24 = UserName[i] xor Serial[i]      v31 = nor(UserName[i], Serial[i]);      v32 = nor(ByteOfSerial, ByteOfSerial);      v33 = nor(ByteOfUsername1, ByteOfUsername1);      v34 = nor(v33, v32);      v35 = nor(v34, v31);                      // v29 = UserName[i] xor Serial[i]      v36 = nor(v35, v30);                      // v30 = not(UserName[i] xor Serial[i])      v37 = nor(ByteOfUsername1, ByteOfUsername1);      v38 = nor(v37, v36);                      // v32 = nor(not(UserName[i]),v30)      v39 = nor(Serial[i], v38);                // v33 = nor(Serial[i],UserName[i] and (UserName[i] xor Serial[i]))      v40 = nor(v39, v25);                      // v34 = not(v33)      v41 = nor(UserName[i], Serial[i]);      v42 = nor(ByteOfSerial, ByteOfSerial);      v43 = nor(ByteOfUsername1, ByteOfUsername1);      v44 = nor(v43, v42);      v45 = nor(v44, v41);                      // v39 = UserName[i] xor Serial[i]      v46 = nor(UserName[i], Serial[i]);      v47 = nor(ByteOfSerial, ByteOfSerial);      v48 = nor(ByteOfUsername1, ByteOfUsername1);      v49 = nor(v48, v47);      v50 = nor(v49, v46);                      // v44 = UserName[i] xor Serial[i]      v51 = nor(v50, v45);                      // v45 = not(UserName[i] xor Serial[i])      v52 = nor(ByteOfUsername1, ByteOfUsername1);      v53 = nor(v52, v51);                      // v47 = UserName[i] and (UserName[i] xor Serial[i])      v54 = nor(Serial[i], v53);                // v48 = nor(Serial[i],UserName[i] and (UserName[i] xor Serial[i]))      v55 = nor(UserName[i], Serial[i]);      v56 = nor(ByteOfSerial, ByteOfSerial);      v57 = nor(ByteOfUsername1, ByteOfUsername1);      v58 = nor(v57, v56);      v59 = nor(v58, v55);                      // v53 = UserName[i] xor Serial[i]      v60 = nor(UserName[i], Serial[i]);      v61 = nor(ByteOfSerial, ByteOfSerial);      v62 = nor(ByteOfUsername1, ByteOfUsername1);      v63 = nor(v62, v61);      v64 = nor(v63, v60);                      // v58 = UserName[i] xor Serial[i]      v65 = nor(v64, v59);                      // v59 = not(v58)      v66 = nor(ByteOfUsername1, ByteOfUsername1);      v67 = nor(v66, v65);                      // v61 = UserName[i] and (UserName[i] xor Serial[i])      v68 = nor(Serial[i], v67);                // v62 = nor(Serial[i],UserName[i] and (UserName[i] xor Serial[i]))      v69 = nor(v68, v54);                      // v63 = not(v62)      v70 = nor(v69, v40);                      // v64 = not(v63) -> v64 = v62      UserName[i] = v70;                        // UserName[i] = v62      v71 = v70;                                // v65 = v62      v72 = (unsigned __int8)*(&SuccessfulData + i);// v66 = SuccessfulData[i]      v73 = nor(v72, v70);      v74 = nor(v71, v71);      v75 = nor(v72, v72);      v76 = nor(v75, v74);                      // v70 = SuccessfulData[i] and v62      *(&SuccessfulData + i) = nor(v76, v73);   // SuccessfulData[i] = SuccessfulDara[i] xor v62      result = 0;      ++i;    }    while ( i < strlen(UserName) );  }  return result;}

总结一下算法:

设:用户名为UserName,注册码为Serial,提示信息为SuccessfulData,用户名和注册码的每个字节的关系为x。
则有:
x = nor(Serial[i],UserName[i] and (UserName[i] xor Serial[i]))
SuccessfulData[i] = SuccessfulData[i] xor x

0x2 注册机的编写
知道了算法,这样就可以写一个注册机了。
不过因为算法本身的原因,这个注册机编写起来还是有一定难度的。
因为不是所有的用户名所对应的注册码都能被输入进去,不过又因为算法的关系,导致了x并不是只有一个结果。

根据这个ReverseMe的成功图片,成功会输出”Hello world!”,正好是SuccessfulData的长度。
那么将字符串”Hello world!”和SuccessfulData逐位异或,得到新的SuccessfulData如下:

char SuccessfulData[] = {0x80,0x90,0x9A,0x8A,0x8A,0x92,0x80,0xCD,0xCE,0xC8,0x80,0xA0};

注册机的代码:

#include <stdio.h>#include <string.h>#include <stdlib.h>#define and &#define xor ^#define not ~int nor(int a,int b){    return not a and not b;}int main(void){    char SuccessfulData[] = {0x80,0x90,0x9A,0x8A,0x8A,0x92,0x80,0xCD,0xCE,0xC8,0x80,0xA0};    char UserName[512];    char Serial[13] = {0};    scanf("%s",UserName);    if(strlen(UserName) != 12)        return 0;    for(int i = 0;i < 12;i++)    {        if(!(UserName[i] >= 0x21 && UserName[i] <= 0x7F))            return 0;    }    for(int i = 0;i < 12;i++)    {ContinueWhile:        for(Serial[i] = 0x21;Serial[i] <= 0x7E;Serial[i]++) //scanf函数接受字符串输入时遇到空格截断.        {            if(nor(Serial[i],UserName[i] and (UserName[i] xor Serial[i])) == SuccessfulData[i])                goto Next;        }        /* 当前用户名没有对应的可显示的注册码,尝试更改用户名 */        if(UserName[i] == 0x7E)            UserName[i] -= (Serial[i] - 0x21);        else            UserName[i]++;        if(UserName[i] == 0x20) //空格截断            UserName[i]++;        goto ContinueWhile;Next:        _asm nop    }    printf("------------------------\nUserName:[%s]\n",UserName);    printf("Serial:[%s]\n",Serial);    system("pause");    return 0;}

运行结果如图所示:
这里写图片描述
ReverseMe的下载链接:http://pan.baidu.com/s/1gf5YC2B 密码:y093

全文完。

原创粉丝点击