界面很簡單,輸入name和serial的輸入框和一個結果提示的標簽。錯誤提示如下:
沒有按鈕觸發檢驗過程,也沒有對話框彈出。所以不能用這些相關函數下斷點了。好在搜索文本的話,可以看到如以上二圖Status那一欄的提示語:“Your serial is not valid.”或“YES! You found your serial!!!”。好了,雙擊Your serial is not valid來到函數調用的地方。
[Asm] 純文本查看 復制代碼
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
004012D7 . /EB 04 jmp short 160-24-C.004012DD
004012D9 |54 push esp
004012DA |45 inc ebp
004012DB |58 pop eax ; kernel32.766DEF8C
004012DC |00AD 33D84975 add byte ptr ss:[ebp+0x7549D833],ch
004012E2 ? FA cli
004012E3 . 81FB FBCFFCAF cmp ebx,0xAFFCCFFB
004012E9 ^ 74 EE je short 160-24-C.004012D9 ; je short 004012D9->JNE
004012EB 68 59304000 push 160-24-C.00403059 ; /Your serial is not valid.
004012F0 . FF35 5C314000 push dword ptr ds:[0x40315C] ; |hWnd = NULL
004012F6 . E8 7D010000 call <jmp.&USER32.SetWindowTextA> ; \SetWindowTextA
004012FB . 33C0 xor eax,eax ; kernel32.BaseThreadInitThunk
004012FD . C9 leave
004012FE . C2 1000 retn 0x10
00401301 . 68 73 30 40 0>ascii "hs0@",0 ; YES! You found your serial!!
00401306 . FF35 5C314000 push dword ptr ds:[0x40315C] ; |hWnd = NULL
0040130C . E8 67010000 call <jmp.&USER32.SetWindowTextA> ; \SetWindowTextA
00401311 . 33C0 xor eax,eax ; kernel32.BaseThreadInitThunk
00401313 . C9 leave
00401314 . C2 1000 retn 0x10
|
看這段代碼的話,錯誤提示排在正確提示前面,那么推測他們前面必然有一個關鍵跳,serial正確的話會跳到00401301;錯的話會走向004012EB。因此我們開始往上翻。不對啊,翻到段頭也沒有看到一個滿足這樣的跳轉邏輯的關鍵跳。怎么回事?
沒辦法,只好翻到段首下斷點。
[Asm] 純文本查看 復制代碼
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
00401255 > \3B05 58314000 cmp eax,dword ptr ds:[0x403158]
0040125B . 74 0C je short 160-24-C.00401269
0040125D . 3B05 54314000 cmp eax,dword ptr ds:[0x403154]
00401263 . 0F85 AE000000 jnz 160-24-C.00401317
00401269 C705 D9124000>mov dword ptr ds:[0x4012D9],0x584554 ; vengers
00401273 . 6A 00 push 0x0 ; /IsSigned = FALSE
00401275 . 8D45 FC lea eax,dword ptr ss:[ebp-0x4] ; |0
00401278 . 50 push eax ; |pSuccess = kernel32.BaseThreadInitThunk
00401279 . 6A 64 push 0x64 ; |ControlID = 64 (100.)
0040127B . FF35 50314000 push dword ptr ds:[0x403150] ; |hWnd = NULL
00401281 . E8 BC010000 call <jmp.&USER32.GetDlgItemInt> ; \GetDlgItemInt
00401286 . 837D FC 00 cmp dword ptr ss:[ebp-0x4],0x0
0040128A . 74 5F je short 160-24-C.004012EB
0040128C . 50 push eax ; kernel32.BaseThreadInitThunk
0040128D . 6A 14 push 0x14 ; /用戶名最長不得超過(20.)
0040128F . 68 6C314000 push 160-24-C.0040316C ; |用戶名
00401294 . FF35 54314000 push dword ptr ds:[0x403154] ; |hWnd = NULL
0040129A . E8 AF010000 call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA
0040129F . 85C0 test eax,eax ; 判斷name不為空
004012A1 . 74 48 je short 160-24-C.004012EB
004012A3 . A1 0B304000 mov eax,dword ptr ds:[0x40300B] ; CTEX
004012A8 . BB 6C314000 mov ebx,160-24-C.0040316C ; 輸入的name
004012AD > 0303 add eax,dword ptr ds:[ebx]
004012AF . 43 inc ebx
004012B0 . 81FB 7C314000 cmp ebx,160-24-C.0040317C
004012B6 .^ 75 F5 jnz short 160-24-C.004012AD ; 這是一輪累加,CTEX開始,依次取用戶名四位加和,會溢出。eax中為后八位。
|
運行程序,輸入name:abcdefgh,serial打算用123456。剛輸入一個1,運行就被中斷到段首。需要注意的是,程序的窗口上,數字1還沒有顯示出來呢。可見程序的處理流程是接收到用戶輸入的每一個serial的字符都要先判斷一次,確定正確與否之后才能顯示在serial的文本框里。Name就不存在這種情況。簡單的說序列號是隨輸隨檢的。
好了,我們用F8步進,并同時觀察數據窗。剛開始就是GetDlgItemInt函數,這是在讀取用戶輸入,還要求是整數形式。好的,我們輸入的就是數字。
繼續,在0040128F出現了name 的GetWindowTextA函數,這時我們發現輸入的name,最長字符長度等參數的入棧過程。這里0x14就是最長長度,20個字符。
在判斷name不為空之后,程序對name進行了一種運算。它從字符串CTEX出發,將name截取前四個字符,與CTEX相加。然后截去name的首位,再截取前四個字符,繼續與之前的累加和相加。一直進行,截到后面不足4位的就用0補齊。這樣就得到一個累加和。
這里有一點需要留意的是,這個累加和會溢出,而eax只能保存后八位。
繼續往下走。
[Asm] 純文本查看 復制代碼
|
1
2
3
4
5
|
004012B8 . 5B pop ebx ; 取序列號來檢測,隨輸隨檢,此時界面都還沒顯示;EBX=1
004012B9 . 03C3 add eax,ebx ; 160-24-C.0040317C
004012BB 3105 D9124000 xor dword ptr ds:[0x4012D9],eax ;
004012C1 . C1E8 10 shr eax,0x10
004012C4 66:2905 D9124>sub word ptr ds:[0x4012D9],ax ;
|
以上幾句看起來是很簡單的運算,之前的累加和與序列號接收到的數字(此處為1)相加,其和與004012D9處的dword字符串做異或運算,結果保存在004012D9;其和取后四位,再與004012D9處的word做減法,結果保存在004012D9。毫不起眼的幾句指令啊。
樓主隨即用perl復現了以上流程。
[Perl] 純文本查看 復制代碼
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
#!/usr/bin/perl
$name="abcdefgh";
#程序對serial隨輸隨檢,這里只用serial的第一位;
$serial="1";
$len=length($name);
@rev =$name=~/\w{1}/g;
$rev= join "",reverse(@rev);
$sum=hex(58455443);#"CTEX";
for($i=1;$i<5;$i++){
$out=substr($rev,0,$i);
$out=hex(str2hex($out));
$sum += $out;
}
for($i=1;$i<$len-3;$i++){
$out=substr($rev,$i,4);
$out=hex(str2hex($out));
$sum += $out;
}
print hex($sum),"\n";#報錯,這個數會溢出,因此只取后八位。
#取后八位;
$mod=&de_overflow_dec($sum);
$mod += hex($serial);
sub de_overflow_dec(){
$dec = shift;
# print "$dec will be de_overflowed...\n";
$dec %= 4294967296;#0x100000000;
return $dec;
}
sub str2hex{
my $s = shift;
my $str;
for (0..length($s)-1) {
$str .= sprintf "%0x", ord substr($s, $_, 1);
}
return $str;
}
sub dec2hex(){
$dec=shift;
$hex=sprintf "%0x",$dec;
return $hex;
}
|
一算,完全沒問題,跟程序算出來的一模一樣。感覺離結果更近了有沒有?
繼續。

[Asm] 純文本查看 復制代碼
|
01
02
03
04
05
06
07
08
09
10
11
12
|
004012CB . BE EC114000 mov esi,160-24-C.004011EC ; 字串的位置,共62個dword
004012D0 . B9 3E000000 mov ecx,0x3E
004012D5 . 33DB xor ebx,ebx
004012D7 . EB 04 jmp short 160-24-C.004012DD
004012D9 14 07 adc al,0x7
004012DB 18F0 sbb al,dh
004012DD AD lods dword ptr ds:[esi]
004012DE 33D8 xor ebx,eax
004012E0 49 dec ecx
004012E1 ^ 75 FA jnz short 160-24-C.004012DD
004012E3 . 81FB FBCFFCAF cmp ebx,0xAFFCCFFB
004012E9 ^ 74 EE je short 160-24-C.004012D9 ; 疑似的關鍵跳轉。
|
繼續F8。我們發現程序指定了一個內存位置004011EC做起始,和一個計數器3E(即62),然后跳向004012DD循環讀取Dword。以EBX=0起始,每讀取依次均與EBX異或,直至循環完成。簡單的說,就是0與62個Dword依次異或。最后判斷這個結果是否等于0xAFFCCFFB.如果不等的話回到004012D9。這次對al操作先加7再減D,隨后又進入上面那樣的異或的循環。
你是不是隱約感到抓住關鍵判斷和關鍵跳轉了?Too young too simple, sometimes naïve。 我們不能讓004012E9跳轉,因為跳轉了就會進入一個無窮無盡的循環;可是不跳轉馬上就報錯了,怎么辦啊?
于是想到那我修改004012E3處的校驗碼0xAFFCCFFB不就可以了?嘿嘿,難不到我啦!
樓主還是用perl算啦。先從004011EC到004012E3復制出來一點HEX,然后用它們做異或,只要我得到這個結果,替換原先的校驗碼0xAFFCCFFB一定可以的,哈哈。
說干就干,還是perl;
[Perl] 純文本查看 復制代碼
|
01
02
03
04
05
06
07
08
09
10
11
12
|
#!/usr/bin/perl
#下面這個字符串就是用來校驗的;
$cons="558BEC83C4FC8B450C83F810750D6A00E86B02000033C0C9C2100083F80F750E8B
4508E81801000033C0C9C2100083F801750633C0C9C210003D110100000F85E70000008B4
5143B0560314000751A6A00689630400068A7304000FF7508E81702000033C0C9C210003B0
558314000740C3B05543140000F85AE000000C705D9124000544558006A008D45FC506A64F
F3550314000E8BC010000837DFC00745F506A14686C314000FF3554314000E8AF01000085C
07448A10B304000BB6C31400003034381FB7C31400075F55B03C33105D9124000C1E81066
2905D9124000BEEC114000B93E00000033DBEB049306F100AD33D84975FA81";
$seed="0x00000000";
for($i=1;$i<63;$i++){
$curstr=substr($cons,($i-1)*8,8);
@splitstr=split(//,$curstr);
$curstr=join('',$splitstr[6],$splitstr[7],$splitstr[4],$splitstr[5],$splitstr[2],$splitstr[3],$splitstr[0],$splitstr[1]);
$seed=hex($seed) ^ hex($curstr);
$seed=sprintf "%0x",$seed;
}
print $seed,"\n";
|
好了,根據以上腳本的計算結果,樓主興沖沖的把得到的結果0adcb7fb替換了0xAFFCCFFB。保存,重新運行,我靠了,居然還是“Your serial is not valid.”
樓主只好重新一步步對比異或循環的結果,一個數值一個數值的對。一直到快接近字串末尾的049306F1運算時,居然跟eax的值不相同了。下一個數值00AD33D8出現了同樣的情況。這樣異或運算到最后的時候,已經不是0adcb7fb了。
真TM的見鬼了,一個字符串還能不相同,我明明是拷貝過去的啊!
樓主在此盤旋了好久好久,就是想不明白。于是把004011EC到004012E3內存位置下了一個寫入斷點,看看到底發生了什么詭異事件。
結果斷在了這里:
[Asm] 純文本查看 復制代碼
|
1
|
00401269 C705 D9124000 5>mov dword ptr ds:[0x4012D9],0x584554 ; vengers
|
什么,那個校驗用的字符串被修改了,校驗值還能對嗎?可是這程序把自己修改了,TM的居然還能正常運行。
繼續運行,又斷在了以下兩處:
[Asm] 純文本查看 復制代碼
|
1
2
|
004012BB 3105 D9124000 xor dword ptr ds:[0x4012D9],eax ;
004012C4 66:2905 D912400>sub word ptr ds:[0x4012D9],ax ;
|
我一下恍然大悟了,這不是之前覺得毫不起眼的那幾句指令啊!程序把自己修改了,而且修改的一定是關鍵的跳轉,否則我們無法解釋找不到合理的關鍵跳的理由。那怎么找回原來的代碼呢?程序之前給出的校驗碼就能起到這個作用啊。利用校驗碼倒推!
樓主看了下,這幾處修改涉及了第60和61個字符串,根據00401269、004012BB和004012C4的運算可以推出正確的代碼應該是04XXXXXXXX和XXAD33D8的樣式。我們現在把這些X算出來。
因為其他60個字串時候不變的,可以算出他們的累計的異或值,再與校驗碼0xAFFCCFFB異或,即為04XXXXXXXX和XXAD33D8異或的值。樓主這里就不貼perl腳本了。這樣就算出他們分別是:04EB2654和58AD33D8。
驗證一下。
好了,樓主又興沖沖的在反匯編窗口把那些被修改的指令修改乘了推算的指令,奇跡出現了:
[Asm] 純文本查看 復制代碼
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
004012D7 . /EB 04 jmp short 160-24-C.004012DD
004012D9 |EB 26 jmp short 160-24-C.00401301 ;被復原的指令
004012DB |54 push esp
004012DC |58 pop eax
004012DD \AD lods dword ptr ds:[esi]
004012DE 33D8 xor ebx,eax
004012E0 49 dec ecx
004012E1 ^ 75 FA jnz short 160-24-C.004012DD
004012E3 . 81FB FBCFFCAF cmp ebx,0xAFFCCFFB
004012E9 ^ 74 EE je short 160-24-C.004012D9 ;
……
00401301 . 68 73 30 40 00 ascii "hs0@",0 ; YES! You found your serial!!
00401306 . FF35 5C314000 push dword ptr ds:[0x40315C] ; |hWnd = 0037095E ('Your serial is not valid.',class='Edit',parent=00430852)
0040130C . E8 67010000 call <jmp.&USER32.SetWindowTextA> ; \SetWindowTextA
|
原來被修改的是一個關鍵的跳轉!而且,這個跳轉直接指向序列號正確的提示便簽那里!
問題好像解決了。保存,運行,我去,還是提示“Your serial is not valid.”!
什么?指令都按照校驗的要求修改過了,怎么還不對?忽然想起曾經被我忽略的那三條語句,它們每次校驗前都會修-改-一-次指令,而且修改是根據輸入的serial計算的。如果你計算出這個修改,并替換校驗碼的話,下一次運行它又變了!
那我就不讓你修改內存指令了行不行?樓主二話不說,把上面3個指令全部nop掉。而且這次程序雖然不修改指令了,可是你nop掉不是又改了校驗字串了嗎?對。樓主又把校驗那個004012E9的跳轉指令從je改為了jne。
運行,serial隨便輸入一些數字,終于正確了:
最后提醒一下,將修改后的字符串用我前面提供的腳本計算循環異或值的話,結果是0x1aeea0dd。所以,之前修改je后面的校驗值改為這個數。我想起了這個軟件的作者惡作劇的臉……
爆破部分就寫到這里了。
注冊機。要使被修改了的代碼正確的復原,必須是name和serial滿足一定的計算關系。樓主用perl來實現serial的計算。
[Perl] 純文本查看 復制代碼
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
#!/usr/bin/perl
#name和serial需滿足的條件:
#(1)以二者為參數進行運算,對內存代碼進行修改,使0x004012D8-0x004012DF段成為正確的代碼(04EB265458AD33D8);
#(2)為滿足1,要求window-sum(name)應小于推算出來的(window-sum(name)+serial);或者說serial必須為正整數;因此name不是隨意定的。
print "-" x 50,"\n";
$name=$ARGV[0];
$len=length($name);
@rev =$name=~/\w{1}/g;
$rev= join "",reverse(@rev);
$sum=hex(58455443);#"CTEX";
print "Window-sum(name) starts......\n";
for($i=1;$i<5;$i++){
$out=substr($rev,0,$i);
$out=hex(str2hex($out));
$sum += $out;
}
for($i=1;$i<$len-3;$i++){
$out=substr($rev,$i,4);
$out=hex(str2hex($out));
$sum += $out;
}
$sum_str=&de_overflow_hex($sum);
print "Window-sum(name) is: $sum_str\n";
print "Window-sum(name) done.\n";
print "-" x 50,"\n";
print "Serial computing starts......\n";
$m_low_digits_str=join("",(split(//,$sum_str))[6,7,4,5]);
$m_high_digits_str=join("",(split(//,$sum_str))[2,3,0,1]);
$low_digits_str=join("",(split(//,$sum_str))[4,5,6,7]);
$high_digits_str=join("",(split(//,$sum_str))[0,1,2,3]);
print "Low_digits,high_digits are in memory: $m_low_digits_str $m_high_digits_str.\n";
print "Actually low and high values are $low_digits_str,$high_digits_str.\n";
$tail=hex("5854")^hex("0058");
print "The high digits are 5854 xor 0058 = ",dec2hex($tail),"\n";
$head=((hex("26eb")+$tail))^hex("4554");
print "The low digits are (26eb+",dec2hex($tail),") xor 4554 = ",dec2hex($head),"\n";
print "Window-sum(name)+serial should be: ",join("",dec2hex($tail),dec2hex($head)),"\n";
$serial=hex(join("",dec2hex($tail),dec2hex($head)))-hex($sum_str);
$serial_hex_str=dec2hex($serial);
print "The serial should be: ",$serial," or in hex: ",$serial_hex_str,"\n";
print "Serial computing done......\n";
print "-" x 50,"\n";
if($serial < 0){
print "Illegal name! Please try a new name with short length (<8 letters)and low ASCII value, i. e. 7654321;abcde;\n";
}
else{
print "Your serial is: $serial.\n";
}
print "-" x 50,"\n";
sub de_overflow_hex(){
$dec = shift;
# print "$dec will be de_overflowed...\n";
$dec %= 4294967296;#0x100000000;
$d2h = sprintf("%0x",$dec);
return $d2h;
}
sub de_overflow_dec(){
$dec = shift;
# print "$dec will be de_overflowed...\n";
$dec %= 4294967296;#0x100000000;
return $dec;
}
sub str2hex{
my $s = shift;
my $str;
for (0..length($s)-1) {
$str .= sprintf "%0x", ord substr($s, $_, 1);
}
return $str;
}
sub dec2hex(){
$dec=shift;
$hex=sprintf "%0x",$dec;
return $hex;
}
|
需要特別留心的是,name不能是隨即定出來的,需要滿足一定的條件才可以。所以可能要多試試幾個Name。
PS: 這是樓主的第二篇帖子,這一篇耗費的精力更大。之所以起這樣一個題目,是為了提現樓主破解這個程序時左右彷徨的心境。樓主完全依照思維推導的流程走,所以里面有些是走了彎路,但完全是合理的。這樣寫帖子的好處是可以完整細致的與感興趣的愛好者交換想法。
|