Reversing SoulMaster NPK encryption algorithm
It will be guide with explanation how to find encryption algorithm that used for unpacking .npk resource files of SoulMaster.
You have already setup/tuned Ollydbg and run SoulMaster under debugger.
Analyze main module “smc.exe”, call “find all referenced strings” and find strings with “.npk”. Choose first entry (it should be “acid_effect.npk”) and proceed to CPU.
We get npk loading algorithm.
0075DB81 |. 68 B8000000 push 0xB8
=> 0075DB86 |. C74424 1C C51>mov dword ptr [esp+0x1C], 0x18C5
=> 0075DB8E |. C74424 20 5A0>mov dword ptr [esp+0x20], 0xD5A
=> 0075DB96 |. C74424 24 940>mov dword ptr [esp+0x24], 0x594
=> 0075DB9E |. C74424 28 941>mov dword ptr [esp+0x28], 0x1294
0075DBA6 |. E8 37322100 call 00970DE2
0075DBAB |. 8BD8 mov ebx, eax
0075DBAD |. 83C4 04 add esp, 0x4
0075DBB0 |. 895C24 14 mov dword ptr [esp+0x14], ebx
...... (skipped)
0075DBE9 |. 90 nop
0075DBEA |. 8BC8 mov ecx, eax
0075DBEC |. 90 nop
0075DBED |. E8 EE711304 call 04894DE0
0075DBF2 |. 8B1D C0539C00 mov ebx, dword ptr [0x9C53C0]
=> 0075DBF8 |. 68 70E8A300 push 00A3E870 ; ASCII "acid_effect.npk"
0075DBFD |. 50 push eax
0075DBFE |. 8D5424 30 lea edx, dword ptr [esp+0x30]
0075DC02 |. 52 push edx
0075DC03 |. C78424 B00000>mov dword ptr [esp+0xB0], 0xA
0075DC0E |. FFD3 call ebx
0075DC10 |. 83C4 0C add esp, 0xC
0075DC13 |. 894424 14 mov dword ptr [esp+0x14], eax
0075DC17 |. 8B4E 0C mov ecx, dword ptr [esi+0xC]
0075DC1A |. 8B01 mov eax, dword ptr [ecx]
0075DC1C |. 8B40 18 mov eax, dword ptr [eax+0x18]
0075DC1F |. 8D5424 18 lea edx, dword ptr [esp+0x18]
0075DC23 |. 52 push edx
0075DC24 |. 8B5424 18 mov edx, dword ptr [esp+0x18]
0075DC28 |. 52 push edx
0075DC29 |. C68424 AC0000>mov byte ptr [esp+0xAC], 0xB
=> 0075DC31 |. FFD0 call eax ; Calling NPK loader
0075DC33 |. 8D4C24 28 lea ecx, dword ptr [esp+0x28]
0075DC37 |. C68424 A40000>mov byte ptr [esp+0xA4], 0xA
0075DC3F |. 90 nop
0075DC40 |. E8 5F82D277 call 78485EA4
0075DC45 |. 8D4C24 48 lea ecx, dword ptr [esp+0x48]
0075DC49 |. 89AC24 A40000>mov dword ptr [esp+0xA4], ebp
0075DC50 |. 90 nop
0075DC51 |. E8 4E82D277 call 78485EA4
Pay attention to first 4 instructions:
- mov dword ptr [esp+0x1C], 0x18C5
mov dword ptr [esp+0x20], 0xD5A
mov dword ptr [esp+0x24], 0x594
mov dword ptr [esp+0x28], 0x1294
You can scroll a little bit down and notice, that the same instructions with different values always appear before each .npk.
Ok, we set BP on 0075DBF8 and run app. When the app stops on BP step over until several steps 0075DC31. It’s a call for NPK loader/unpacker.
NPK loading algorithm
04901770 . 6A FF push -0x1
04901772 . 68 FA389C04 push 049C38FA
04901777 . 64:A1 0000000>mov eax, dword ptr fs:[0]
0490177D . 50 push eax
0490177E . 83EC 44 sub esp, 0x44
04901781 . A1 F80CB604 mov eax, dword ptr [0x4B60CF8]
04901786 . 33C4 xor eax, esp
...... (skipped)
0490183A . 50 push eax
0490183B . 68 A854A604 push 04A654A8 ; ASCII "GxNPackage::Create - Fail to find file %s"
04901840 . E8 4BDBF2FF call 0482F390
04901845 . 83C4 08 add esp, 0x8
04901848 . 85F6 test esi, esi
0490184A . 0F84 2D010000 je 0490197D
04901850 . 56 push esi
04901851 . E9 21010000 jmp 04901977
04901856 > 8B4C24 18 mov ecx, dword ptr [esp+0x18]
0490185A . 51 push ecx
0490185B . 8BCB mov ecx, ebx
0490185D . FF15 50739C04 call dword ptr [0x49C7350]
04901863 . 50 push eax
=> 04901864 . E8 E7FF0100 call 04921850 ; loading NPK, NPK checker, decryption
04901869 . 83C4 08 add esp, 0x8
0490186C . 8945 64 mov dword ptr [ebp+0x64], eax
0490186F . 85C0 test eax, eax
04901871 . 75 3B jnz short 049018AE
Loading NPK, NPK checker, decryption
04921850 /$ 81EC 30020000 sub esp, 0x230
04921856 |. A1 F80CB604 mov eax, dword ptr [0x4B60CF8]
0492185B |. 33C4 xor eax, esp
0492185D |. 898424 2C0200>mov dword ptr [esp+0x22C], eax
04921864 |. 53 push ebx
04921865 |. 8B9C24 3C0200>mov ebx, dword ptr [esp+0x23C]
0492186C |. 55 push ebp
0492186D |. 56 push esi
0492186E |. 57 push edi
0492186F |. 8BBC24 440200>mov edi, dword ptr [esp+0x244]
04921876 |. 6A 4C push 0x4C ; /size = 4C (76.)
04921878 |. 895C24 20 mov dword ptr [esp+0x20], ebx ; |
0492187C |. C74424 14 000>mov dword ptr [esp+0x14], 0x0 ; |
04921884 |. FF15 E4739C04 call dword ptr [0x49C73E4] ; malloc
...... (skipped)
049218E1 |. E8 2A0B0000 call 04922410
049218E6 |. 83C4 1C add esp, 0x1C
049218E9 |. 83F8 01 cmp eax, 0x1
049218EC |. 0F85 6E030000 jnz 04921C60
; reading first 4 bytes from file and checking the file header
049218F2 |. 6A 04 push 0x4
049218F4 |$ 68 ACF0A704 push 04A7F0AC ; |s2 = "NPK!"
049218F9 |. 55 push ebp ; |s1
049218FA |. FF15 94749C04 call dword ptr [0x49C7494] ; strncmp
04921900 |. 83C4 0C add esp, 0xC
04921903 |. 85C0 test eax, eax
04921905 |. 74 19 je short 04921920
04921907 |. 6A 04 push 0x4 ; /maxlen = 4
04921909 |. 68 A4F0A704 push 04A7F0A4 ; |s2 = "NPAK"
0492190E |. 55 push ebp ; |s1
0492190F |. FF15 94749C04 call dword ptr [0x49C7494] ; strncmp
04921915 |. 83C4 0C add esp, 0xC
04921918 |. 85C0 test eax, eax
0492191A |. 0F85 40030000 jnz 04921C60
04921920 |> 8B45 04 mov eax, dword ptr [ebp+0x4]
04921923 |. 83F8 15 cmp eax, 0x15
04921926 |. 7D 0A jge short 04921932
04921928 |. 68 01FFFFFF push -0xFF
0492192D |. E9 26030000 jmp 04921C58
04921932 |> 85DB test ebx, ebx
04921934 |. 75 07 jnz short 0492193D
04921936 |. 6A DF push -0x21
04921938 |. E9 1B030000 jmp 04921C58
0492193D |> 83F8 17 cmp eax, 0x17
; adding ciphers to the stack
04921940 |. 8B0B mov ecx, dword ptr [ebx]
04921942 |. 894D 18 mov dword ptr [ebp+0x18], ecx
04921945 |. 8B53 04 mov edx, dword ptr [ebx+0x4]
04921948 |. 8955 1C mov dword ptr [ebp+0x1C], edx
0492194B |. 8B4B 08 mov ecx, dword ptr [ebx+0x8]
0492194E |. 894D 20 mov dword ptr [ebp+0x20], ecx
04921951 |. 8B53 0C mov edx, dword ptr [ebx+0xC]
04921954 |. 8955 24 mov dword ptr [ebp+0x24], edx
04921957 |. 7C 2B jl short 04921984
...... (skipped)
049219EB |. 6A 01 push 0x1
049219ED |. 51 push ecx
049219EE |. 53 push ebx
049219EF |. 50 push eax
049219F0 |. 8B4424 34 mov eax, dword ptr [esp+0x34]
049219F4 |. 52 push edx
049219F5 |. 50 push eax
=> 049219F6 |. E8 F50A0000 call 049224F0
After that, app calculates the position of encrypted data in .npk and copy it to the buffer
Step into the: 049219F6 |. E8 F50A0000 call 049224F0
Inside, after another “step into”, we will stopped on
04922860 /$ 8B4424 08 mov eax, dword ptr [esp+0x8]
04922864 |. 8BC8 mov ecx, eax
04922866 |. 83E1 07 and ecx, 0x7
04922869 |. 56 push esi
0492286A |. 8B7424 08 mov esi, dword ptr [esp+0x8]
0492286E |. 2BC1 sub eax, ecx
04922870 |. 57 push edi
04922871 |. 8D3C30 lea edi, dword ptr [eax+esi]
04922874 |. 3BF7 cmp esi, edi
04922876 |. 73 1A jnb short 04922892
04922878 |. 53 push ebx
04922879 |. 8B5C24 18 mov ebx, dword ptr [esp+0x18]
0492287D |. 8D49 00 lea ecx, dword ptr [ecx]
=> 04922880 |> 53 /push ebx
=> 04922881 |. 56 |push esi
=> 04922882 |. E8 39FFFFFF |call 049227C0
=> 04922887 |. 83C6 08 |add esi, 0x8
=> 0492288A |. 83C4 08 |add esp, 0x8
=> 0492288D |. 3BF7 |cmp esi, edi
=> 0492288F |.^ 72 EF jb short 04922880
04922891 |. 5B pop ebx
04922892 |> 5F pop edi
04922893 |. 5E pop esi
04922894 . C3 retn
Here the cycle that passes to the decryption algorithm two dwords from the buffer.
In EDI stored the latest dword from the buffer (in my case for “acid_effect.npk“ it equal to EDI 06DA16D0 ASCII ".glua"). Ok, and this “call 049227C0” point to decryptor.
Now, C++ decryption code.
Function DecryptMemory – decryption of given memory block
Parameters:
Buffer – Pointer to a region with encrypted data.
BufSize – Size of the Buffer.
DecryptTable – Pointer to ciphers (4 DWORD's)
void __stdcall __declspec(naked) DecryptMemory(void *Buffer, size_t BufSize, void *DecryptTable)
{
__asm
{
l04942860: MOV EAX, [ESP+0x8]
l04942864: MOV ECX, EAX
l04942866: AND ECX, 0x7
l04942869: PUSH ESI
l0494286A: MOV ESI, [ESP+0x8]
l0494286E: SUB EAX, ECX
l04942870: PUSH EDI
l04942871: LEA EDI, [EAX+ESI]
l04942874: CMP ESI, EDI
l04942876: JNB l04942892
l04942878: PUSH EBX
l04942879: MOV EBX, [ESP+0x18]
l0494287D: LEA ECX, [ECX]
l04942880: PUSH EBX
l04942881: PUSH ESI
l04942882: CALL l049427C0
l04942887: ADD ESI, 0x8
l0494288A: ADD ESP, 0x8
l0494288D: CMP ESI, EDI
l0494288F: JB l04942880
l04942891: POP EBX
l04942892: POP EDI
l04942893: POP ESI
l04942894: RETN
l049427C0: SUB ESP, 0x10
l049427C3: MOV EAX, [ESP+0x14]
l049427C7: MOV ECX, [EAX]
l049427C9: MOV EAX, [EAX+0x4]
l049427CC: PUSH EBX
l049427CD: PUSH ESI
l049427CE: PUSH EDI
l049427CF: MOV EDI, [ESP+0x24]
l049427D3: MOV EBX, [EDI+0x8]
l049427D6: MOV [ESP+0x10], EBX
l049427DA: MOV EBX, [EDI+0xC]
l049427DD: MOV [ESP+0xC], EBX
l049427E1: MOV EBX, [EDI+0x4]
l049427E4: MOV EDI, [EDI]
l049427E6: MOV ESI, 0x20
l049427EB: MOV EDX, 0xC6EF3720
l049427F0: MOV [ESP+0x18], EBX
l049427F4: MOV [ESP+0x14], EDI
l049427F8: JMP l04942800
l049427FA: LEA EBX, [EBX]
l04942800: MOV EDI, ECX
l04942802: SHR EDI, 0x5
l04942805: ADD EDI, [ESP+0xC]
l04942809: MOV EBX, ECX
l0494280B: SHL EBX, 0x4
l0494280E: ADD EBX, [ESP+0x10]
l04942812: DEC ESI
l04942813: XOR EDI, EBX
l04942815: LEA EBX, [EDX+ECX]
l04942818: XOR EDI, EBX
l0494281A: SUB EAX, EDI
l0494281C: MOV EDI, EAX
l0494281E: SHL EDI, 0x4
l04942821: ADD EDI, [ESP+0x14]
l04942825: MOV EBX, EAX
l04942827: SHR EBX, 0x5
l0494282A: ADD EBX, [ESP+0x18]
l0494282E: XOR EDI, EBX
l04942830: LEA EBX, [EDX+EAX]
l04942833: XOR EDI, EBX
l04942835: SUB ECX, EDI
l04942837: ADD EDX, 0x61C88647
l0494283D: TEST ESI, ESI
l0494283F: JA l04942800
l04942841: MOV EDX, [ESP+0x20]
l04942845: POP EDI
l04942846: POP ESI
l04942847: MOV [EDX], ECX
l04942849: MOV [EDX+0x4], EAX
l0494284C: POP EBX
l0494284D: ADD ESP, 0x10
l04942850: RETN
}
}
Another one for Delphi
procedure DecryptMemory(Buffer: Pointer; BufSize: Cardinal; DecryptTable: Pointer); stdcall;
begin
asm
mov esp, ebp
pop ebp
@l04942860: MOV EAX, [ESP+$8]
@l04942864: MOV ECX, EAX
@l04942866: AND ECX, $7
@l04942869: PUSH ESI
@l0494286A: MOV ESI, [ESP+$8]
@l0494286E: SUB EAX, ECX
@l04942870: PUSH EDI
@l04942871: LEA EDI, [EAX+ESI]
@l04942874: CMP ESI, EDI
@l04942876: JNB @l04942892
@l04942878: PUSH EBX
@l04942879: MOV EBX, [ESP+$18]
@l0494287D: LEA ECX, [ECX]
@l04942880: PUSH EBX
@l04942881: PUSH ESI
@l04942882: CALL @l049427C0
@l04942887: ADD ESI, $8
@l0494288A: ADD ESP, $8
@l0494288D: CMP ESI, EDI
@l0494288F: JB @l04942880
@l04942891: POP EBX
@l04942892: POP EDI
@l04942893: POP ESI
@l04942894: RETN
@l049427C0: SUB ESP, $10
@l049427C3: MOV EAX, [ESP+$14]
@l049427C7: MOV ECX, [EAX]
@l049427C9: MOV EAX, [EAX+$4]
@l049427CC: PUSH EBX
@l049427CD: PUSH ESI
@l049427CE: PUSH EDI
@l049427CF: MOV EDI, [ESP+$24]
@l049427D3: MOV EBX, [EDI+$8]
@l049427D6: MOV [ESP+$10], EBX
@l049427DA: MOV EBX, [EDI+$C]
@l049427DD: MOV [ESP+$C], EBX
@l049427E1: MOV EBX, [EDI+$4]
@l049427E4: MOV EDI, [EDI]
@l049427E6: MOV ESI, $20
@l049427EB: MOV EDX, $C6EF3720
@l049427F0: MOV [ESP+$18], EBX
@l049427F4: MOV [ESP+$14], EDI
@l049427F8: JMP @l04942800
@l049427FA: LEA EBX, [EBX]
@l04942800: MOV EDI, ECX
@l04942802: SHR EDI, $5
@l04942805: ADD EDI, [ESP+$C]
@l04942809: MOV EBX, ECX
@l0494280B: SHL EBX, $4
@l0494280E: ADD EBX, [ESP+$10]
@l04942812: DEC ESI
@l04942813: XOR EDI, EBX
@l04942815: LEA EBX, [EDX+ECX]
@l04942818: XOR EDI, EBX
@l0494281A: SUB EAX, EDI
@l0494281C: MOV EDI, EAX
@l0494281E: SHL EDI, $4
@l04942821: ADD EDI, [ESP+$14]
@l04942825: MOV EBX, EAX
@l04942827: SHR EBX, $5
@l0494282A: ADD EBX, [ESP+$18]
@l0494282E: XOR EDI, EBX
@l04942830: LEA EBX, [EDX+EAX]
@l04942833: XOR EDI, EBX
@l04942835: SUB ECX, EDI
@l04942837: ADD EDX, $61C88647
@l0494283D: TEST ESI, ESI
@l0494283F: JA @l04942800
@l04942841: MOV EDX, [ESP+$20]
@l04942845: POP EDI
@l04942846: POP ESI
@l04942847: MOV [EDX], ECX
@l04942849: MOV [EDX+$4], EAX
@l0494284C: POP EBX
@l0494284D: ADD ESP, $10
@l04942850: RETN
end;
end;
”Decryption keys”
"resacid_effect.npk"
0x18C5
0xD5A
0x594
0x1294
"ressound.npk"
0x12FA
0x8D2
0x19FE
0x1CF7
"resui.npk"
0x16E9
0x11AE
0x19B8
0x11E6
"res_us_enui.npk"
0x11E9
0x148A
0x11D8
0x11E9
"resVData.npk"
0x16E7
0x158A
0x2149
0x1AC4
"resscript.npk"
0x10A2
0x1980
0x4E8
0x1980
"resposteffect.npk"
0x504
0x54E
0x12F4
0x1AC4
NPK structure was described here
Regards to Genz, who helps with reversing.
© Dwar & Genz
Feel free using our knowledge and guides, but please,
keep linkbacks to the original article