標 題: 【原創(chuàng)】神泣逆向工程―我的處女破文
作 者: Bughoho
時 間: 2006-12-03,17:53
鏈 接: http://bbs.pediy.com/showthread.php?t=35832
【文章標題】: 神泣逆向工程―我的處女破文
【文章作者】: BUG
【作者郵箱】: ********
【作者QQ號】: ********
【軟件名稱】: 神泣
【下載地址】: 自己搜索下載
【加殼方式】: Asprotect
【編寫語言】: VC++
【使用工具】: PEID,OD,IDA 5.0
【操作平臺】: WINDOWS XP
【軟件介紹】: 神泣是光通代理的一款韓國泡菜式游戲
【作者聲明】: 最近國內(nèi)對游戲破解開發(fā)搞得有點嚴,本來我也不想發(fā)布的,但是這是我的第一次:),不管怎樣也得記錄一下嘛,失誤之處還請各位高手指正,謝謝!
--------------------------------------------------------------------------------
【前言】
破解此游戲并非用在商業(yè)用途,只是想自己脫機外掛升升級,讓電腦自己去跟電腦說話,我就可以靜心研究了,不過也是通過這一次逆向,讓我感覺進步了不少。
這是我第一寫破文,還請大家批評指正!
【詳細過程】
首先是脫殼,這個游戲是Asprotect的殼子,我現(xiàn)在還是新手,要不是有Asprotect的脫殼機,我想我在這一關(guān)上又得研究好幾天了。脫掉它的"外衣"之后,就開始對它逆向工程了,要分析發(fā)送數(shù)據(jù)的來源,就得對send下斷點,然后輸入賬號密碼,登陸。這時斷點斷在了WS_32模塊,回到用戶領(lǐng)空,
到達這里:
0051B740 /$ B8 00100000 MOV EAX,1000
0051B745 |. E8 A68C0200 CALL _game.005443F0
0051B74A |. 53 PUSH EBX
0051B74B |. 8B9C24 0C1000>MOV EBX,DWORD PTR SS:[ESP+100C]
0051B752 |. 8BCB MOV ECX,EBX
0051B754 |. 55 PUSH EBP
0051B755 |. 56 PUSH ESI
0051B756 |. 8BB424 101000>MOV ESI,DWORD PTR SS:[ESP+1010]
0051B75D |. 8BC1 MOV EAX,ECX
0051B75F |. 57 PUSH EDI
0051B760 |. 8D6B 02 LEA EBP,DWORD PTR DS:[EBX+2]
0051B763 |. 8D7C24 12 LEA EDI,DWORD PTR SS:[ESP+12]
0051B767 |. C1E9 02 SHR ECX,2
0051B76A |. 66:896C24 10 MOV WORD PTR SS:[ESP+10],BP
0051B76F |. 53 PUSH EBX
0051B770 |. F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
0051B772 |. 8BC8 MOV ECX,EAX
0051B774 |. 83E1 03 AND ECX,3
0051B777 |. F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>
0051B779 |. 8D4C24 16 LEA ECX,DWORD PTR SS:[ESP+16]
0051B77D |. 51 PUSH ECX
0051B77E |. E8 2DB5FFFF CALL _game.00516CB0 ; 加密函數(shù)
0051B783 |. A1 A0661002 MOV EAX,DWORD PTR DS:[21066A0]
0051B788 |. 83C4 08 ADD ESP,8
0051B78B |. 83C3 02 ADD EBX,2
0051B78E |. 8D5424 10 LEA EDX,DWORD PTR SS:[ESP+10]
0051B792 |. 6A 00 PUSH 0 ; /Flags = 0
0051B794 |. 53 PUSH EBX ; |DataSize
0051B795 |. 52 PUSH EDX ; |Data
0051B796 |. 50 PUSH EAX ; |Socket => EC
0051B797 |. FF15 D8135B00 CALL DWORD PTR DS:[<&ws2_32.send>] ; \send
這里還挺近的,PUSH ECX就是原始數(shù)據(jù),
這以后我就用IDA看了,動態(tài)調(diào)試時用OD,靜態(tài)分析還是IDA強大,
跟進_game.00516CB0到達:
.text:00516CB0 sub_516CB0 proc near ; CODE XREF: sub_51B740+3Ep
.text:00516CB0
.text:00516CB0 arg_0 = dword ptr 4
.text:00516CB0 arg_4 = dword ptr 8
.text:00516CB0
.text:00516CB0 mov al, ds:byte_2100685
.text:00516CB5 test al, al
.text:00516CB7 jz short locret_516CEB
.text:00516CB7
.text:00516CB9 mov eax, ds:dword_210067C
.text:00516CBE test eax, eax
.text:00516CC0 jnz short loc_516CD7
.text:00516CC0
.text:00516CC2 mov eax, [esp+arg_4]
.text:00516CC6 mov ecx, [esp+arg_0]
.text:00516CCA push eax
.text:00516CCB push ecx
.text:00516CCC mov ecx, offset unk_20FF4D8
.text:00516CD1 call sub_517CC0 ;先不管那2個全
局變量地址判斷了什么,發(fā)送登陸封包是通過了這個函數(shù)
.text:00516CD1
.text:00516CD6 retn
.text:00516CD6
.text:00516CD7 ; -----------------------------------------------------------------------
----
.text:00516CD7
.text:00516CD7 loc_516CD7: ; CODE XREF: sub_516CB0+10j
.text:00516CD7 mov edx, [esp+arg_4]
.text:00516CDB mov eax, [esp+arg_0]
.text:00516CDF push edx
.text:00516CE0 push eax
.text:00516CE1 mov ecx, offset unk_20FF628
.text:00516CE6 call sub_518000
.text:00516CE6
.text:00516CEB
.text:00516CEB locret_516CEB: ; CODE XREF: sub_516CB0+7j
.text:00516CEB retn
.text:00516CEB
.text:00516CEB sub_516CB0 endp
分析可知arg_4 = 長度,arg_0為原始內(nèi)容,unk_20FF4D8就是一個偏移量,從代碼上看來,我猜測它是this指針,類是一個全局變量,所以直接賦值了一個偏移量,在后面的代碼上看來我的分析是正確的.
.........
以上是水話.現(xiàn)在說點實在的.
最終到達sub_517B20:
.text:00517B20 sub_517B20 proc near ; CODE XREF: sub_517CC0+Bp
.text:00517B20
.text:00517B20 dest = dword ptr 10h
.text:00517B20 src = dword ptr 14h
.text:00517B20 datalen = dword ptr 18h
.text:00517B20
.text:00517B20 push ebx
.text:00517B21 push ebp
.text:00517B22 push esi
.text:00517B23 mov esi, ecx ; esi = this指針
.text:00517B25 xor ebp, ebp
.text:00517B27 push edi
.text:00517B28 mov edx, [esi+104h]
.text:00517B2E cmp edx, ebp
.text:00517B30 jle else ; 如果 esi+104h <= 0 就跳到另一個分支
.text:00517B30
.text:00517B36 mov ecx, [esp+4+datalen]
.text:00517B3A mov eax, 10h
.text:00517B3F sub eax, edx
.text:00517B41 cmp ecx, eax
.text:00517B43 jg short _len_jg_else
.text:00517B43
.text:00517B45 mov eax, ecx
.text:00517B47 dec ecx
.text:00517B48 test eax, eax
.text:00517B4A jz short _small_return
.text:00517B4A
.text:00517B4C mov eax, [esp+4+dest]
.text:00517B50 lea edi, [ecx+1]
.text:00517B53 mov ecx, [esp+4+src]
.text:00517B53
.text:00517B57
.text:00517B57 _loc_for: ; CODE XREF: sub_517B20+5Aj
.text:00517B57 mov edx, [esi+104h]
.text:00517B5D mov bl, [ecx]
.text:00517B5F mov dl, [esi+edx+108h]
.text:00517B66 xor dl, bl
.text:00517B68 mov [eax], dl
.text:00517B6A mov ebx, [esi+104h]
.text:00517B70 inc ebx
.text:00517B71 inc eax
.text:00517B72 inc ecx
.text:00517B73 dec edi
.text:00517B74 mov [esi+104h], ebx
.text:00517B7A jnz short _loc_for
.text:00517B7A
.text:00517B7C
.text:00517B7C _small_return: ; CODE XREF: sub_517B20+2Aj
.text:00517B7C cmp dword ptr [esi+104h], 10h
.text:00517B83 jl _loc_return
.text:00517B83
.text:00517B89 mov [esi+104h], ebp
.text:00517B8F pop edi
.text:00517B90 pop esi
.text:00517B91 pop ebp
.text:00517B92 pop ebx
.text:00517B93 retn 0Ch
.text:00517B93
.text:00517B96 ; -----------------------------------------------------------------------
----
.text:00517B96
.text:00517B96 _len_jg_else: ; CODE XREF: sub_517B20+23j
.text:00517B96 mov ebx, [esp+4+src]
.text:00517B9A mov edi, [esp+4+dest]
.text:00517B9E sub ecx, eax
.text:00517BA0 cmp edx, 10h
.text:00517BA3 mov [esp+4+datalen], ecx
.text:00517BA7 jge short loc_517BD0
.text:00517BA7
.text:00517BA9
.text:00517BA9 _loc_for_1: ; CODE XREF: sub_517B20+AEj
.text:00517BA9 mov eax, [esi+104h]
.text:00517BAF mov dl, [esi+eax+108h]
.text:00517BB6 mov al, [ebx]
.text:00517BB8 xor dl, al
.text:00517BBA mov [edi], dl
.text:00517BBC mov eax, [esi+104h]
.text:00517BC2 inc eax
.text:00517BC3 inc edi
.text:00517BC4 inc ebx
.text:00517BC5 cmp eax, 10h
.text:00517BC8 mov [esi+104h], eax
.text:00517BCE jl short _loc_for_1
.text:00517BCE
.text:00517BD0
.text:00517BD0 loc_517BD0: ; CODE XREF: sub_517B20+87j
.text:00517BD0 mov [esi+104h], ebp
.text:00517BD6 jmp short loc_517BE4
.text:00517BD6
.text:00517BD8 ; -----------------------------------------------------------------------
----
.text:00517BD8
.text:00517BD8 else: ; CODE XREF: sub_517B20+10j
.text:00517BD8 mov ebx, [esp+4+src]
.text:00517BDC mov edi, [esp+4+dest]
.text:00517BE0 mov ecx, [esp+4+datalen]
.text:00517BE0
.text:00517BE4
.text:00517BE4 loc_517BE4: ; CODE XREF: sub_517B20+B6j
.text:00517BE4 cmp ecx, 10h
.text:00517BE7 jl short loc_517C58
.text:00517BE7
.text:00517BE9 mov ebp, ecx
.text:00517BEB shr ebp, 4
.text:00517BEE mov eax, ebp
.text:00517BF0 neg eax
.text:00517BF2 shl eax, 4
.text:00517BF5 add ecx, eax
.text:00517BF7 mov [esp+4+datalen], ecx
.text:00517BF7
.text:00517BFB
.text:00517BFB loc_517BFB: ; CODE XREF: sub_517B20+132j
.text:00517BFB ; 這個循環(huán)就是加密了.
.text:00517BFB lea eax, [esi+0F4h] ; DWORD m_key1[4]
.text:00517C01 mov ecx, esi ; KeyCode函數(shù)里面會壓入this指針進入真正執(zhí)行操作的函數(shù)
.text:00517C03 push eax
.text:00517C04 lea eax, [esi+108h] ; DWORD m_key2[4]
.text:00517C0A push eax
.text:00517C0B call KeyCode ; 這個函數(shù)是通過循環(huán)將edx,eax,ecx(ecx這里雖然是一個指針,其實是指向第一個成員變量)
.text:00517C0B ; 這個函數(shù)太長,我寫程序時是用的內(nèi)嵌匯編,我寫這篇文章時正在還原這個函數(shù).
.text:00517C0B
.text:00517C10 mov ecx, esi
.text:00517C12 call convKey ; 呵呵,小動作.把一個KEY數(shù)組的值全部加1
.text:00517C12
.text:00517C17 mov ecx, [ebx]
.text:00517C19 mov edx, [esi+108h]
.text:00517C1F xor ecx, edx
.text:00517C21 add ebx, 10h
.text:00517C24 mov [edi], ecx
.text:00517C26 mov edx, [ebx-0Ch]
.text:00517C29 mov eax, [esi+10Ch]
.text:00517C2F add edi, 10h
.text:00517C32 xor edx, eax
.text:00517C34 mov [edi-0Ch], edx
.text:00517C37 mov eax, [ebx-8]
.text:00517C3A xor eax, [esi+110h]
.text:00517C40 mov [edi-8], eax
.text:00517C43 mov ecx, [ebx-4]
.text:00517C46 mov eax, [esi+114h]
.text:00517C4C xor ecx, eax
.text:00517C4E dec ebp
.text:00517C4F mov [edi-4], ecx
.text:00517C52 jnz short loc_517BFB
.text:00517C52
.text:00517C54 mov ecx, [esp+4+datalen]
.text:00517C54
.text:00517C58
.text:00517C58 loc_517C58: ; CODE XREF: sub_517B20+C7j
.text:00517C58 test ecx, ecx
.text:00517C5A jle short _loc_return
.text:00517C5A
.text:00517C5C lea edx, [esi+0F4h]
.text:00517C62 lea eax, [esi+108h]
.text:00517C68 push edx
.text:00517C69 push eax
.text:00517C6A mov ecx, esi
.text:00517C6C call KeyCode
.text:00517C6C
.text:00517C71 mov ecx, esi
.text:00517C73 call convKey
.text:00517C73
.text:00517C78 mov ecx, [esp+4+datalen]
.text:00517C7C mov eax, [esi+104h]
.text:00517C82 cmp eax, ecx
.text:00517C84 jge short _loc_return
.text:00517C84
.text:00517C86
.text:00517C86 loc_517C86: ; CODE XREF: sub_517B20+18Cj
.text:00517C86 mov edx, [esi+104h]
.text:00517C8C mov al, [esi+edx+108h]
.text:00517C93 mov dl, [ebx]
.text:00517C95 xor al, dl
.text:00517C97 mov [edi], al
.text:00517C99 mov ebp, [esi+104h]
.text:00517C9F inc ebp
.text:00517CA0 inc edi
.text:00517CA1 mov eax, ebp
.text:00517CA3 inc ebx
.text:00517CA4 cmp eax, ecx
.text:00517CA6 mov [esi+104h], ebp
.text:00517CAC jl short loc_517C86
.text:00517CAC
.text:00517CAE
.text:00517CAE _loc_return: ; CODE XREF: sub_517B20+63j
.text:00517CAE ; sub_517B20+13Aj
.text:00517CAE ; sub_517B20+164j
.text:00517CAE pop edi
.text:00517CAF pop esi
.text:00517CB0 pop ebp
.text:00517CB1 pop ebx
.text:00517CB2 retn 0Ch
.text:00517CB2
.text:00517CB2 sub_517B20 endp
代碼太多,我又很懶,我主要還原一下 loc_517BFB 加密這個循環(huán)部分吧。
要逆向這個游戲涉及的地方太多,又不能一一貼出,我講解一下我分析出來的結(jié)果好了
class CCode//20FF4D8
{
BYTE m_keydata[240];//這是一個KEY表,加密的條件之1
//+F0
int m_num;//這個還不知道它的完全用途
//+F4
DWORD m_key1[4];//這是個DWORD數(shù)組,加密的條件之1
//+104
DWORD m_nopnum;//現(xiàn)在僅知道通過它來判斷加密還是解密,不過從值上來看不像是它的完全用途
//+108
DWORD m_key2[4];//這是個DWORD數(shù)組,加密的條件之1
//----------------------------------------------------------
這里是loc_517BFB 還原出來的代碼
else//加密
{
if( len >= 10 )
{
int nnum;
__asm
{
mov eax,len;
shr eax,4
mov nnum,eax
neg eax
shl eax,4
add len,eax
}
for( ; nnum > 0; nnum-- )//一次循環(huán)加密16字節(jié),所以上面會shr eax,4
{
KeyCode(m_key2,m_key1);//每一次循環(huán)都會變換m_key1,m_key2和m_keydata的值(m_keydata的值是否變化有待深入考究)
convKey();
DWORD tmp;
memcpy(&tmp,( src ),sizeof(DWORD));
tmp ^= m_key2[0];
*(DWORD*)( dest ) = tmp;
memcpy(&tmp,( src + 4 ),sizeof(DWORD));
tmp ^= m_key2[1];
*(DWORD*)( dest + 4) = tmp;
memcpy(&tmp,( src + 8 ),sizeof(DWORD));
tmp ^= m_key2[2];
*(DWORD*)( src + 8) = tmp;
memcpy(&tmp,( src + 12 ),sizeof(DWORD));
tmp ^= m_key2[3];
*(DWORD*)( dest + 12) = tmp;
src += 16;
dest += 16;
}
}
這就是加密的全過程了,解密部分我也還原出來了,但是這里還是不貼的好,以免某些人搞破壞。
雖然這里加密就完了,其實遠遠還沒這么簡單,上面提到加密需要的三個數(shù)據(jù)m_key1,m_key2,m_keydata。m_keydata和 m_key1會在游戲開始時初始化,首先,連接游戲服務(wù)器,連接成功后服務(wù)端會主動熱情的發(fā)送給你一個KEY封包,它的結(jié)構(gòu)是這樣子的:
//消息頭結(jié)構(gòu)
typedef struct _MsgHeader
{
WORD len;
WORD cmd;
_MsgHeader()
{
len = 0;
cmd = 0x0;
}
}MsgHeader;
//接收密鑰結(jié)構(gòu)
typedef struct _MsgKey
{
BYTE d1;
BYTE d2;
BYTE d3;
BYTE key1[0x40];
BYTE key2[0x80];
}MsgKey;
MsgKey的長度 = C7-MsgHeader
然后通過這個KEY就可以變形得出m_keydata和key1的值
我的表達能力不是很好,寫到這里的時候,已經(jīng)用了我1個多小時的時間了,深切的感到自己語文成績之差,這里我已經(jīng)不好描述了,所以我還是貼代碼描述好了,抱歉!
從 DeCodeKey 函數(shù)開始
DWORD dword_210067C;// =dekey2 中的 d1,只有這次寫入操作;
DWORD dword_2100688 = 0x456FC070;//本來是通過時間來生成一個KEY,只有這次寫入操作,應(yīng)該不會有副本做判斷,直接賦值常量可能也能成功
CCode::CCode()
{
//m_num = 0x0a;
//memcpy((void*)m_key1,"\x81\x28\x59\x9E\x00\xBD\xC7\xB7\xD1\xBB\x9F\xF1\xA8\x37\xD8\xE2",16);
//m_nopnum = 0;
//strcpy((char*)m_key2,"");
}
CCode::~CCode()
{
}
//設(shè)置KEY
//data : 接收的密鑰封包去掉頭2字節(jié)
void CCode::DeCodeKey(BYTE* data)//接受到的KEY封包
{
MsgKey msgkey;
msgkey.d1 = (BYTE)data[0];
msgkey.d2 = (BYTE)data[2];
msgkey.d3 = (BYTE)data[1];
memcpy( msgkey.key1, &data[0x3],0x40 );//復制到結(jié)構(gòu)里去
memcpy( msgkey.key2, &data[0x43],0x80 );
dekey1(msgkey.d1,msgkey.d3,msgkey.d2,msgkey.key1,msgkey.key2 );
}
void CCode::dekey1( BYTE d1,BYTE d2,BYTE d3,BYTE* key1,BYTE* key2 )//dekey1在游戲程序中是一個虛函數(shù)
{
DWORD var_84 = 0;
BYTE var_80[0x80];
dekey2(var_80,&var_84,d1 & 0xFF,key1,d2 & 0xFF,key2,d3 & 0xFF);
}
//一些無關(guān)痛癢的結(jié)構(gòu)
typedef struct _Unstname//暫時不知道干什么的,dekey2中把這個結(jié)構(gòu)進行變形
{
BYTE var_8C[0x7F];
BYTE var_D;
_Unstname()
{
memset(var_8C,0,0x80);//一次性把var_D也刷掉
}
}Unstname;
DWORD d_dword_210065C[4];
void CCode::dekey2(OUT BYTE* var_80,OUT DWORD* var_84,BYTE d1,BYTE* key1,BYTE d2,BYTE* key2,BYTE d3)
{
//DWORD var_C4 = 0;
//DWORD var_C0 = 0;
//DWORD var_BC = 0;
//DWORD var_B8 = 0;
//DWORD var_B4 = 0;
//DWORD var_B0 = 0;
//DWORD var_AC = 0;
//DWORD var_A8 = 0;
DWORD var_C4[8] = {0};
BYTE var_A4[0x24];
Unstname unstname;
BYTE var_C[0x8];
DWORD var_4;
dword_210067C = d1;
outkey80((BYTE*)&unstname,sizeof(Unstname)/*0x80*/);//這個函數(shù)通過了dword_2100688變
量來生成一個80字節(jié)的數(shù)據(jù),那個變量是跟時間有關(guān)的數(shù)據(jù)變形得到的,不過我用常量好象也能使用,待考究
unstname.var_D &= 0x7F;//還原代碼而已,還沒看到用途
SHA256(var_C4,(unsigned char *)&unstname,0x80,key2,d3);//這里就通過SHA算法生成一個數(shù)據(jù)
//SHA256函數(shù)里面我就直接按游戲中的加密順序套用了標準函數(shù)了
if( dword_210067C == 0 )
{
//快到InitKey函數(shù)了...快打通了..
InitKey(var_C4,0x10);//變形得到m_keydata.
d_dword_210065C[0] = var_C4[4];
d_dword_210065C[1] = var_C4[5];
d_dword_210065C[2] = var_C4[6];
d_dword_210065C[3] = var_C4[7];
setkey1(d_dword_210065C);//設(shè)置m_key1的值
}
else
{
}
}
void CCode::setkey1(DWORD key[4])
{
m_key1[0] = key[0];
m_key1[1] = key[1];
m_key1[2] = key[2];
m_key1[3] = key[3];
//memcpy(m_key1,key,0x10);
m_nopnum = 0;
}
void CCode::SHA256(DWORD* var_C4,unsigned char *input,DWORD len,BYTE* key2,BYTE d3)
{
//BYTE var_114[0x68];//可能是0x28+0x40
sha256_context ctx;
BYTE sdata1[0x40];
BYTE sdata2[0x40];
BYTE var_sha_outstr[32];
BYTE var_C[8];//跟異常有關(guān)
DWORD var_4;//完全沒用到
//call sub_517E80 //關(guān)于var_114
var_4 = 0;
BYTE* s = NULL;
if(len > 0x40)
{
sha256_starts(&ctx);
sha256_update(&ctx,input,len);
sha256_finish(&ctx,var_sha_outstr);
s = var_sha_outstr;
}
else
{
s = input;
}
memset(sdata1,0,0x40);
memset(sdata2,0,0x40);
memcpy(sdata1, s, 32 );
memcpy(sdata2, s, 32 );
for(int idx = 0; idx < 0x40; idx++)
{
sdata1[idx] = sdata1[idx] ^ 0x36;
sdata2[idx] = sdata2[idx] ^ 0x5C;
}
sha256_starts( &ctx );
sha256_update( &ctx, (uint8 *) sdata1, 0x40 );
sha256_update( &ctx, (uint8 *) key2, d3 );
sha256_finish( &ctx, (uint8 *)var_C4);
sha256_starts( &ctx );
sha256_update( &ctx, (uint8 *) sdata2, 0x40 );
sha256_update( &ctx, (uint8 *) var_C4, 0x20 );
sha256_finish( &ctx, (uint8 *)var_C4);
}
//初始化KEY表
//addr:可能是通過密鑰封包運算出來的
void CCode::InitKey(DWORD* addr,int num)
{
InitKeys(addr,num*8,this);//匯編代碼.....太長了加我太懶了..
}
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請
點擊舉報。