[Bug 16365] Archlord Episode 3 Client crashes on startup (decrypting files with a RC4 session key derived from MD5 hash fails, only 40 bits are used, salt is dropped)

wine-bugs at winehq.org wine-bugs at winehq.org
Sun Nov 10 07:08:26 CST 2013


http://bugs.winehq.org/show_bug.cgi?id=16365

Anastasius Focht <focht at gmx.net> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                URL|                            |http://www.gamefront.com/fi
                   |                            |les/13750760/
                 CC|                            |focht at gmx.net
          Component|-unknown                    |rsaenh
            Summary|Archlord crashes with       |Archlord Episode 3 Client
                   |err:dbghelp:SymCleanup this |crashes on startup
                   |process has not had         |(decrypting files with a
                   |SymInitialize() called for  |RC4 session key derived
                   |it                          |from MD5 hash fails, only
                   |                            |40 bits are used, salt is
                   |                            |dropped)

--- Comment #14 from Anastasius Focht <focht at gmx.net> 2013-11-10 07:08:26 CST ---
Hello folks,

confirming.

It seems the game has several encrypted files and Wine doesn't properly decrypt
them, leading to failure.

Example: "ini\\sysstr.txt"

Map the file into memory, allocate decrypt buffer, copy file content:

--- snip ---
$ pwd
/home/focht/.wine/drive_c/Program Files/Codemasters/Archlord
...
$ WINEDEBUG=+tid,+seh,+relay wine ./alefclient.exe >>log.txt 2>&1
...
0025:Starting process L"C:\\Program
Files\\Codemasters\\Archlord\\alefclient.exe" (entryproc=0x75a94a) 
...
0025:Call KERNEL32.CreateFileA(007ee634
"ini\\sysstr.txt",80000000,00000000,00000000,00000003,00000080,00000000)
ret=004ea20d
0025:Ret  KERNEL32.CreateFileA() retval=000000ac ret=004ea20d
0025:Call
KERNEL32.CreateFileMappingA(000000ac,00000000,00000002,00000000,00000000,00000000)
ret=004ea225
0025:Ret  KERNEL32.CreateFileMappingA() retval=000000b0 ret=004ea225
0025:Call KERNEL32.MapViewOfFile(000000b0,00000004,00000000,00000000,00000000)
ret=004ea248
0025:Ret  KERNEL32.MapViewOfFile() retval=05a30000 ret=004ea248
0025:Call KERNEL32.GetFileSize(000000ac,00000000) ret=004ea25a
0025:Ret  KERNEL32.GetFileSize() retval=0000401b ret=004ea25a 
...
0025:Call ntdll.RtlAllocateHeap(00110000,00000000,0000401c) ret=7e25f194
0025:Ret  ntdll.RtlAllocateHeap() retval=053be168 ret=7e25f194
0025:Ret  msvcrt.??2 at YAPAXI@Z() retval=053be168 ret=004e1be6
0025:Call msvcrt.memcpy(053be168,05a30000,0000401b) ret=004e1bf0
0025:Ret  msvcrt.memcpy() retval=053be168 ret=004e1bf0
--- snip ---

Decrypting the content with session key (derived from MD5 hash + 4 char
password "1111"):

--- snip ---
...
0025:Call advapi32.CryptSetProviderA(008038cc "Microsoft Base Cryptographic
Provider v1.0",00000001) ret=004e9781
0025:Ret  advapi32.CryptSetProviderA() retval=00000000 ret=004e9781
0025:Call advapi32.CryptAcquireContextA(0033f294,00000000,008038cc "Microsoft
Base Cryptographic Provider v1.0",00000001,f0000000) ret=004e9796
0025:Call rsaenh.CPAcquireContext(053c22f0,00000000,f0000000,053c2370)
ret=7e93e7eb 
...
0025:Ret  rsaenh.CPAcquireContext() retval=00000001 ret=7e93e7eb
0025:Ret  advapi32.CryptAcquireContextA() retval=00000001 ret=004e9796
...
0025:Call
advapi32.CryptCreateHash(053c22e0,00008003,00000000,00000000,0033f278)
ret=004e98cd
0025:Call rsaenh.CPCreateHash(00000002,00008003,00000000,00000000,053c2198)
ret=7e93efb8
0025:Call ntdll.RtlAllocateHeap(00110000,00000000,00000170) ret=7cc507a6
0025:Ret  ntdll.RtlAllocateHeap() retval=053c2608 ret=7cc507a6
0025:Call advapi32.MD5Init(053c2628) ret=7cc508c8
0025:Ret  advapi32.MD5Init() retval=053c2628 ret=7cc508c8
0025:Ret  rsaenh.CPCreateHash() retval=00000001 ret=7e93efb8
0025:Ret  advapi32.CryptCreateHash() retval=00000001 ret=004e98cd
...
0025:Call advapi32.CryptHashData(053c2190,00802d18,00000004,00000000)
ret=004e98fe
0025:Call rsaenh.CPHashData(00000002,00000004,00802d18,00000004,00000000)
ret=7e9410c8
0025:Call advapi32.MD5Update(053c2628,00802d18,00000004) ret=7cc509d4
0025:Ret  advapi32.MD5Update() retval=053c2640 ret=7cc509d4
0025:Ret  rsaenh.CPHashData() retval=00000001 ret=7e9410c8
0025:Ret  advapi32.CryptHashData() retval=00000001 ret=004e98fe 
...
0025:Call advapi32.CryptDeriveKey(053c22e0,00006801,053c2190,00000004,0033f27c)
ret=004e991d
0025:Call rsaenh.CPDeriveKey(00000002,00006801,00000004,00000004,053c21b0)
ret=7e93f2d3
0025:Call ntdll.RtlAllocateHeap(00110000,00000000,000003e4) ret=7cc507a6
0025:Ret  ntdll.RtlAllocateHeap() retval=053c2780 ret=7cc507a6
0025:Call advapi32.MD5Final(053c2628) ret=7cc50b2e
0025:Ret  advapi32.MD5Final() retval=053c2680 ret=7cc50b2e
0025:Ret  rsaenh.CPDeriveKey() retval=00000001 ret=7e93f2d3 
...
0025:Call advapi32.CryptDestroyHash(053c2190) ret=004e992c
0025:Call rsaenh.CPDestroyHash(00000002,00000004) ret=7e93f412
...
0025:Ret  rsaenh.CPDestroyHash() retval=00000001 ret=7e93f412
0025:Ret  advapi32.CryptDestroyHash() retval=00000001 ret=004e992c 
...
0025:Call
advapi32.CryptDecrypt(053c21a8,00000000,00000000,00000000,053be168,0033f28c)
ret=004e994f
0025:Call
rsaenh.CPDecrypt(00000002,00000003,00000000,00000000,00000000,053be168,0033f28c)
ret=7e93f14b
0025:Ret  rsaenh.CPDecrypt() retval=00000001 ret=7e93f14b
0025:Ret  advapi32.CryptDecrypt() retval=00000001 ret=004e994f 
...
0025:Call advapi32.CryptDestroyKey(053c21a8) ret=004e9962
0025:Call rsaenh.CPDestroyKey(00000002,00000003) ret=7e93f534
...
0025:Ret  rsaenh.CPDestroyKey() retval=00000001 ret=7e93f534 
0025:Ret  advapi32.CryptDestroyKey() retval=00000001 ret=004e9962
...
0025:Call rsaenh.CPReleaseContext(00000002,00000000) ret=7e93ecc6
...
0025:Ret  advapi32.CryptReleaseContext() retval=00000001 ret=004e998f 
--- snip ---

After decryption the code tries to parse "key=value" pair strings on the
buffer:

--- snip ---
...
0025:Call ntdll.RtlAllocateHeap(00110000,00000000,00001000) ret=7e25f194
0025:Ret  ntdll.RtlAllocateHeap() retval=053c2190 ret=7e25f194
0025:Ret  msvcrt.??2 at YAPAXI@Z() retval=053c2190 ret=004e1c1e
0025:Call msvcrt.memset(053c2190,00000000,00001000) ret=004e1c32
0025:Ret  msvcrt.memset() retval=053c2190 ret=004e1c32
0025:Call msvcrt.memcpy(053c2190,053be168,00000013) ret=004ea127
0025:Ret  msvcrt.memcpy() retval=053c2190 ret=004ea127
0025:Call ntdll.strcspn(053c2190
"\x8a\r|\xe6\x03\x07<\xe5n7\x03Gb\xc0\xe3\xa4\xfaR",00802f9c "=") ret=004e1c5c
0025:Ret  ntdll.strcspn() retval=00000012 ret=004e1c5c
0025:Call ntdll.strcspn(053c2190
"\x8a\r|\xe6\x03\x07<\xe5n7\x03Gb\xc0\xe3\xa4\xfaR",00802f9c "=") ret=004e1ccd
0025:Ret  ntdll.strcspn() retval=00000012 ret=004e1ccd
0025:Call msvcrt.??2 at YAPAXI@Z(00000000) ret=004e1cf8
0025:Call ntdll.RtlAllocateHeap(00110000,00000000,00000000) ret=7e25f194
0025:Ret  ntdll.RtlAllocateHeap() retval=053c3198 ret=7e25f194
0025:Ret  msvcrt.??2 at YAPAXI@Z() retval=053c3198 ret=004e1cf8
0025:Call msvcrt.memcpy(053c3198,053c21a3,ffffffff) ret=004e1d04
0025:trace:seh:raise_exception code=c0000005 flags=0 addr=0xf74f33f1
ip=f74f33f1 tid=0025
0025:trace:seh:raise_exception  info[0]=00000001
0025:trace:seh:raise_exception  info[1]=053d0000
0025:trace:seh:raise_exception  eax=053cf03b ebx=f7562000 ecx=ffff30e7
edx=053cffb0 esi=0033f2bc edi=0033f28c
0025:trace:seh:raise_exception  ebp=0033f278 esp=0033f258 cs=0023 ds=002b
es=002b fs=0063 gs=006b flags=00010286 
...
--- snip ---

This obviously fails because the buffer content was incorrectly decrypted ->
garbage strings to strcspn().

Dumping 40-bit session key:

--- snip ---
0x0553d87c:  bf679cb5 00000019 00000000 00000000
0x0553d88c:  00000000 00000000 00000000 00000000
0x0553d89c:  00000000 00000000 00000000 00000000
--- snip ---

128-bit hash -> 88 bits leftover, salt value retrieved by CryptGetKeyParam(
KP_SALT)

--- snip ---
Wine-dbg>bt
Backtrace:
=>0 0x7e944600 MD5Final+0x161(ctx=0x553d6e8)
[/home/focht/projects/wine/wine-git/dlls/advapi32/crypt_md5.c:179] in advapi32
(0x0033ee58)
  1 0x7d862b2e finalize_hash_impl+0xc1(aiAlgid=0x8003, pHashContext=0x553d6e8,
pbHashValue="") [/home/focht/projects/wine/wine-git/dlls/rsaenh/implglue.c:142]
in rsaenh (0x0033ee88)
  2 0x7d86d3be finalize_hash+0x1c6(pCryptHash=0x553d6c8)
[/home/focht/projects/wine/wine-git/dlls/rsaenh/rsaenh.c:721] in rsaenh
(0x0033ef28)
  3 0x7d872820 RSAENH_CPGetHashParam+0x248(hProv=0x2, hHash=0x4, dwParam=0x2,
pbData="`╥SÉ∙ê}    `╥Sα≡3", pdwDataLen=0x33f0a8, dwFlags=0)
[/home/focht/projects/wine/wine-git/dlls/rsaenh/rsaenh.c:3341] in rsaenh
(0x0033ef88)
...
Wine-dbg>p *ctx
{i={0x20, 0}, buf={0xbf679cb5, 0x58476a19, 0xf7421e19, 0xbace7066}, in="???",
digest="????????"}
...
--- snip ---

-> final session key

--- snip ---
Wine-dbg>x/10x pbHashValue
0x0553d7b8:  bf679cb5 58476a19 f7421e19 bace7066
0x0553d7c8:  00000000 00000000 00000000 00000000
0x0553d7d8:  00000000 00000000
...
--- snip ---

When I dumped the key in RSAENH_CPDeriveKey() -> setup_key() ->
setup_key_impl() -> rc4_ready() I got a surprise ...

Only 40-bit were actually used!

--- snip ---
0x0553d87c:  bf679cb5 00000019 00000000 00000000
0x0553d88c:  00000000 00000000 00000000 00000000
0x0553d89c:  00000000 00000000 00000000 00000000
--- snip ---

 /* make RC4 perm and shuffle */
    for (x = 0; x < 256; x++) {
        s[x] = x;
    } 

--- snip ---
0x0553d87c:  03020100 07060504 0b0a0908 0f0e0d0c
0x0553d88c:  13121110 17161514 1b1a1918 1f1e1d1c
0x0553d89c:  23222120 27262524 2b2a2928 2f2e2d2c
0x0553d8ac:  33323130 37363534 3b3a3938 3f3e3d3c
--- snip ---

    for (j = x = y = 0; x < 256; x++) {
        y = (y + prng->rc4.buf[x] + key[j++]) & 255;
        if (j == keylen) {
           j = 0; 
        }
        tmp = s[x]; s[x] = s[y]; s[y] = tmp;
    }  

Result:

--- snip ---
0x0553d87c:  7dbb52b5 15a59f9a d2c7bd0f b4f9ebde
0x0553d88c:  c5f37acd 341dacf2 047f654c 1f1e161c
...
--- snip ---

Source:
http://source.winehq.org/git/wine.git/blob/bfd2c533beef454a61b24f92790f91099e607365:/dlls/rsaenh/rsaenh.c#l3896

--- snip ---
BOOL WINAPI RSAENH_CPDeriveKey(HCRYPTPROV hProv, ALG_ID Algid, HCRYPTHASH
hBaseData, 
                               DWORD dwFlags, HCRYPTKEY *phKey)
{
    CRYPTKEY *pCryptKey, *pMasterKey;
    CRYPTHASH *pCryptHash;
    BYTE abHashValue[RSAENH_MAX_HASH_SIZE*2];
    DWORD dwLen;
...
        case ALG_CLASS_DATA_ENCRYPT:
            *phKey = new_key(hProv, Algid, dwFlags, &pCryptKey);
            if (*phKey == (HCRYPTKEY)INVALID_HANDLE_VALUE) return FALSE;

            /* 
             * We derive the key material from the hash.
             * If the hash value is not large enough for the claimed key, we
have to construct
             * a larger binary value based on the hash. This is documented in
MSDN: CryptDeriveKey.
             */
            dwLen = RSAENH_MAX_HASH_SIZE;
            RSAENH_CPGetHashParam(pCryptHash->hProv, hBaseData, HP_HASHVAL,
abHashValue, &dwLen, 0);

            if (dwLen < pCryptKey->dwKeyLen) { 
...
            }

            memcpy(pCryptKey->abKeyValue, abHashValue, 
                   RSAENH_MIN(dwLen, sizeof(pCryptKey->abKeyValue)));
            break;
...
        default:
            SetLastError(NTE_BAD_ALGID);
            return FALSE;
    }

    setup_key(pCryptKey);
    return TRUE;    
}
--- snip ---

The culprit:
http://source.winehq.org/git/wine.git/blob/bfd2c533beef454a61b24f92790f91099e607365:/dlls/rsaenh/rsaenh.c#l3966

--- snip ---
3966   memcpy(pCryptKey->abKeyValue, abHashValue,
3967     RSAENH_MIN(pCryptKey->dwKeyLen, sizeof(pCryptKey->abKeyValue)));
--- snip ---

IMHO "pCryptKey->dwKeyLen" should be "dwLen"

With that part fixed all files get properly decrypted and the client/launcher
actually starts.

Although it seems this game version is abandoned (domain/website for sale) it
still proves to be a useful test case for Wine ;-)

$ sha1sum ArchlordEpisode3.exe 
52b81c9148536c504fe3d697ddf808d80b2e76ed  ArchlordEpisode3.exe

$ du -sh ArchlordEpisode3.exe 
1.5G    ArchlordEpisode3.exe

$ wine --version
wine-1.7.6-109-g917d303

Regards

-- 
Configure bugmail: http://bugs.winehq.org/userprefs.cgi?tab=email
Do not reply to this email, post in Bugzilla using the
above URL to reply.
------- You are receiving this mail because: -------
You are watching all bug changes.


More information about the wine-bugs mailing list