《同級生1》DMM版破解手記之腳本文件封包

上次把腳本文件解了出來,不過文件都被加密了,需要推算出加密算法才能編寫封包程序。實際上也不難,本質上就是個簡單的壓縮算法,這次我們來分析一下這一段解壓縮的彙編程序。

0042C327   .  398424 2C1000>CMP DWORD PTR SS:[ESP+102C],EAX
0042C32E   .  BF EE0F0000   MOV EDI,0FEE
0042C333   .  0F84 CB000000 JE AI5WIN.0042C404
0042C339   .  EB 09         JMP SHORT AI5WIN.0042C344

[ESP+102C]存放解密前的數據長度,eax初值是0。edi是臨時緩衝區的指針,初值ffe。下句跳轉基本不會執行,所以直接去42C344。

0042C344   >  D1E8          SHR EAX,1
0042C346   .  A9 00010000   TEST EAX,100
0042C34B   .  894424 10     MOV DWORD PTR SS:[ESP+10],EAX
0042C34F   .  75 12         JNZ SHORT AI5WIN.0042C363

檢查eax的第8位是否爲1,是的話跳轉到42C363。eax的初值爲0,所以第一次運行到這裏的時候會不發生跳轉,我們繼續往下看。

0042C351   .  8A041E        MOV AL,BYTE PTR DS:[ESI+EBX]
0042C354   .  0FB6C0        MOVZX EAX,AL
0042C357   .  83C6 01       ADD ESI,1
0042C35A   .  0D 00FF0000   OR EAX,0FF00
0042C35F   .  894424 10     MOV DWORD PTR SS:[ESP+10],EAX
0042C363   >  F64424 10 01  TEST BYTE PTR SS:[ESP+10],1
0042C368   .  74 22         JE SHORT AI5WIN.0042C38C

從[ebx+esi]中取一個字節給eax後遞增esi,於是我們猜想ebx是密文基址,esi是密文偏移。然後將eax的8-15位都變成1後賦給[esp+10]暫時保存了起來。接着檢查eax的第0位是否爲1,這一行也是上一段代碼跳轉到的地址,看來是個比較重要的判斷點。如果是0的話往下跳轉,第一次運行的時候不是0,所以繼續往下看。

0042C36A   .  8A041E        MOV AL,BYTE PTR DS:[ESI+EBX]
0042C36D   .  8B5424 14     MOV EDX,DWORD PTR SS:[ESP+14]
0042C371   .  0FB6C0        MOVZX EAX,AL
0042C374   .  88443C 1C     MOV BYTE PTR SS:[ESP+EDI+1C],AL
0042C378   .  88042A        MOV BYTE PTR DS:[EDX+EBP],AL
0042C37B   .  83C7 01       ADD EDI,1
0042C37E   .  83C6 01       ADD ESI,1
0042C381   .  83C5 01       ADD EBP,1
0042C384   .  81E7 FF0F0000 AND EDI,0FFF
0042C38A   .  EB 69         JMP SHORT AI5WIN.0042C3F5

又從[ebx+esi]中取了一個字節給eax,證明了上一段的判斷(ebx是密文基址,esi是密文偏移),然後給edx賦了值,看起來像是另一個緩衝區的地址。然後分別把eax賦給了[esp+edi+1c]和[edx+ebp],第一個在棧上,第二個在堆上,在棧上那個利用了上文說到的那個緩衝區,奇怪的是還加了個1c的偏移,先不管它;在堆上的那個應該就是明文地址了,再次猜測edx是密文基址,ebp是密文偏移。最後遞增了一堆變量,於是可以確定ebp是偏移。還有個add edi,1比較怪,看來是限制了棧緩衝區的大小不超過0x1000。最後是一個強制跳轉:

0042C3F5   >  3BB424 2C1000>CMP ESI,DWORD PTR SS:[ESP+102C]
0042C3FC   .^ 0F85 3EFFFFFF JNZ AI5WIN.0042C340

這裏很簡單,是循環的控制語句,看來esi是密文偏移,和密文長度進行比較,於是我們跳回去:

0042C340   >  8B4424 10     MOV EAX,DWORD PTR SS:[ESP+10]
0042C344   >  D1E8          SHR EAX,1
0042C346   .  A9 00010000   TEST EAX,100
……
0042C34F   .  75 12         JNZ SHORT AI5WIN.0042C363

把之前暫存在[esp+10]的變量又賦給了eax,然後繼續進行了上文分析過的判斷。看來[esp+10]的變量應該起到一個控制符的作用。繼續往下分析,這時eax的8-15位已經是ff了,所以跳轉發生,跟進:

0042C363   >  F64424 10 01  TEST BYTE PTR SS:[ESP+10],1
0042C368   .  74 22         JE SHORT AI5WIN.0042C38C

又是這行判斷。結合or eax,0ff00和test eax,100兩行語句,到這裏已經可以基本判斷出這個小循環會進行8次,正好是一個字節的大小,加上上文對[esp+10]的變量作用的猜測,於是我們可以進一步猜測[esp+10]作爲一個控制字節,每一位是1還是0會改變下面的代碼流程。之前這裏沒有跳,我們單步運行幾次,看看跳轉之後的情況:

0042C38C   >  8A141E        MOV DL,BYTE PTR DS:[ESI+EBX]
0042C38F   .  8A441E 01     MOV AL,BYTE PTR DS:[ESI+EBX+1]
0042C393   .  83C6 01       ADD ESI,1
0042C396   .  0FB6C8        MOVZX ECX,AL
0042C399   .  8BC1          MOV EAX,ECX
0042C39B   .  25 F0000000   AND EAX,0F0
0042C3A0   .  0FB6D2        MOVZX EDX,DL
0042C3A3   .  C1E0 04       SHL EAX,4
0042C3A6   .  0BC2          OR EAX,EDX
0042C3A8   .  83E1 0F       AND ECX,0F
0042C3AB   .  83C6 01       ADD ESI,1
0042C3AE   .  83C1 02       ADD ECX,2
0042C3B1   .  894C24 18     MOV DWORD PTR SS:[ESP+18],ECX
0042C3B5   .  BA 00000000   MOV EDX,0

遇見了新的代碼,看起來比較複雜,所以分段分析。這一段首先連續取出了兩個字節,進行一番運算後放到了eax,運算規則是第二個字節的高4位左移4位後加上第一個字節,組成一個新的word。接着將第二個字節的低4位加上2之後存放到[esp+18]作爲下面小循環的最大循環次數。往下看小循環的代碼:

0042C3BA   .  78 39         JS SHORT AI5WIN.0042C3F5
0042C3BC   .  8D6424 00     LEA ESP,DWORD PTR SS:[ESP]
0042C3C0   >  8B5C24 14     MOV EBX,DWORD PTR SS:[ESP+14]
0042C3C4   .  8D0C02        LEA ECX,DWORD PTR DS:[EDX+EAX]
0042C3C7   .  81E1 FF0F0000 AND ECX,0FFF
0042C3CD   .  0FB64C0C 1C   MOVZX ECX,BYTE PTR SS:[ESP+ECX+1C]
0042C3D2   .  884C3C 1C     MOV BYTE PTR SS:[ESP+EDI+1C],CL
0042C3D6   .  83C7 01       ADD EDI,1
0042C3D9   .  880C2B        MOV BYTE PTR DS:[EBX+EBP],CL
0042C3DC   .  83C2 01       ADD EDX,1
0042C3DF   .  83C5 01       ADD EBP,1
0042C3E2   .  81E7 FF0F0000 AND EDI,0FFF
0042C3E8   .  3B5424 18     CMP EDX,DWORD PTR SS:[ESP+18]
0042C3EC   .^ 7E D2         JLE SHORT AI5WIN.0042C3C0
0042C3EE   .  8B9C24 241000>MOV EBX,DWORD PTR SS:[ESP+1024]
0042C3F5   >  3BB424 2C1000>CMP ESI,DWORD PTR SS:[ESP+102C]
0042C3FC   .^ 0F85 3EFFFFFF JNZ AI5WIN.0042C340

第一行的js基本沒用,第二行的lea也意義不明,往下看。首先改變了ebx的值,變成了明文基址,因爲edx被挪用作爲循環計數器和棧緩衝區偏移值了。計算出eax(棧緩衝區偏移1)+edx(棧緩衝區偏移2)賦給ecx,然後從[esp+ecx+1c]中取出一個字節賦給ecx,然後繼續賦給[esp+edi+1c],看來棧緩衝區是循環利用的,裏面保存着已經解出來的最大0x1000字節的明文數據。接着累加一堆變量,並把明文字節cl賦給明文緩衝區[ebx+ebp]。最後還原ebx,並檢查大循環的計數器。

到此所有的解密代碼分析完畢,總結一下就是:

1、讀取下一個字節作爲控制字節,控制接下來的8次操作。

2、從二進制低位向高位檢查控制字節的值,如果爲1則直接取出下個字節,進行步驟4;如果爲0則取出下2個字節,進行步驟3。

3、第二個字節的高4位左移4位後加上第一個字節,作爲從棧緩衝區中讀數據的偏移值;接着將第二個字節的低4位加上2之後存放到[esp+18]作爲讀取字節的個數。

4、如果操作次數未到8次則進行步驟2,否則進行步驟5。

5、如果讀取的總字節數小於密文長度則進行步驟1,否則解密結束。

有了解密算法之後很容易就能逆推出加密算法了,爲了加快封包速度,可以不進行壓縮,直接將所有的控制字符變成ff,然後8個字節一組寫進去就行。原始的mes.arc大小是2066KB,不壓縮封包後的大小是6694KB,還可以接受,關鍵是這樣可以避免很多潛在的由於壓縮算法導致的問題。代碼如下:

DWORD Encode(void* srt, DWORD len, void* dst)
{
    DWORD offset_by_srt = 0;
    BYTE* pCon = (BYTE*)dst;
    BYTE* pOutput = pCon + 1;
    BYTE bitmask = 0x00000001;

    while (offset_by_srt < len)
    {
        BYTE bCon = 0x00000000;
        for (unsigned char i = 0; i < 8 && offset_by_srt < len; i++)
        {
            *pOutput++ = *((char*)srt + offset_by_srt);
            bCon = bCon | (bitmask << i);
            offset_by_srt++;
        }
        *pCon = bCon;
        pCon = pOutput;
        pOutput = pCon + 1;
    }

    return pCon - dst;
}

到這裏,遊戲腳本文件的解包和封包工作均進行完畢。有時間的話,我會再討論一下CG文件的解包和封包,因爲CG文件又多進行了一次加密,elf的程序員真是蛋疼……

 轉載請註明來源及鏈接:未來代碼研究所
 本文鏈接地址:http://blog.atelier39.org/rev_eng/638.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章