Registration Code (Part 1): w%kQ6
Registration Code (Part 2): b<#$1[*(cw~
In order to register on this forum, you must use the codes above. Combine them into one code (copy paste).

[Wolcen] How To Get PAK RSA Key

Have a release that does not fit elsewhere? Post it here!
Post Reply
User avatar
atom0s
Site Admin
Posts: 417
Joined: Sun Jan 04, 2015 11:23 pm
Location: 127.0.0.1
Contact:

[Wolcen] How To Get PAK RSA Key

Post by atom0s » Thu Jan 10, 2019 12:04 pm

How To Obtain Wolcen RSA PAK Key

Some tools you will want to have while doing this, this is what I use:
  • x64dbg
  • IDA (or similar disassembler)

Preface

The CryEngine platform allows you to encrypt and zip the content and data of your game using their in-house Zip engine that makes use of RSA encryption to attempt to prevent people from accessing the data files. While compiling the game, developers can alter the RSA key used for this process making the unpacking process unique to their game. This will be a short guide on how to find this key in Wolcen.


Understanding CryEngine

Before mindlessly poking about the game, it is best to get yourself familiar with the engine. CryEngine is open source therefore you can find information regarding it fairly easily. You can also find how the PAK system works entirely through the source code, where the RSA key is used and how its stored etc. Wolcen uses CryEngine 3, which is a bit more effort to obtain a full copy of the source code but it is out there.

First, we find the RSA key stored in the following file:
\GameSDK\GameDll\GameStartup.cpp

Which looks like this:

  1. static unsigned char g_rsa_public_key_data[] =
  2. {
  3.     0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  4.     0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  5.     0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  6.     0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  7.     0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  8.     0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  9.     0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
  10. };
  11.  
  12. const uint8* CGameStartup::GetRSAKey(uint32 *pKeySize) const
  13. {
  14.     *pKeySize = sizeof(g_rsa_public_key_data);
  15.     return g_rsa_public_key_data;
  16. }

So we know a call to GetRSAKey will return the data stored in this buffer. We know the buffer is just a static block of memory as well.

If we trace back further to where this is used, we can find the following in the code:
CryEngine\CrySystem\CryPak.cpp

  1. CCryPak::CCryPak(IMiniLog* pLog, PakVars* pPakVars, const bool bLvlRes, const IGameStartup *pGameStartup ):
  2. m_pLog (pLog),
  3. m_eRecordFileOpenList(RFOM_Disabled),
  4. m_pPakVars (pPakVars?pPakVars:&g_cvars.pakVars),
  5. m_fFileAcessTime(0.f),
  6. m_bLvlRes(bLvlRes),
  7. m_renderThreadId(0),
  8. m_pWidget(NULL)
  9. {
  10. #if defined(LINUX) || defined(APPLE)
  11.     m_HandleSource = 0;
  12. #endif
  13.     m_pITimer = gEnv->pTimer;
  14.  
  15.     m_bGameFolderWritable = true;
  16.     m_disableRuntimeFileAccess[0] = m_disableRuntimeFileAccess[1] = false;
  17.  
  18.     // Default game folder
  19.     m_strDataRoot = "game";
  20.     m_strDataRootWithSlash = m_strDataRoot + (char)g_cNativeSlash;
  21.  
  22.     m_pEngineStartupResourceList = new CResourceList;
  23.     m_pLevelResourceList = new CResourceList;
  24.     m_pNextLevelResourceList = new CNextLevelResourceList;
  25.     m_arrAliases.reserve(16);
  26.     m_arrArchives.reserve(64);
  27.  
  28. #if defined(XENON) || defined(PS3)
  29.     m_bInstalledToHDD = false;
  30. #else
  31.     m_bInstalledToHDD = true;
  32. #endif
  33.  
  34.     m_mainThreadId = GetCurrentThreadId();
  35.  
  36.     gEnv->pSystem->GetISystemEventDispatcher()->RegisterListener(this);
  37.  
  38. #ifdef INCLUDE_LIBTOMCRYPT
  39.     if(pGameStartup)
  40.     {
  41.         uint32 keyLen;
  42.         const uint8* pKeyData = pGameStartup->GetRSAKey(&keyLen);
  43.         if(pKeyData && keyLen > 0)
  44.         {
  45.             ZipEncrypt::Init(pKeyData, keyLen);
  46.         }
  47.     }
  48. #endif
  49. }

This is the important function to us. We want to find this function in the game while its starting up. Because of this preparing the ZipEncrypt system with the RSA key, this is what we want to find and set a breakpoint on in order to dump the key.

So for now we know the following about what we need:

- The code we want would be compiled inside of: CrySystem.dll (This is where CryPak.cpp is from.)
- The function we are looking for (CCryPak::CCryPak) has a string reference we can look for which is the word: "game"
- The function we are looking for (CCryPak::CCryPak) has a common API reference we can look for which is: GetCurrentThreadId

This is what we can use to find this function a bit easier. There is other data here we can use as well such as the .reserve calls using 16 and 64 as values, but for now the "game" string is plenty.


Finding The String Reference

Next, we will open up CrySystem.dll inside of IDA. You can find this dll in your Wolcen install folder such as:
<Path To Steam>\steamapps\common\Wolcen\win_x64\CrySystem.dll

Keep in mind this is a 64bit game so you need a 64bit disassembler or similar!

Once this DLL is fully loaded, we can then open the string references found within it. In IDA, you can do this via:
View -> Open Subviews -> Strings

Once this loads, use the search bar at the bottom (Ctrl+F to open it) and enter the word 'game' without quotes. You will get a lot of results, so sort the strings and find the lower-case version of the word game as its written in the code. You should find a reference like this:

  1. .rdata:0000000180628C74 00000005    C   game

Double-click this entry. Now you should see it in in the main IDA view. You should see the word "game" to the right and a named variable such as 'aGame' next to it to the left.

  1. .rdata:0000000180628C74 aGame           db 'game',0             ; DATA XREF: sub_180137010+2D0↑o
  2. .rdata:0000000180628C79                 align 20h

Here we will right-click the aGame variable and choose 'Jump to xref to operand'. In the new window there should be only 1 result. Double-click that and we are taken the function that references this. Once the function in raw assembly loads, I recommend switching to Text-View for easier to read code. To do that right-click in the grey areas of the window and choose 'Text View'. You should see something like this:

  1. .text:00000001801372D4                 call    sub_1800176AC
  2. .text:00000001801372D9                 mov     [r14+388h], rax
  3. .text:00000001801372E0                 lea     rdx, aGame      ; "game"
  4. .text:00000001801372E7                 mov     [r14+398h], rbp

If we scroll a little bit down we will also see the call to GetCurrentThreadId.

  1. .text:00000001801376AC                 mov     byte ptr [r14+208h], 1
  2. .text:00000001801376B4                 call    cs:__imp_GetCurrentThreadId
  3. .text:00000001801376BA                 mov     [r14+2ACh], eax

So now we can find the call that handled the key here. We know it was near the bottom of the function so we can assume where it is at with this information. Because it came after GetCurrentThreadId we know it will be below it. (Keep in mind depending on certain compilers this is not always true, optimizations can reorganize code that will alter the order they are in, so keep this in mind!)

Here is the last part of the function we will be working with:

  1. .text:00000001801376BA                 mov     [r14+2ACh], eax
  2. .text:00000001801376C1                 mov     rax, cs:qword_1807421F8
  3. .text:00000001801376C8                 mov     rcx, [rax+0C0h]
  4. .text:00000001801376CF                 mov     rax, [rcx]
  5. .text:00000001801376D2                 call    qword ptr [rax+230h]
  6. .text:00000001801376D8                 lea     rdx, [r14+8]
  7. .text:00000001801376DC                 mov     rcx, rax
  8. .text:00000001801376DF                 mov     r8, [rax]
  9. .text:00000001801376E2                 call    qword ptr [r8+8]
  10. .text:00000001801376E6                 mov     rcx, [rsp+78h+arg_20]
  11. .text:00000001801376EE                 test    rcx, rcx
  12. .text:00000001801376F1                 jz      short loc_180137719
  13. .text:00000001801376F3                 mov     rax, [rcx]
  14. .text:00000001801376F6                 lea     rdx, [rsp+78h+arg_18]
  15. .text:00000001801376FE                 call    qword ptr [rax+40h]
  16. .text:0000000180137701                 test    rax, rax
  17. .text:0000000180137704                 jz      short loc_180137719
  18. .text:0000000180137706                 mov     edx, [rsp+78h+arg_18]
  19. .text:000000018013770D                 test    edx, edx
  20. .text:000000018013770F                 jz      short loc_180137719
  21. .text:0000000180137711                 mov     rcx, rax
  22. .text:0000000180137714                 call    sub_18001B806

We know that the last part of the function to use the key was the following call:

  1. ZipEncrypt::Init(pKeyData, keyLen);

So here we can follow the last call and see if this takes us to the same thing.
Double click on the sub_18001B806 here then follow the jump table again that shows up.

In this function we will see a string shortly into the function like this:

  1. .text:00000001800C86D5                 lea     rcx, off_180655D60 ; Buf2
  2. .text:00000001800C86DC                 call    sub_180023B96
  3. .text:00000001800C86E1                 lea     rcx, aYarrow    ; "yarrow"

Perfect, if we follow the ZipEncrypt::Init function in the actual code, we will see this:

  1. void ZipEncrypt::Init(const uint8* pKeyData, uint32 keyLen)
  2. {
  3.     ltc_mp = ltm_desc;
  4.     register_hash (&sha1_desc);
  5.     register_hash (&sha256_desc);
  6.  
  7.     register_cipher (&twofish_desc);
  8.  
  9.     int prng_idx = register_prng(&yarrow_desc) != -1;
  10.     assert( prng_idx != -1 );
  11.     rng_make_prng(128, find_prng("yarrow"), &g_yarrow_prng_state, NULL);

Which does include the 'yarrow' string. We now know where we need to set a breakpoint and dump some information.

Since IDA loads the DLL as a static file at a pre-defined base address we need the offset inside of it where this call was at. So from our information above we have:

  1. .text:0000000180137714                 call    sub_18001B806

Then we need the base address IDA is using for this. We can find this by simply scrolling up to the top of the IDA View and see this:

  1. .text:0000000180001000 ; Input SHA256 : DE8FF68F112C36246A031375A34179CB18E0EFD381D5914358B6D7DC22011916
  2. .text:0000000180001000 ; Input MD5    : 0D88EC6FBECBDA7CDFB9983B9ABDD9E1
  3. .text:0000000180001000 ; Input CRC32  : 009FF1D1
  4. .text:0000000180001000
  5. .text:0000000180001000 ; File Name   : D:\Games\Steam\steamapps\common\Wolcen\win_x64\CrySystem.dll
  6. .text:0000000180001000 ; Format      : Portable executable for AMD64 (PE)
  7. .text:0000000180001000 ; Imagebase   : 180000000
  8. .text:0000000180001000 ; Timestamp   : 5AD84A1D (Thu Apr 19 07:49:49 2018)
  9. .text:0000000180001000 ; Section 1. (virtual address 00001000)
  10. .text:0000000180001000 ; Virtual size                  : 006160CD (6381773.)
  11. .text:0000000180001000 ; Section size in file          : 00616200 (6382080.)
  12. .text:0000000180001000 ; Offset to raw data for section: 00000400
  13. .text:0000000180001000 ; Flags 60000020: Text Executable Readable
  14. .text:0000000180001000 ; Alignment     : default
  15. .text:0000000180001000 ; PDB File Name : c:\Wolcen\Umbra\BinTemp\win_x64_release\Code\CryEngine\CrySystem\CrySystem.pdb
  16. .text:0000000180001000

Imagebase is what we're after. So we have:

0000000180137714 - The address of the call to ZipEncrypt::Init
180000000 - The base address of the CrySystem.dll inside of IDA.

We take these two values and subtract them from each other to get our offset. Which is:
0000000180137714 - 180000000 = 137714 (keep in mind these values are all in hexadecimal!)

Next step is to debug the game.


Debugging Wolcen / Dumping The Key

Next, load up x64dbg and load up Wolcen.exe into it. Before we go further we want to force x64dbg to stop each time a DLL is loaded. This is important because we want it to stop as soon as CrySystem.dll is loaded before it calls the ZipEncrypt::Init function.

So in x64dbg go to: Options -> Preferences -> Events -> Break On - make sure DLL Load is checked.

We will also want to ignore exceptions that happen while its loading. So we can ignore all of them by opening:

Options -> Preferences -> Exceptions -> Add Range -> and enter:
Start: 0
End: FFFFFFFF

Click OK -> Save.

Now, press the -> (Run - F9) button to continue stepping through the loading of Wolcen until you see it has loaded CrySystem.dll. x64dbg will keep pausing each time it loads a DLL which you can see what was loaded in the bottom status bar next to the yellow 'Puased' word.

Once you see it say it loaded CrySystem.dll stop pressing the play button.

Now that it is loaded we want to go to our offset to set a breakpoint. To do this we do the following:

- In x64dbg, click on the 'Memory Map' tab.
- Scroll down til you find 'crysystem.dll' in the Info column.
- Right-click and choose 'Follow in Disassembler'.
- In the Disassembler, right-click the first line that has the bytes '4D 5A' and choose Copy -> Address.
- Press CTRL+G to open the Go To window.
- Paste in the address you just copied then also add: + 137714 which is our offset we calculated from before.
- Your Goto should look like this: 00007FF9707A0000 + 137714 (With your address differing from mine of course.)
- Click OK to go to this address. it should land on a call like this:

  1. 00007FF9708D7714 | E8 ED 40 EE FF                  | call crysystem.7FF9707BB806                        |

Press F2 now to set a breakpoint here. Next we can remove the option to stop each time a DLL is loaded. So to remove that we do:
Options -> Preferences -> Events -> Break On - make sure DLL Load is unchecked now.

Then click Save and press the -> (Run - F9) button again to let the game continue to load until we hit our breakpoint here.
Once the breakpoint is hit, we can dump the key now.

Looking at the registers on the right side of x64dbg, we can see the following:
- RAX holds a pointer to the RSA key.
- RDX holds the size of the RSA key.

Right-click on the RAX value and choose 'Follow in Dump'.

Here is the RSA key. :)

Copy the size of the key that matches RDX's value, currently it is 8C bytes long, again which is hex so you want to copy 140 bytes (decimal) long of data to get the full key.

  1. 00007FF96C5D1790  30 81 89 02 81 81 00 DD 6A 61 1E 84 79 D7 AE 91  0......Ýja..y×®.  
  2. 00007FF96C5D17A0  60 50 6A 7E 9A 99 74 95 27 28 06 29 4F 1E 4D DC  `Pj~..t.'(.)O.MÜ  
  3. 00007FF96C5D17B0  C7 52 46 1F 5F 63 11 7B 61 E9 06 50 42 15 97 FD  ÇRF._c.{aé.PB..ý  
  4. 00007FF96C5D17C0  03 04 B1 BE 89 2B C3 4E DA D1 7E E7 5C 18 E4 21  ..±¾.+ÃNÚÑ~ç\.ä!  
  5. 00007FF96C5D17D0  69 03 E2 7D 7A F5 83 A6 50 5E 72 00 EF CA E5 07  i.â}zõ.¦P^r.ïÊå.  
  6. 00007FF96C5D17E0  67 5C BB 77 7A E7 74 FE C0 F9 99 FA 41 1E 80 7C  g\»wzçtþÀù.úA..|  
  7. 00007FF96C5D17F0  54 BB 18 CB FC 86 48 35 BC DD C8 6F 34 97 6C B8  T».Ëü.H5¼ÝÈo4.l¸  
  8. 00007FF96C5D1800  82 4B 76 94 76 34 BF 2D 70 52 2B BA 15 35 36 6E  .Kv.v4¿-pR+º.56n  
  9. 00007FF96C5D1810  06 D9 88 D4 00 70 9D 02 03 01 00 01              .Ù.Ô.p......

And that's it, you now have the needed RSA key to decrypt the PAK files of Wolcen.


Closing..

One last note for this, this should technically work for any CryEngine game as long as they haven't heavily modified things to where this information is invalid. But, in general, you should be able to dump the RSA key of any CryEngine game using this method.

For more advanced users, you can just create a pattern to find and dump the key as well through other means, but I wont go into detail for that. It's simple enough for those that know how to do it, this was more of a tutorial aimed at getting it manually for those that wanted a tutorial from me.

Enjoy!
Derp~
Need a great web host? Check out: AnHonestHost.com


Donations can be made via Paypal:
https://www.paypal.me/atom0s
sM0Ke
Posts: 1
Joined: Thu Jan 10, 2019 5:32 am

Re: [Wolcen] How To Get PAK RSA Key

Post by sM0Ke » Sun Jan 20, 2019 2:30 am

This is great! Thank you very much atom0s!
User avatar
atom0s
Site Admin
Posts: 417
Joined: Sun Jan 04, 2015 11:23 pm
Location: 127.0.0.1
Contact:

Re: [Wolcen] How To Get PAK RSA Key

Post by atom0s » Mon Apr 08, 2019 1:27 am

The latest key for the current Wolcen beta (1.0.4.0) is:

Code: Select all

30 81 89 02 81 81 00 AD 04 B6 34 5F EC 3C 1D 4C
41 C5 8F F2 D5 68 F0 D6 85 F3 93 A1 17 03 90 B6
BD C4 45 0B 05 94 F8 7C A5 C3 2B 78 F6 98 29 6A
E6 75 0D 1E 73 D2 BB 50 DB EB 64 EB C2 F6 2D 95
A2 C2 0D 84 F3 60 B7 C6 3D 62 72 61 EA E3 50 B6
A0 4B D0 D6 9F 08 08 7C AF 75 F2 D8 07 E5 09 FC
01 A8 5B 41 51 BB 09 18 84 84 6B F2 70 9F CB 03
61 72 C9 E3 B2 77 CD 1B B1 9F D3 FE 46 CB 67 D6
A0 FD 39 3A A9 87 B3 02 03 01 00 01
Derp~
Need a great web host? Check out: AnHonestHost.com


Donations can be made via Paypal:
https://www.paypal.me/atom0s
User avatar
atom0s
Site Admin
Posts: 417
Joined: Sun Jan 04, 2015 11:23 pm
Location: 127.0.0.1
Contact:

Re: [Wolcen] How To Get PAK RSA Key

Post by atom0s » Sat Sep 28, 2019 11:53 am

Here is the latest key for Wolcen Beta 1.2.1.0 which is currently available on Steam.
  1. 30 81 89 02 81 81 00 E2 72 5E F9 BB 16 88 71 C2 38 D9 1B 64 CF B8 B1 33 2F 1B BC F1 05 F4 0F 25 2F B9 3F 3A 60 9D 52 4C F8 F5 EE 09 BC 55 4F D9 18 DB 8B B3 53 1D 6F 88 BE FE A4 BF BD F5 1C B1 E1 DF 5E 5D FA 83 FD 65 84 D3 7E 27 99 24 22 4F C4 F8 BB 6C 98 ED 50 D2 70 02 E8 BA 21 F3 5F 01 55 A0 8D 9E D2 76 71 40 32 AE EC DA 06 6C 17 FA 54 F1 C3 3E 5D AF 8B 33 2B 3C C0 77 14 90 A1 52 61 B2 DD 90 8F 53 F1 02 03 01 00 01



Here is also a new/easier way to obtain the key instead of having to debug. This can be done with ease just disassembling CryGameSDK.dll now.

Open CryGameSDK.dll in a disassembler/debugger, whichever you prefer.

Look for this pattern:
  1. C7 ?? 8C 00 00 00 ?? ?? ?? ?? ?? ?? ?? C3


Should find one instance. The second instruction (lea) should be the RSA key buffer pointer. In the current game version it looks like this:
  1. .text:0000000180993820
  2. .text:0000000180993820 sub_180993820   proc near               ; DATA XREF: .rdata:0000000180B50C48↓o
  3. .text:0000000180993820                 mov     dword ptr [rdx], 8Ch
  4. .text:0000000180993826                 lea     rax, unk_180C5A920
  5. .text:000000018099382D                 retn
  6. .text:000000018099382D sub_180993820   endp

The mov instruction is the size of the RSA key, the lea instruction is loading the buffer pointer into RAX. Follow the buffer pointer, unk_180C5A920 in this case, and you will find the RSA key data.
Derp~
Need a great web host? Check out: AnHonestHost.com


Donations can be made via Paypal:
https://www.paypal.me/atom0s
Manus
Posts: 1
Joined: Fri Oct 18, 2019 2:08 pm

Re: [Wolcen] How To Get PAK RSA Key

Post by Manus » Fri Oct 18, 2019 2:19 pm

Thanks for the tutorial. I'm currently trying to decrypt a tool made with cryengine 3 and was able to get the RSA key with this tutorial.

But now i have the proble to find a working link to the PakDecrypt source code to replace the RSA and build my own exe.
I hope you can help me with this an provide a link to a still working copy of the source code.
Post Reply

Who is online

Users browsing this forum: No registered users and 0 guests