zbnysjwsnd8 发表于 2017-9-28 19:27

一个ReverseMe的算法分析

前几天写到博客的 然后昨天写了一个和这个很相似的KeygenMe.
ReverseMe:https://www.52pojie.cn/thread-647291-1-1.html
KeygenMe:https://www.52pojie.cn/thread-647830-1-1.html
这个ReverseMe是我一年前写的,不过现在源代码丢了而且怎么写的也忘了。正好昨天逛一个论坛的时候看到了这个ReverseMe,就顺便下载玩了玩(也算是重温了一下),于是就有了这篇文章。
因为篇幅有限 我就写写关键的地方。
**0x0 寻找算法地址**
直接来到main函数(0x004019B2)处。
程序首先获取ntdll!ZwContinue函数的地址,然后保存到0x00417F7C处。

```
004019BE    68 DC504100   push    004150DC                         ; NtContinue
004019C3    68 E8504100   push    004150E8                         ; n
004019C8    33F6            xor   esi, esi
004019CA    33DB            xor   ebx, ebx
004019CC    C705 787F4100 0>mov   dword ptr , 00412000
004019D6    FF15 04304100   call    dword ptr [<&KERNEL32.GetModuleH>; kernel32.GetModuleHandleW
004019DC    50            push    eax
004019DD    FF15 0C304100   call    dword ptr [<&KERNEL32.GetProcAdd>; kernel32.GetProcAddress
004019E3    68 FC504100   push    004150FC                         ; E
004019E8    A3 7C7F4100   mov   dword ptr , eax
```
继续往下走,发现程序设置了CONTEXT结构体然后调用ZwContinue函数。

```
00401ABB    8D05 3D1B4000   lea   eax, dword ptr       ; 返回地址
00401AC1    50            push    eax
00401AC2    9C            pushfd
00401AC3    8F45 FC         pop   dword ptr
00401AC6    8B45 FC         mov   eax, dword ptr
00401AC9    8945 F0         mov   dword ptr , eax
00401ACC    8965 FC         mov   dword ptr , esp
00401ACF    8B45 FC         mov   eax, dword ptr
00401AD2    8945 F4         mov   dword ptr , eax
00401AD5    896D FC         mov   dword ptr , ebp
00401AD8    8B45 FC         mov   eax, dword ptr
00401ADB    C785 30FFFFFF 0>mov   dword ptr , 0x10007    ; CONTEXT_FULL
00401AE5    8945 E4         mov   dword ptr , eax
00401AE8    A1 547F4100   mov   eax, dword ptr
00401AED    8945 E8         mov   dword ptr , eax      ; Eip
00401AF0    16            push    ss
00401AF1    8F45 FC         pop   dword ptr
00401AF4    8B45 FC         mov   eax, dword ptr
00401AF7    8945 F8         mov   dword ptr , eax
00401AFA    0E            push    cs
00401AFB    8F45 FC         pop   dword ptr
00401AFE    8B45 FC         mov   eax, dword ptr
00401B01    8945 EC         mov   dword ptr , eax
00401B04    1E            push    ds
00401B05    8F45 FC         pop   dword ptr
00401B08    8B45 FC         mov   eax, dword ptr
00401B0B    8945 C8         mov   dword ptr , eax
00401B0E    06            push    es
00401B0F    8F45 FC         pop   dword ptr
00401B12    8B45 FC         mov   eax, dword ptr
00401B15    8945 C4         mov   dword ptr , eax
00401B18    0FA0            push    fs
00401B1A    8F45 FC         pop   dword ptr
00401B1D    8B45 FC         mov   eax, dword ptr
00401B20    8945 C0         mov   dword ptr , eax
00401B23    0FA8            push    gs
00401B25    8F45 FC         pop   dword ptr
00401B28    8B45 FC         mov   eax, dword ptr
00401B2B    6A 00         push    0x0
00401B2D    8945 BC         mov   dword ptr , eax
00401B30    8D85 30FFFFFF   lea   eax, dword ptr
00401B36    50            push    eax
00401B37    FF15 7C7F4100   call    dword ptr              ; ntdll.ZwContinue
00401B3D    68 3C514100   push    0041513C                         ; pause
00401B42    E8 A9000000   call    00401BF0
```

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

```
778165E3    A1 547F4100   mov   eax, dword ptr
778165E8    0105 387B4100   add   dword ptr , eax
778165EE    16            push    ss
778165EF    8F05 4C7B4100   pop   dword ptr
778165F5    A1 4C7B4100   mov   eax, dword ptr
778165FA    A3 487B4100   mov   dword ptr , eax
778165FF    0E            push    cs
77816600    8F05 4C7B4100   pop   dword ptr
77816606    A1 4C7B4100   mov   eax, dword ptr
7781660B    A3 3C7B4100   mov   dword ptr , eax
77816610    1E            push    ds
77816611    8F05 4C7B4100   pop   dword ptr
77816617    A1 4C7B4100   mov   eax, dword ptr
7781661C    A3 187B4100   mov   dword ptr , eax
77816621    06            push    es
77816622    8F05 4C7B4100   pop   dword ptr
77816628    A1 4C7B4100   mov   eax, dword ptr
7781662D    A3 147B4100   mov   dword ptr , eax
77816632    0FA0            push    fs
77816634    8F05 4C7B4100   pop   dword ptr
7781663A    A1 4C7B4100   mov   eax, dword ptr
7781663F    A3 107B4100   mov   dword ptr , eax
77816644    0FA8            push    gs
77816646    8F05 4C7B4100   pop   dword ptr
7781664C    A1 4C7B4100   mov   eax, dword ptr
77816651    6A 00         push    0x0
77816653    68 807A4100   push    0x417A80
77816658    A3 0C7B4100   mov   dword ptr , eax
7781665D    FF15 7C7F4100   call    dword ptr              ; ntdll.ZwContinue
77816663    90            nop
77816664    FF05 507F4100   inc   dword ptr
```
在0x7781665D处下断,拦截每次的Eip值。
拦截以后发现几个有用的函数:

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

**0x1 分析算法**
来到0x00412057处,简单看看代码,发现一堆push call pop之类的指令,这里我使用IDA的F5插件来分析。
![这里写图片描述](http://img.blog.csdn.net/20170926221714323?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMTk0NTk4NTM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
其中的EncryptData:
![这里写图片描述](http://img.blog.csdn.net/20170926221656708?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMTk0NTk4NTM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

这里仅仅调用了两个函数:strlen和sub_401000。
我们目前需要做的就是分析出函数sub_401000是干什么的。
IDA进入401000处,发现代码很简短。
![这里写图片描述](http://img.blog.csdn.net/20170926221953251?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMTk0NTk4NTM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
这段代码很简短,就是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;
*((_DWORD *)&SuccessfulData + 1) = EncryptData;
*((_DWORD *)&SuccessfulData + 2) = EncryptData;
*(&SuccessfulData + 12) = LOBYTE(EncryptData);
result = 0;
i = 0;
if ( strlen(UserName) != 0 )
{
    do
    {
      ByteOfSerial1 = Serial;                // Serial
      ByteOfUsername = UserName;             // UserName
      ByteOfSerial = Serial;               // Serial
      ByteOfUsername1 = UserName;            // UserName
      v12 = nor(ByteOfUsername, ByteOfSerial);
      v13 = nor(ByteOfSerial, ByteOfSerial);
      v14 = nor(ByteOfUsername1, ByteOfUsername1);
      v15 = nor(v14, v13);
      v16 = nor(v15, v12);                      // v10 = UserName xor Serial
      v17 = nor(UserName, Serial);
      v18 = nor(ByteOfSerial, ByteOfSerial);
      v19 = nor(ByteOfUsername1, ByteOfUsername1);
      v20 = nor(v19, v18);
      v21 = nor(v20, v17);                      // v15 = UserName xor Serial
      v22 = nor(v21, v16);                      // v16 = not(UserName xor Serial)
      v23 = nor(ByteOfUsername1, ByteOfUsername1);
      v24 = nor(v23, v22);                      // v18 = UserName and v15
      v25 = nor(Serial, v24);                // v19 = nor(Serial,UserName and (UserName xor Serial))
      v26 = nor(UserName, Serial);
      v27 = nor(ByteOfSerial, ByteOfSerial);
      v28 = nor(ByteOfUsername1, ByteOfUsername1);
      v29 = nor(v28, v27);
      v30 = nor(v29, v26);                      // v24 = UserName xor Serial
      v31 = nor(UserName, Serial);
      v32 = nor(ByteOfSerial, ByteOfSerial);
      v33 = nor(ByteOfUsername1, ByteOfUsername1);
      v34 = nor(v33, v32);
      v35 = nor(v34, v31);                      // v29 = UserName xor Serial
      v36 = nor(v35, v30);                      // v30 = not(UserName xor Serial)
      v37 = nor(ByteOfUsername1, ByteOfUsername1);
      v38 = nor(v37, v36);                      // v32 = nor(not(UserName),v30)
      v39 = nor(Serial, v38);                // v33 = nor(Serial,UserName and (UserName xor Serial))
      v40 = nor(v39, v25);                      // v34 = not(v33)
      v41 = nor(UserName, Serial);
      v42 = nor(ByteOfSerial, ByteOfSerial);
      v43 = nor(ByteOfUsername1, ByteOfUsername1);
      v44 = nor(v43, v42);
      v45 = nor(v44, v41);                      // v39 = UserName xor Serial
      v46 = nor(UserName, Serial);
      v47 = nor(ByteOfSerial, ByteOfSerial);
      v48 = nor(ByteOfUsername1, ByteOfUsername1);
      v49 = nor(v48, v47);
      v50 = nor(v49, v46);                      // v44 = UserName xor Serial
      v51 = nor(v50, v45);                      // v45 = not(UserName xor Serial)
      v52 = nor(ByteOfUsername1, ByteOfUsername1);
      v53 = nor(v52, v51);                      // v47 = UserName and (UserName xor Serial)
      v54 = nor(Serial, v53);                // v48 = nor(Serial,UserName and (UserName xor Serial))
      v55 = nor(UserName, Serial);
      v56 = nor(ByteOfSerial, ByteOfSerial);
      v57 = nor(ByteOfUsername1, ByteOfUsername1);
      v58 = nor(v57, v56);
      v59 = nor(v58, v55);                      // v53 = UserName xor Serial
      v60 = nor(UserName, Serial);
      v61 = nor(ByteOfSerial, ByteOfSerial);
      v62 = nor(ByteOfUsername1, ByteOfUsername1);
      v63 = nor(v62, v61);
      v64 = nor(v63, v60);                      // v58 = UserName xor Serial
      v65 = nor(v64, v59);                      // v59 = not(v58)
      v66 = nor(ByteOfUsername1, ByteOfUsername1);
      v67 = nor(v66, v65);                      // v61 = UserName and (UserName xor Serial)
      v68 = nor(Serial, v67);                // v62 = nor(Serial,UserName and (UserName xor Serial))
      v69 = nor(v68, v54);                      // v63 = not(v62)
      v70 = nor(v69, v40);                      // v64 = not(v63) -> v64 = v62
      UserName = v70;                        // UserName = v62
      v71 = v70;                              // v65 = v62
      v72 = (unsigned __int8)*(&SuccessfulData + i);// v66 = SuccessfulData
      v73 = nor(v72, v70);
      v74 = nor(v71, v71);
      v75 = nor(v72, v72);
      v76 = nor(v75, v74);                      // v70 = SuccessfulData and v62
      *(&SuccessfulData + i) = nor(v76, v73);   // SuccessfulData = SuccessfulDara xor v62
      result = 0;
      ++i;
    }
    while ( i < strlen(UserName) );
}
return result;
}
```
总结一下算法:

> 设:用户名为UserName,注册码为Serial,提示信息为SuccessfulData,用户名和注册码的每个字节的关系为x。
> 则有:
> x = nor(Serial,UserName and (UserName xor Serial))
> SuccessfulData = SuccessfulData 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;
        char Serial = {0};
        scanf("%s",UserName);
        if(strlen(UserName) != 12)
                return 0;
        for(int i = 0;i < 12;i++)
        {
                if(!(UserName >= 0x21 && UserName <= 0x7F))
                        return 0;
        }
        for(int i = 0;i < 12;i++)
        {
ContinueWhile:
                for(Serial = 0x21;Serial <= 0x7E;Serial++) //scanf函数接受字符串输入时遇到空格截断.
                {
                        if(nor(Serial,UserName and (UserName xor Serial)) == SuccessfulData)
                                goto Next;
                }
                /* 当前用户名没有对应的可显示的注册码,尝试更改用户名 */
                if(UserName == 0x7E)
                        UserName -= (Serial - 0x21);
                else
                        UserName++;
                if(UserName == 0x20) //空格截断
                        UserName++;
                goto ContinueWhile;
Next:
                _asm nop
        }
        printf("------------------------\nUserName:[%s]\n",UserName);
        printf("Serial:[%s]\n",Serial);
        system("pause");
        return 0;
}
```
运行结果如图所示:
![这里写图片描述](http://img.blog.csdn.net/20170926223456278?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMTk0NTk4NTM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
ReverseMe的下载链接:http://pan.baidu.com/s/1gf5YC2B 密码:y093

全文完。

仙梦 发表于 2017-9-28 21:07

帮忙顶一下666

keyu 发表于 2017-9-28 22:49

帮忙顶一下

2864095098 发表于 2017-9-28 23:46

感觉算法好难分析
页: [1]
查看完整版本: 一个ReverseMe的算法分析