[标题]逆向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指令, 更多有待总结。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课