界面很簡單,輸入name和serial的輸入框和一個(gè)結(jié)果提示的標(biāo)簽。錯(cuò)誤提示如下:
沒有按鈕觸發(fā)檢驗(yàn)過程,也沒有對(duì)話框彈出。所以不能用這些相關(guān)函數(shù)下斷點(diǎn)了。好在搜索文本的話,可以看到如以上二圖Status那一欄的提示語:“Your serial is not valid.”或“YES! You found your serial!!!”。好了,雙擊Your serial is not valid來到函數(shù)調(diào)用的地方。
[Asm] 純文本查看 復(fù)制代碼
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
|
看這段代碼的話,錯(cuò)誤提示排在正確提示前面,那么推測他們前面必然有一個(gè)關(guān)鍵跳,serial正確的話會(huì)跳到00401301;錯(cuò)的話會(huì)走向004012EB。因此我們開始往上翻。不對(duì)啊,翻到段頭也沒有看到一個(gè)滿足這樣的跳轉(zhuǎn)邏輯的關(guān)鍵跳。怎么回事?
沒辦法,只好翻到段首下斷點(diǎn)。
[Asm] 純文本查看 復(fù)制代碼
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開始,依次取用戶名四位加和,會(huì)溢出。eax中為后八位。
|
運(yùn)行程序,輸入name:abcdefgh,serial打算用123456。剛輸入一個(gè)1,運(yùn)行就被中斷到段首。需要注意的是,程序的窗口上,數(shù)字1還沒有顯示出來呢。可見程序的處理流程是接收到用戶輸入的每一個(gè)serial的字符都要先判斷一次,確定正確與否之后才能顯示在serial的文本框里。Name就不存在這種情況。簡單的說序列號(hào)是隨輸隨檢的。
好了,我們用F8步進(jìn),并同時(shí)觀察數(shù)據(jù)窗。剛開始就是GetDlgItemInt函數(shù),這是在讀取用戶輸入,還要求是整數(shù)形式。好的,我們輸入的就是數(shù)字。
繼續(xù),在0040128F出現(xiàn)了name 的GetWindowTextA函數(shù),這時(shí)我們發(fā)現(xiàn)輸入的name,最長字符長度等參數(shù)的入棧過程。這里0x14就是最長長度,20個(gè)字符。
在判斷name不為空之后,程序?qū)ame進(jìn)行了一種運(yùn)算。它從字符串CTEX出發(fā),將name截取前四個(gè)字符,與CTEX相加。然后截去name的首位,再截取前四個(gè)字符,繼續(xù)與之前的累加和相加。一直進(jìn)行,截到后面不足4位的就用0補(bǔ)齊。這樣就得到一個(gè)累加和。
這里有一點(diǎn)需要留意的是,這個(gè)累加和會(huì)溢出,而eax只能保存后八位。
繼續(xù)往下走。
[Asm] 純文本查看 復(fù)制代碼
1
2
3
4
5
|
004012B8 . 5B pop ebx ; 取序列號(hào)來檢測,隨輸隨檢,此時(shí)界面都還沒顯示;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 ;
|
以上幾句看起來是很簡單的運(yùn)算,之前的累加和與序列號(hào)接收到的數(shù)字(此處為1)相加,其和與004012D9處的dword字符串做異或運(yùn)算,結(jié)果保存在004012D9;其和取后四位,再與004012D9處的word做減法,結(jié)果保存在004012D9。毫不起眼的幾句指令啊。
樓主隨即用perl復(fù)現(xiàn)了以上流程。
[Perl] 純文本查看 復(fù)制代碼
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";
#程序?qū)erial隨輸隨檢,這里只用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";#報(bào)錯(cuò),這個(gè)數(shù)會(huì)溢出,因此只取后八位。
#取后八位;
$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;
}
|
一算,完全沒問題,跟程序算出來的一模一樣。感覺離結(jié)果更近了有沒有?
繼續(xù)。

[Asm] 純文本查看 復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
|
004012CB . BE EC114000 mov esi,160-24-C.004011EC ; 字串的位置,共62個(gè)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 ; 疑似的關(guān)鍵跳轉(zhuǎn)。
|
繼續(xù)F8。我們發(fā)現(xiàn)程序指定了一個(gè)內(nèi)存位置004011EC做起始,和一個(gè)計(jì)數(shù)器3E(即62),然后跳向004012DD循環(huán)讀取Dword。以EBX=0起始,每讀取依次均與EBX異或,直至循環(huán)完成。簡單的說,就是0與62個(gè)Dword依次異或。最后判斷這個(gè)結(jié)果是否等于0xAFFCCFFB.如果不等的話回到004012D9。這次對(duì)al操作先加7再減D,隨后又進(jìn)入上面那樣的異或的循環(huán)。
你是不是隱約感到抓住關(guān)鍵判斷和關(guān)鍵跳轉(zhuǎn)了?Too young too simple, sometimes naïve。 我們不能讓004012E9跳轉(zhuǎn),因?yàn)樘D(zhuǎn)了就會(huì)進(jìn)入一個(gè)無窮無盡的循環(huán);可是不跳轉(zhuǎn)馬上就報(bào)錯(cuò)了,怎么辦啊?
于是想到那我修改004012E3處的校驗(yàn)碼0xAFFCCFFB不就可以了?嘿嘿,難不到我啦!
樓主還是用perl算啦。先從004011EC到004012E3復(fù)制出來一點(diǎn)HEX,然后用它們做異或,只要我得到這個(gè)結(jié)果,替換原先的校驗(yàn)碼0xAFFCCFFB一定可以的,哈哈。
說干就干,還是perl;
[Perl] 純文本查看 復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
|
#!/usr/bin/perl
#下面這個(gè)字符串就是用來校驗(yàn)的;
$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";
|
好了,根據(jù)以上腳本的計(jì)算結(jié)果,樓主興沖沖的把得到的結(jié)果0adcb7fb替換了0xAFFCCFFB。保存,重新運(yùn)行,我靠了,居然還是“Your serial is not valid.”
樓主只好重新一步步對(duì)比異或循環(huán)的結(jié)果,一個(gè)數(shù)值一個(gè)數(shù)值的對(duì)。一直到快接近字串末尾的049306F1運(yùn)算時(shí),居然跟eax的值不相同了。下一個(gè)數(shù)值00AD33D8出現(xiàn)了同樣的情況。這樣異或運(yùn)算到最后的時(shí)候,已經(jīng)不是0adcb7fb了。
真TM的見鬼了,一個(gè)字符串還能不相同,我明明是拷貝過去的啊!
樓主在此盤旋了好久好久,就是想不明白。于是把004011EC到004012E3內(nèi)存位置下了一個(gè)寫入斷點(diǎn),看看到底發(fā)生了什么詭異事件。
結(jié)果斷在了這里:
[Asm] 純文本查看 復(fù)制代碼
1
|
00401269 C705 D9124000 5>mov dword ptr ds:[0x4012D9],0x584554 ; vengers
|
什么,那個(gè)校驗(yàn)用的字符串被修改了,校驗(yàn)值還能對(duì)嗎?可是這程序把自己修改了,TM的居然還能正常運(yùn)行。
繼續(xù)運(yùn)行,又?jǐn)嘣诹艘韵聝商帲?/p>
[Asm] 純文本查看 復(fù)制代碼
1
2
|
004012BB 3105 D9124000 xor dword ptr ds:[0x4012D9],eax ;
004012C4 66:2905 D912400>sub word ptr ds:[0x4012D9],ax ;
|
我一下恍然大悟了,這不是之前覺得毫不起眼的那幾句指令啊!程序把自己修改了,而且修改的一定是關(guān)鍵的跳轉(zhuǎn),否則我們無法解釋找不到合理的關(guān)鍵跳的理由。那怎么找回原來的代碼呢?程序之前給出的校驗(yàn)碼就能起到這個(gè)作用啊。利用校驗(yàn)碼倒推!
樓主看了下,這幾處修改涉及了第60和61個(gè)字符串,根據(jù)00401269、004012BB和004012C4的運(yùn)算可以推出正確的代碼應(yīng)該是04XXXXXXXX和XXAD33D8的樣式。我們現(xiàn)在把這些X算出來。
因?yàn)槠渌?0個(gè)字串時(shí)候不變的,可以算出他們的累計(jì)的異或值,再與校驗(yàn)碼0xAFFCCFFB異或,即為04XXXXXXXX和XXAD33D8異或的值。樓主這里就不貼perl腳本了。這樣就算出他們分別是:04EB2654和58AD33D8。
驗(yàn)證一下。
好了,樓主又興沖沖的在反匯編窗口把那些被修改的指令修改乘了推算的指令,奇跡出現(xiàn)了:
[Asm] 純文本查看 復(fù)制代碼
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 ;被復(fù)原的指令
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
|
原來被修改的是一個(gè)關(guān)鍵的跳轉(zhuǎn)!而且,這個(gè)跳轉(zhuǎn)直接指向序列號(hào)正確的提示便簽?zāi)抢铮?br/>
問題好像解決了。保存,運(yùn)行,我去,還是提示“Your serial is not valid.”!
什么?指令都按照校驗(yàn)的要求修改過了,怎么還不對(duì)?忽然想起曾經(jīng)被我忽略的那三條語句,它們每次校驗(yàn)前都會(huì)修-改-一-次指令,而且修改是根據(jù)輸入的serial計(jì)算的。如果你計(jì)算出這個(gè)修改,并替換校驗(yàn)碼的話,下一次運(yùn)行它又變了!
那我就不讓你修改內(nèi)存指令了行不行?樓主二話不說,把上面3個(gè)指令全部nop掉。而且這次程序雖然不修改指令了,可是你nop掉不是又改了校驗(yàn)字串了嗎?對(duì)。樓主又把校驗(yàn)?zāi)莻(gè)004012E9的跳轉(zhuǎn)指令從je改為了jne。
運(yùn)行,serial隨便輸入一些數(shù)字,終于正確了:
最后提醒一下,將修改后的字符串用我前面提供的腳本計(jì)算循環(huán)異或值的話,結(jié)果是0x1aeea0dd。所以,之前修改je后面的校驗(yàn)值改為這個(gè)數(shù)。我想起了這個(gè)軟件的作者惡作劇的臉……
爆破部分就寫到這里了。
注冊(cè)機(jī)。要使被修改了的代碼正確的復(fù)原,必須是name和serial滿足一定的計(jì)算關(guān)系。樓主用perl來實(shí)現(xiàn)serial的計(jì)算。
[Perl] 純文本查看 復(fù)制代碼
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)以二者為參數(shù)進(jìn)行運(yùn)算,對(duì)內(nèi)存代碼進(jìn)行修改,使0x004012D8-0x004012DF段成為正確的代碼(04EB265458AD33D8);
#(2)為滿足1,要求window-sum(name)應(yīng)小于推算出來的(window-sum(name)+serial);或者說serial必須為正整數(shù);因此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不能是隨即定出來的,需要滿足一定的條件才可以。所以可能要多試試幾個(gè)Name。
PS: 這是樓主的第二篇帖子,這一篇耗費(fèi)的精力更大。之所以起這樣一個(gè)題目,是為了提現(xiàn)樓主破解這個(gè)程序時(shí)左右彷徨的心境。樓主完全依照思維推導(dǎo)的流程走,所以里面有些是走了彎路,但完全是合理的。這樣寫帖子的好處是可以完整細(xì)致的與感興趣的愛好者交換想法。
|