[Malie] 尋找密鑰 P1
[Malie引擎] 尋找密鑰 P1
今天下午剛考完,也沒什么事干,也沒錢,只能在宿舍坐著了。
坐著也坐著,上KF看看,看到個帖子 求Campus 戀音セ?ピアーチェ的完整立繪
https://kf.miaola.work/read.php?tid=980463&sf=fd2
campus這個社的游戲還行啊,點開看看,好像還有好幾個相關帖子,也是封包解不開的問題
雖然這引擎也見過好幾次,但是也沒認真看過,反正咱也不是漢化組的,沒什么必要也不會去看。
好吧,那就看看吧,看帖子說Garbro解包不了,這個挺正常的,因為這引擎的封包都是單獨密鑰加密的,基本上每個游戲都不太一樣,所以得手動找密鑰,但是Garbro的作者也不知道是為什么考慮,設計游戲密鑰都不公開明文,也很少會說怎么找密鑰。

編譯調試Garbro
因為Garbro有一部分campus社的游戲是可以解包的,因為作者已經導入密鑰,反正現在也不知道怎么找密鑰,先下一個Garbro在打開封包的地方下個斷點看看密鑰長啥樣吧(對的雖然它不公開,但是調試的時候依然可以看到,打開Garbro的項目也可以看到有一個叫SchemeBuilder的項目被刪了)

在編譯和運行Garbro之前還會有Nuget包管理器的問題,參考以下兩個鏈接解決
https://stackoverflow.com/questions/57394869/missing-nuget-targets
https://learn.microsoft.com/en-us/nuget/consume-packages/package-restore
解決完成之后就可以正常編譯運行了。可能還會有什么鬼簽名的問題,自己搜一下吧。
編譯exdieslib
exdieslib就比較簡單了,用的是C++寫的,只要到這里下.cpp即可,但是作者沒提供as-util.h這個頭文件,可以在FuckGalEngine的Minori/Minori/fuckpaz里找到一個。可能是FuckGalEngine項目作者自己寫的。
http://asmodean.reverse.net/pages/exdieslib.html
https://github.com/Inori/FuckGalEngine/tree/master/Minori/Minori/fuckpaz
得到一個cpp和一個頭文件,vs新建一個項目加進去就行了,不過exdieslib有好幾個函數用的都是非標準c的函數,vs編譯會報錯,依據報錯改過來就行了。

exdieslib也能直接看到密鑰啥樣。

先看密鑰!
先找到ArcLIB.cs這個文件,然后到找到151行,在foreach處下斷點。然后GARbro.GUI為調試的目標程序,點擊綠色三角運行即可。之后就是在GarbroGUI的窗口里找到封包雙擊打開,就斷下了。

我選擇的游戲是 Deep Love Diary -戀人日記- パッケージ版 這個是Garbro有內置密鑰支持解包的,需要注意的是有時候Garbro自身殘留配置文件,會導致無法識別游戲,進而沒辦法解包,即使內置密鑰也不行。由于我懶得找Garbro的配置文件了,之前記得在用戶文件夾的什么地方,后面忘了,反出現這種情況。直接把Garbro移動到別的文件夾或路徑下就可以了。
ok,準備就緒我們來看看密鑰,在左下角的auto窗口可以看到KnownSchemes這個結構,點+展開,就能看到里面對應的游戲名和密鑰了。和exdieslib是一樣的,不過Garbro的密鑰大小會少4*4個字節,不過一般都是00。

可以結合exdieslib來看,exdieslib里面明明白白的寫出了密鑰,用c語言寫的結構也更清晰。
反正兩個對著看,大概是看明白了密鑰長啥樣,至于封包結構,暫時還不要去理這個。
找一個看看
現在我們已經看到了,密鑰是一張表,現在的問題是怎么找到這張表。很顯然,我們先找一個Garbro或exdieslib里有密鑰的游戲,我們先對著這個游戲找出一樣的密鑰即可,這個時候隨便亂找也行,沒準你亂搜還真能搜到。當然這邊我就不去亂搜了,我稍微看了一下exdieslib和Garbro的解析,都會先去解析封包開始的一些字節,來判斷封包的標頭,是不是LIB字段,以此來確定目前使用的密鑰是能夠解密游戲的,然后用這個能解析出正確標頭的密鑰進行文件索引和數據內容的解密。
依據上述的特征,我準備開始下斷點看文件頭了,結果發現這個社好幾個游戲都有Playdrm加殼,雖然這個加殼并沒什么用,但是為了簡化分析還是找了沒殼又有現成密鑰的。
最后我選擇了 神咒神威神楽 超先行 體験版 是的體驗版一般都沒亂七八糟的加殼,比較好分析(按Tab)
https://dlsoft.dmm.co.jp/detail/views_0363/
本來想先斷一下CreateFileA/W的,結果發現斷下來都是沒什么用的信息,那換成ReadFile,發現它第一次ReadFile就讀取了0x1000的大小,并且剛好是文件頭上還是的0x1000個字節,只能說來得正好,但是不確定的是為什么要讀0x1000,而且每次都讀0x1000,然后翻了下導入表,原來是有msvcrt,有c的庫函數fopen和fread這些,現在換成斷fopen,fread,發現正常了,第一次fread就打開了指定的封包,fread則是每次讀取0x10個字節。
翻了下上面兩個項目的源碼,發現這東西是按塊解密的,就是文件分成了很多十六個字節的塊,一個個解密過去。看著可能是更這個有關,算了,別理他為什么,先給讀到內存里的文件頭上的第一個字節和第四個字節下硬件斷點。

按F9運行,斷下。

發現直接就對比文件標頭了。這個時候IDA也一起打開來輔助分析。

按x查找引用返回上一層函數。發現這個函數,是用來打開文件的,后面會調用fopen,打開后返回的指針存儲著打開文件的信息,像是句柄啥的。第二個參數模式,有LFIE_I,還有CFI這種,第一個是文件的相對路徑。接著把獲得的指針傳給了另一個函數,這個函數則是調用fread從封包讀取數據到buffer里。

大概整理一下,是這樣的

第一次讀取并校驗的時候是不成功的,所以進入了第二次讀取。第二次比較奇怪的地方是GetBlockHandle的模式變成了CFI,這次GetBlock讀取進來的的封包前0x10個字節居然是已經解密了,直接就能看到LIB字段。由于GetBlock會調用fread,觀察第二次讀取的fread,發現依舊是0x10個字節,而是也是文件開頭的0x10個字節,與第一次讀取一樣,說明這個GetBlock里面對文件標頭進行了解密。
我們先對這次讀進來的數據下硬件斷點,F9運行。

發現fread剛剛返回就對讀取進來的數據進行了位移操作,僅接著,在sub_4247F0函數運行后,觀察這個函數的參數,發現數據已經解密了。繼續觀察這個函數,發現第二個參數就是密鑰,雖然順序有點不一樣。

寫一個程序把密鑰順序搞一下
#include <iostream>
int main(int argc, char* argv[])
{
if (argc > 1)
{
unsigned long aKey[56] = { 0 };
FILE* fp = nullptr;
errno_t err = fopen_s(&fp, argv[1], "rb");
if (err || !fp) return 0;
fread(aKey, 1, 0xE0, fp);
for (size_t iteLine = 0; iteLine < 14; iteLine++)
{
for (size_t iteEle = 0; iteEle < 4; iteEle++)
{
printf("0x%08X", aKey[(13 - iteLine) * 4 + iteEle]);
if (!(iteLine == 13 && iteEle == 3)) putchar(',');
}
putchar('\n');
}
}
else
{
printf("KeyFormat.exe key.bin");
}
system("pause");
}
Keyformat.cpp

由于我這個是按照exdieslib來的,所以要往上翻4*4個字節,這個上面16個字節一般都是0。
好了現在密鑰就有了,不過還不清楚這個密鑰表是怎么來的,直接搜索也找不到這個表,于是又跟蹤了一下。
發現游戲自己搞了一個讀取和打開封包的玩意,就是封裝這兩個函數。那個上面GetBlockHandle的MODE像是fopen這種的擴展,游戲還有自己的MODE,上面CFI,FILE什么的。在第二次GetBlock跟蹤走了下圖這個函數。,這個函數有個奇怪的參數,寫死的,而是像是一段密碼一樣的字符串。

繼續跟蹤會發現這個參數進入了一個函數,類似之前的解密函數,第一個參數也是0x80

執行完畢后,第三個參數指向的buffer就變成了密鑰的那張表了。

看來真正的密鑰其實是:XogRr2FjLW0waAuW,經過這個函數運算就成了Garbo和exdieslib里的那個表。
加殼怎么找?
其實沒啥關系,因為很多所謂的加殼,都沒有虛擬化代碼,也就是代碼其實運行后都原封不動吐出來了,只是會加密IAT之類的。
不過因為加密了IAT我們就不能從導入表中直接對fread,fopen下斷點了,這有關系嗎?沒有,完全沒有,因為這兩個函數也不是在exe里實現的。不過值得注意的是,有些殼會把fread和fopen重新定向到殼子內部,繞一圈,調用CreateFileW,fread則是調用ReadFile,或者有些程序干脆就把庫函數一起編譯進去了,這個時候我們只能從CreateFile來定位回去。
這邊以 Deep Love Diary -戀人日記- パッケージ版 為例子展示一個在Playdrm加殼的情況下找密鑰,當然前提你先破解,這個不多說。
為了節省篇幅就直說了,fread和fopen是斷不下來的,我們直接斷CreateFileW,(注意下,如果有反調試,記得裝scyllahide)
看到有 0155EDF0 01996968 L".\data\data9.dat" 這種就說明游戲已經開始打開封包了。這個時候直接點返回用戶區代碼

然后在棧里往下翻,因為fopen一般都有一個mode參數,rb wb 什么的,往下翻就能看到,有好幾個,可以到壓入的返回地址,就是那個紅色的,地址處看調用的地方,然后你可以看到這么個結構的代碼。

對的,就是這里,這個引擎調用fopen的時候會進行寬字節到窄字節的轉換。
那么00683B02 | E8 C6020100 | call malie3.693DCD | 其實就是fopen了。
我們在add esp 8下斷點,等他返回。因為游戲沒data9這個封包,返回值也是0,沒關系,游戲會多次調用fopen,直到找到封包。
我們按F7單步返回上一層函數,返回到第二層的時候可以看到游戲的兩個讀取模式了LFILE,CFI,并且call 0x00687B60這個函數其實就是對比文件標頭的,可以點進去看,這個位置其實就是我們剛剛的InitHeader那個函數一樣的地方。

這個時候就很好辦了吧。只要知道GetBlock這個函數就行了,不過這里運行后這個函數就退出了,因為data9這個封包沒有,所有GetBlockHandle這個函數會失敗,等讀到data.dat封包的時候就好了。這個時候可以配合斷下ReadFile,并在讀取的數據上下硬件斷點。和之前一樣就能跟蹤到解密的地方。
當然你說有沒更快的方法呢?當然有了,還記得上面那個參數寫死,也就是生成密鑰表的函數嗎?因為那個函數會對比模式,也就是rb wb r+b這種,所有我們直接運行游戲,等游戲跑起來,我們直接來到游戲這個模塊,右鍵搜索該模塊全部字符串。注意左上角的Module:malie3.exe你別搜到別的模塊去了。直接搜索w+b

可以看到只有兩個結果,找到這個push的。

是不是看到了熟悉的push 80,如果你返回這個函數調用上一層,或者看函數傳進來的參數,你就能看到用于生成密鑰表的密文

第一部分結束
說了一堆 戀音セ?ピアーチェ 這個游戲都還沒開始解,我知道你很急,但是你先別急。
先講到這,有點長了,有些新的Malie引擎的游戲會出現直接沒有那個密文生成密鑰的函數的情況。也就沒辦法直接獲取密鑰表了,這個下一節講 :)

浙公網安備 33010602011771號