[标题]逆向TEST指令
[作者]神杀中龙

已经有经验的知道TEST指令是对寄存器或者说对两个操作数进行逻辑与,然后影响标志寄存器。
最常见的指令是TEST EAX,EAX, 但是如何逆向出TEST指令呢? 对我们新手来说正向经验不足是个难度。

开始的时候 我也不知道从何下手得到这 TEST指令, 我就随便看Object Pascal官方中文版的书,
然后反出来看, 于是渐渐的TEST指令就出来了。 
而实际要逆出的指令是这一组, 不妨我们都逆逆看。

0040899C  |.  8B1D 4CD24000 mov     ebx, dword ptr [40D24C]          ;  NoPacker.0040D164
004089A2  |.  8B5B 18       mov     ebx, dword ptr [ebx+18]
004089A5  |.  85DB          test    ebx, ebx
004089A7  |.  74 12         je      short 004089BB
004089A9  |.  A1 4CD24000   mov     eax, dword ptr [40D24C]
004089AE  |.  E8 DDB9FFFF   call    <jmp.&kernel32.GetACP>           ; [GetACP
004089B3  |.  3BD8          cmp     ebx, eax
004089B5  |.  0F85 D2030000 jnz     00408D8D
004089BB  |>  BA 9CE94000   mov     edx, 0040E99C


一、 
(1)函数调用
00407CDC  |.  B8 FC7C4000   mov     eax, 00407CFC                    ;  ASCII "0 :"
00407CE1  |.  E8 8AFFFFFF   call    00407C70
00407CE6  |.  B8 087D4000   mov     eax, 00407D08                    ;  ASCII "Hello World"
00407CEB  |.  E8 90FFFFFF   call    00407C80

(2)另外一种形式的 如 MessageBox(0, PChar(varb), PChar(varb2), 0);
00407CBF  |.  6A 00         push    0
00407CC1  |.  8B45 F8       mov     eax, dword ptr [ebp-8]
00407CC4  |.  E8 77BCFFFF   call    00403940
00407CC9  |.  50            push    eax
00407CCA  |.  8B45 FC       mov     eax, dword ptr [ebp-4]
00407CCD  |.  E8 6EBCFFFF   call    00403940
00407CD2  |.  50            push    eax                          ; |Text
00407CD3  |.  6A 00         push    0                            ; |hOwner = NULL
00407CD5  |.  E8 8EC8FFFF   call    <jmp.&user32.MessageBoxA>    ; \MessageBoxA

(3)两个整型参数 func(90, 90);
00407D3A  |.  BA 5A000000   mov     edx, 5A
00407D3F  |.  B8 5A000000   mov     eax, 5A
00407D44  |.  E8 47FFFFFF   call    00407C90

(4)将两个值相加再转为string   tmpStr:=IntToStr(a + b);
00407CB8  |.  8D55 FC       lea     edx, dword ptr [ebp-4]
00407CBB  |.  8D041E        lea     eax, dword ptr [esi+ebx]
00407CBE  |.  E8 01D6FFFF   call    004052C4

(5)值相加tBuf := Integer('A'); 
       tmpStr:=IntToStr(a + b + tBuf);

00407CB9  |.  BB 41000000   mov     ebx, 41
00407CBE  |.  8D55 FC       lea     edx, dword ptr [ebp-4]
00407CC1  |.  8D0437        lea     eax, dword ptr [edi+esi]
00407CC4  |.  03C3          add     eax, ebx
00407CC6  |.  E8 F9D5FFFF   call    004052C4

(6)获取一个函数指针
00407CB9  |.  A1 A0974000   mov     eax, dword ptr [4097A0]
00407CBE  |.  A3 9C974000   mov     dword ptr [40979C], eax

(7)来回传递 F := Func(P);
       Func(P) := F;
00407CB9  |.  A1 A0974000   mov     eax, dword ptr [4097A0]
00407CBE  |.  A3 9C974000   mov     dword ptr [40979C], eax
00407CC3  |.  A1 9C974000   mov     eax, dword ptr [40979C]
00407CC8  |.  A3 A0974000   mov     dword ptr [4097A0], eax

(8) 相同的反汇编结果
 @F := P; 和 F := Func(P);
00407CB9  |.  A1 A0974000   mov     eax, dword ptr [4097A0]
00407CBE  |.  A3 9C974000   mov     dword ptr [40979C], eax

00407CCD  |.  A1 A0974000   mov     eax, dword ptr [4097A0]
00407CD2  |.  A3 9C974000   mov     dword ptr [40979C], eax

P := @F; 和  Func(P) := F;也是相同的

(9)调用  N := F(N);
00407CDC  |.  A1 A4974000   mov     eax, dword ptr [4097A4]
00407CE1  |.  FF15 9C974000 call    dword ptr [40979C]
00407CE7  |.  A3 A4974000   mov     dword ptr [4097A4], eax

(10) N := Func(P)(N);
00407CEC  |.  A1 A4974000   mov     eax, dword ptr [4097A4]
00407CF1  |.  FF15 A0974000 call    dword ptr [4097A0]
00407CF7  |.  A3 A4974000   mov     dword ptr [4097A4], eax

(11)
00407CB9  |.  BB 03000000   mov     ebx, 3    ; I := 3
00407CBE  |.  8D55 FC       lea     edx, dword ptr [ebp-4] ;得到tmpStr地址
00407CC1  |.  8D0437        lea     eax, dword ptr [edi+esi] ;edi+esi就是90+90
00407CC4  |.  03C3          add     eax, ebx  ; I + edi + esi值
00407CC6  |.  E8 F9D5FFFF   call    004052C4  ; 存储在tmpStr内

(12) 
00407CB9  |.  BB 03000000   mov     ebx, 3   ;I := 3
00407CBE  |.  43            inc     ebx      ;I := I+1;

(13)当不自动为变量初始化时Delphi会自己给变量初始化, 形态是这样的。
//一般不是自己手动的临时变量要么是作为输入参数要么是作为中转存储。
00407C9B  |.  894D F8       mov     dword ptr [ebp-8], ecx
00407C9E  |.  894D F4       mov     dword ptr [ebp-C], ecx
00407CA1  |.  8955 FC       mov     dword ptr [ebp-4], edx

(14)Delphi的临时栈空间是在每个函数的后面。
00407D1F      00                 db      00
00407D20   .  FFFFFFFF           dd      FFFFFFFF
00407D24   .  14000000           dd      00000014
00407D28   .  4D 69 63 72 6F 73 >ascii   "Microsoft vs Bor"
00407D38   .  6C 61 6E 64 00     ascii   "land",0
00407D3D      00                 db      00
00407D3E      00                 db      00
00407D3F      00                 db      00
如这段, 它在00407D1F之前存在一个函数将引用此段内某临时变量。

(15) 一个逻辑表达式 Done := (I>=1) and (I<100);
00407CB6  |.  83FB 01            cmp     ebx, 1   (1)
00407CB9  |.  7C 05              jl      short 00407CC0
00407CBB  |.  83FB 64            cmp     ebx, 64  (100)
00407CBE  |.  7C 04              jl      short 00407CC4
00407CC0  |>  33C0               xor     eax, eax              //清零
00407CC2  |.  EB 02              jmp     short 00407CC6
00407CC4  |>  B0 01              mov     al, 1                 //清1
00407CC6  |>  8BD8               mov     ebx, eax

(16) sqr求平方, sqrt开平方 
I:= Sqr(J);
00407CB3  |.  B9 64000000        mov     ecx, 64
00407CB8  |.  8BC1               mov     eax, ecx
00407CBA  |.  F7E8               imul    eax
00407CBC  |.  8BD8               mov     ebx, eax

(17)关键字 test终于被我再次搞出来了, 且是存在于循环之中。
00407CB3  |.  BB 64000000        mov     ebx, 64
00407CB8  |>  4B                 /dec     ebx
00407CB9  |.  85DB               |test    ebx, ebx    //这里的test ebx ebx正好合乎我的需求。
00407CBB  |.^ 7F FB              \jg      short 00407CB8

当然肯定还有各种test的情形, 不一定非要是循环。不过我们到是可以检测下各种
条件表达式下的循环。
[源]
I := 100;
       while I > 0 do
       begin
           I:= I-1;
       end;
关于条件表达式中进行总结。

根据上面的循环由于 I 和 0 作比较, 所以一般用test指令作为检测指令。然后根据
标志寄存器状态作跳转。我根据此推测又写了一个循环,同样还是让另外一个新变量J和
0作比较。
[反]
00407CB3  |.  BB 64000000        mov     ebx, 64    ;i = 100
00407CB8  |.  B8 04000000        mov     eax, 4     ;j = 4;
00407CBD  |>  48                 /dec     eax       ;循环四次,没次减1
00407CBE  |.  85C0               |test    eax, eax
00407CC0  |.^ 75 FB              \jnz     short 00407CBD

00407CC2  |.  85DB               test    ebx, ebx        
00407CC4  |.  7E 05              jle     short 00407CCB  ;(test后若ZF!=OF或ZF=1)转
                                                         ;否则循环100次,每次减1
00407CC6  |>  4B                 /dec     ebx
00407CC7  |.  85DB               |test    ebx, ebx
00407CC9  |.^ 7F FB              \jg      short 00407CC6
00407CCB  |> \8D55 FC            lea     edx, dword ptr [ebp-4]

这是根据循环,我目的是想产生类似这样的代码。
0040899C  |.  8B1D 4CD24000 mov     ebx, dword ptr [40D24C]          ;  NoPacker.0040D164
004089A2  |.  8B5B 18       mov     ebx, dword ptr [ebx+18]
004089A5  |.  85DB          test    ebx, ebx
004089A7  |.  74 12         je      short 004089BB
004089A9  |.  A1 4CD24000   mov     eax, dword ptr [40D24C]
004089AE  |.  E8 DDB9FFFF   call    <jmp.&kernel32.GetACP>           ; [GetACP
004089B3  |.  3BD8          cmp     ebx, eax
004089B5  |.  0F85 D2030000 jnz     00408D8D
004089BB  |>  BA 9CE94000   mov     edx, 0040E99C

现在知道了如何产生  test指令, 而cmp指令则很容易产生,一般 逻辑或条件表达式
内又大于0的数就会使用cmp作减法, 然后比较, test 和 cmp都是 将两个操作数进行
逻辑和算术运算,但是不修改寄存器值。 test负责逻辑判断影响标志寄存器。
cmp进行算术寄存器,也影响标志寄存器。

现在再次推理, 如果被比较的操作数是小于0, 或者 是个存储器操作数,或是个函数,
或是其他形式 该如何产生test指令呢?  如果 被比较的数是1或 -1会不会也产生
test指令呢。

下面这段是前面两个小循环
I := 100;
       J := 4;
       while j <> 0 do
       begin;
          J := J - 1;
       end;

       while I > 0 do
       begin
           I:= I-1;
       end;

//先面开始测试是否 跟 1 -1等有关, 也可产生test指令。
第一次试验失败,看来跟1比较是不行的
00407CB3  |.  B8 64000000        mov     eax, 64
00407CB8  |>  48                 /dec     eax
00407CB9  |.  83F8 01            |cmp     eax, 1
00407CBC  |.^ 75 FA              \jnz     short 00407CB8
[源]
J := 100;
       
       while j <> 1 do
       begin;
          J := J - 1;
       end;
还有-1
同样也失败
00407CB3  |.  B8 64000000        mov     eax, 64
00407CB8  |>  48                 /dec     eax
00407CB9  |.  83F8 FF            |cmp     eax, -1
00407CBC  |.^ 75 FA              \jnz     short 00407CB8
[源]
J := 100;
       
       while j <> 1 do
       begin;
          J := J - 1;
       end;
看来都不行, 不过现在知道和0比较可以了就可以了。那么再次推测,和常量0
或者 存储器0, 或者其他形式的, 如返回值0比较是否也要产生test指令呢?

先不管循环的形式, 如果将条件转移为if then else ...类的条件内和0比较是否也可以呢
00407CB3  |.  B8 64000000        mov     eax, 64
00407CB8  |.  85C0               test    eax, eax
00407CBA  |.  7E 13              jle     short 00407CCF
00407CBC  |.  6A 00              push    0                               ; /Style = MB_OK|MB_APPLMODAL
00407CBE  |.  68 3C7D4000        push    00407D3C                        ; |Title = "Hello World"
00407CC3  |.  68 3C7D4000        push    00407D3C                        ; |Text = "Hello World"
00407CC8  |.  6A 00              push    0                               ; |hOwner = NULL
00407CCA  |.  E8 99C8FFFF        call    <jmp.&user32.MessageBoxA>       ; \MessageBoxA
00407CCF  |>  8D55 FC            lea     edx, dword ptr [ebp-4]
看来是可以的, 
[源]
J := 100;
       
       if J > 0 then
       begin
           MessageBox(0, 'Hello World', 'Hello World', 0);
       end;
小结: test是两两个寄存器作与运算,然后影响标志寄存器,然后后面的转移指令根据标志寄存器
来转。
我稍微改变了个符号后
00407CB3  |.  B8 64000000        mov     eax, 64
00407CB8  |.  85C0               test    eax, eax
00407CBA  |.  7D 13              jge     short 00407CCF
00407CBC  |.  6A 00              push    0                               ; /Style = MB_OK|MB_APPLMODAL
00407CBE  |.  68 3C7D4000        push    00407D3C                        ; |Title = "Hello World"
00407CC3  |.  68 3C7D4000        push    00407D3C                        ; |Text = "Hello World"
00407CC8  |.  6A 00              push    0                               ; |hOwner = NULL
00407CCA  |.  E8 99C8FFFF        call    <jmp.&user32.MessageBoxA>       ; \MessageBoxA
[源]
J := 100;
       
       if J < 0 then
       begin
           MessageBox(0, 'Hello World', 'Hello World', 0);
       end;
则jle 被改为了jge, jge要求 SF=OF, 由于test指令对eax进行了与运算,将全部标志寄存器清零了。
所以SF=OF,所以要转。好下面完成后面的测试, 当被比较的不是0, 是常量,是变量,是函数时
会产生什么代码。
(1).1常量
[源]
const ZeroN = 0;
...
J := 100;
       
       if J < ZeroN then
       begin
           MessageBox(0, 'Hello World', 'Hello World', 0);
       end;
[反]
00407CB3  |.  B8 64000000        mov     eax, 64
00407CB8  |.  85C0               test    eax, eax
00407CBA  |.  7D 13              jge     short 00407CCF
00407CBC  |.  6A 00              push    0                               ; /Style = MB_OK|MB_APPLMODAL
00407CBE  |.  68 3C7D4000        push    00407D3C                        ; |Title = "Hello World"
00407CC3  |.  68 3C7D4000        push    00407D3C                        ; |Text = "Hello World"
00407CC8  |.  6A 00              push    0                               ; |hOwner = NULL
00407CCA  |.  E8 99C8FFFF        call    <jmp.&user32.MessageBoxA>       ; \MessageBoxA
00407CCF  |>  8D55 FC            lea     edx, dword ptr [ebp-4]
看来常量是可以的。那么存储器的呢。
存储器的就不行了
00407CB3  |.  B8 64000000        mov     eax, 64      ;J := 100;
00407CB8  |.  33DB               xor     ebx, ebx     ;I := 0;
00407CBA  |.  3BD8               cmp     ebx, eax
00407CBC  |.  7E 13              jle     short 00407CD1
00407CBE  |.  6A 00              push    0                               ; /Style = MB_OK|MB_APPLMODAL
00407CC0  |.  68 407D4000        push    00407D40                        ; |Title = "Hello World"
00407CC5  |.  68 407D4000        push    00407D40                        ; |Text = "Hello World"
00407CCA  |.  6A 00              push    0                               ; |hOwner = NULL
00407CCC  |.  E8 97C8FFFF        call    <jmp.&user32.MessageBoxA>       ; \MessageBoxA
00407CD1  |>  8D55 FC            lea     edx, dword ptr [ebp-4]

存储通过xor 进行了清零,然后用的cmp 做减法 然后判断跳。
还有一种就是函数的返回值了。当函数返回Boolean值或 指针 或 0 值时。
函数的返回值得也是不行的
00407CE0  |.  BB 64000000        mov     ebx, 64
00407CE5  |.  B8 5A000000        mov     eax, 5A
00407CEA  |.  E8 A1FFFFFF        call    00407C90
00407CEF  |.  3BD8               cmp     ebx, eax
00407CF1  |.  7D 13              jge     short 00407D06
00407CF3  |.  6A 00              push    0                               ; /Style = MB_OK|MB_APPLMODAL
00407CF5  |.  68 747D4000        push    00407D74                        ; |Title = "Hello World"
00407CFA  |.  68 747D4000        push    00407D74                        ; |Text = "Hello World"
00407CFF  |.  6A 00              push    0                               ; |hOwner = NULL
00407D01  |.  E8 62C8FFFF        call    <jmp.&user32.MessageBoxA>       ; \MessageBoxA
00407D06  |>  8D55 FC            lea     edx, dword ptr [ebp-4]

但是我们随便Google一下test指令, 找到的破解的test的形式有如下几种等:
1)TEST EAX,EAX
2)TEST EBX,EBX
3)TEST ESI,ESI
4)TEST EDI,EDI  这种直接逻辑与寄存器的比较多。 含存储器的呢?
5)test al,1     含一个立即数
6)test ebp,esi   总之还有其他形式的test , 但是以test eax,eax最多, 因为Windows API多对eax做了优化, 当使用 if 检测是否返回0
时, 就要使用test指令, 更多有待总结。