[YU-RIS] 中文路徑無法運行
[YU-RIS] 中文路徑無法運行
0x00 背景
今天發現個游戲 鯨神のティアスティラ
一般來說yuris的游戲,雙擊是可以打開的,即使沒轉區,頂多也就亂碼嘛,但是這游戲還確定有點奇葩,雙擊打不開,轉區可以打開,然后把路徑全部換成英文也可以打開。這種情況BGI之前遇到過,BGI那個是MultiByteToWideChar的問題。
這種打不開的情況是連窗口都出不來的,其實更多的游戲不轉區打不開的情況通常是腳本和文件讀取的編碼問題,就是游戲可以跑起來,但是會黑屏之類的。
0x01 觀察
我們用x64dbg運行一下游戲看看,發現游戲自己退出了。

查看log窗口也沒發現什么異常,估計是調用了ExitProcess()函數來結束程序的

給 ExitProcess()函數下個斷點,重新跑一下,發現確實斷下來了。為了方便跟蹤退出的原因,我們先用IDA分析一下游戲執行文件。
我們先來到看看斷下來的地方,找到壓入棧里的返回地址(也就是來到調用 ExitProcess()函數的地方)也就是找到 call dword ptr ds:[&ExitProcess],復制一下地址,在IDA里按G轉到相應的地方,然后反編譯成C語言

可以發現這個是在doexit這個函數里執行的,對doexit按x來查看誰調用了這個函數,有兩個結果,然后用x64dbg對他們下斷點,看看是誰調用的,重復該動作(注意觀察傳入函數的值),ExitProcess(uExitCode);反編譯后有IDA可以看到這個,uExitCode這個我們已經從x64dbg里可以看到是0,所以這也是一個關鍵是線索,往回追的時候要注意看這個變量什么時候變成了0。
v5 = WinMain(ModuleHandleA, 0, lpCmdLine, v3);
exit(v5);
最終會來到這個地方,可以發現0其實是v5,也就是WinMain的返回值。
現在進到WinMain里看看誰Return了0,配合x64dbg方便定位。
hMutex = CreateMutexA(0, 0, lpMutexName);
if ( hMutex )
{
if ( GetLastError() == 183 )
{
CloseHandle(hMutex);
v6 = 1;
}
else
{
v6 = 0;
}
}
else
{
v6 = -1;
}
if ( v6 )
{
sub_41194C();
sub_411CFC();
return 0;
}
最終可以發現,其實是WinMain里第二次調用CreateMutexA的時候出了問題,導致了程序退出。
0x02 理解
CreateMutexA是什么呢?你搜一下可以發現這個是創建互斥體的,哎,不懂也沒關系,其實你也不要管他干啥的,可以先看看它的返回值,判斷一下是什么問題導致這個函數執行失敗。
在x64dbg里調試觀察該函數執行完畢收的eax的值,發現是0
If the function succeeds, the return value is a handle to the newly created mutex object.
If the function fails, the return value is NULL. To get extended error information, call GetLastError.
MSDN里對返回值的描述是這樣的,叫我們調用GetLastError(),整好,下面就一個
00403C52 | 33C0 | xor eax,eax |
00403C54 | 68 C0606800 | push kujira.6860C0 |
00403C59 | 50 | push eax |
00403C5A | 50 | push eax |
00403C5B | FF15 78F25800 | call dword ptr ds:[<&CreateMutexA>] |
00403C61 | A3 B4CC8300 | mov dword ptr ds:[83CCB4],eax |
00403C66 | 85C0 | test eax,eax |
00403C68 | 0F84 7D020000 | je kujira.403EEB |
00403C6E | FF15 94F25800 | call dword ptr ds:[<&GetLastError>] |
00403C74 | 3D B7000000 | cmp eax,B7 |
00403C79 | 75 13 | jne kujira.403C8E |
00403C7B | FF35 B4CC8300 | push dword ptr ds:[83CCB4] |
00403C81 | FF15 7CF25800 | call dword ptr ds:[<&CloseHandle>] |
00403C87 | B8 01000000 | mov eax,1 |
00403C8C | EB 02 | jmp kujira.403C90 | |
但是eax=0的時候并不會調用,所以我們可以改一下標志寄存器,或者把eax改成1之類的。
EAX : 00000003
EBX : 002DE000
ECX : 00E50000
EDX : 00E50000
發現是3,可以用vs的tools欄里的error lookup查一下

看樣子是說的路徑問題,但是這函數的參數好像也沒路徑吧
HANDLE CreateMutexA(
[in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,
[in] BOOL bInitialOwner,
[in, optional] LPCSTR lpName
);
在x64dbg里看看 lpName這個東西是,發現看著還挺像路徑的,而且還是GBK編碼的
c::users:dir-a:desktop:鯨神のティアスティラ\kujira.exe
在MSDN看看這個參數的說明
The remainder of the name can contain any character except the backslash character (\)
他說這個參數里不能包含\這個字符,顯然這里可以看到,這玩意傳進來的參數里居然有\那肯定是不行了。我們先把這個反斜杠去掉看看,可以發現,這個函數成功執行過去了,返回值也是正常的,但是游戲還是退出了,后面應該還有問題。
0x03 思索
現在就有兩種思路了,繼續去跟蹤后面是什么問題導致了退出,或往前跟蹤看看是什么導致了這個問題,也就是第三個參數里有反斜杠。
這里我選擇了第二種思路,然后我試著轉區運行了,這是可以打開的,然后觀察剛剛的地方,這次第三個參數的編碼是SJIS的了,并且沒有反斜杠。猜測一下問題可能就出在SJIS和GBK的編碼問題上。
再到x64dbg看看CreateMutexA的第三個參數,可以看到這個直接push了一個具體的地址,應該是一個全局的變量,然后運行后把字符串寫了進來,我們直接對這個地址下硬件斷點,執行后就可以知道誰往這里寫了字符串了
00411880 | 46 | inc esi |
00411881 | 84D2 | test dl,dl |
00411883 | 75 F6 | jne kujira.41187B |
00411885 | BA C0606800 | mov edx,kujira.6860C0 |
0041188A | 0FBE02 | movsx eax,byte ptr ds:[edx] |
0041188D | 85C0 | test eax,eax |
0041188F | 74 27 | je kujira.4118B8 |
00411891 | 0FB6F0 | movzx esi,al |
00411894 | 0FB68E A0745B00 | movzx ecx,byte ptr ds:[esi+5B74A0] |
0041189B | 41 | inc ecx |
0041189C | 0FBEF9 | movsx edi,cl | edi:":\\users\\dir-a\\desktop\\鯨神のティアスティラ\\kujira.exe"
0041189F | 83FF 02 | cmp edi,2 | edi:":\\users\\dir-a\\desktop\\鯨神のティアスティラ\\kujira.exe"
004118A2 | 0F84 8E000000 | je kujira.411936 |
004118A8 | 83F8 5C | cmp eax,5C | 5C:'\\'
004118AB | 75 03 | jne kujira.4118B0 |
004118AD | C602 3A | mov byte ptr ds:[edx],3A | 3A:':'
004118B0 | 42 | inc edx |
可以發現是這個地方,可以看到有5C 3A的常數,看著像是把反斜杠替換成冒號,對這個地方進行跟蹤后發現,確實是這樣,但是有個地方的反斜杠沒換到,具體原因是這里
movzx ecx, byte ptr ds:[esi+0x5B74A0]
我們直接上IDA反編譯的代碼吧(我稍微對變量命名了一下)
strcpy(lpMutexName, FullPath);
iteString_ = lpMutexName;
for ( iChar = lpMutexName[0]; *iteString_; iChar = *iteString_ )
{
if ( (SJIS_Range_Table[iChar] + 1) == 2 )
{
iteString_ += 2;
}
else
{
if ( iChar == 0x5C )
*iteString_ = 0x3A;
++iteString_;
}
}
0x5B74A0 這個地址其實是SJIS_Range_Table,也就是yuris常見的那個SJIS范圍檢查的表,沒想到這玩意居然會拿來檢查路徑,這個路徑是這樣獲取的
GetModuleFileNameA(0, FullPath, 0x105u);
獲取的路徑放到FullPath里,然后通過strcpy拷貝到lpMutexName里,拷貝完成后對這個路徑進行檢查,并把反斜杠替換成冒號,但是這東西并不是機械的掃描過去發現5C就干成3A的,如果路徑里有日文字符的話,SJIS編碼的第二位也有可能是5C,所以這玩意用SJIS的范圍校驗表來檢查字符串里的日文字符,并跳過。
但是在中文區域的系統下,GetModuleFileNameA()獲取的是GBK編碼的字符,這就導致了它在檢查字符的時候出現了問題,把一些GBK編碼的字符當成了ASCII的單字節字符,而有些當成了雙字節字符,恰巧就出現了一個地方,前面的字節在SJIS內,后面剛好是0x5C,iteString_ += 2; 直接跳過了。如果全部范圍都不在SJIS里,其實也是可以的,就是機械的把這段字符串全部3A換成5C,至少CreateMutexA()這個函數執行不會出問題。
0x04 解決
好了,現在就很好辦了,直接把SJIS的這個范圍檢查表個改成GBK的就行了,這個以前說過了,這里就不多說了。

浙公網安備 33010602011771號