TouHou Danmaku Kagura

2022-11-17 Update:

Dankagu Net Enc – Network packet decryption


It has been a long while, and here I’m researching game internals once again.

il2cpp has developed so rapidly recent years. I actually tried to research Idoly Pride two months ago, but that game heavily uses reflection calls. Following the logic is really hard since there is dynamic function pointer everywhere, and so I gave up. After the new Touhou Danmaku Kagura game starts its service, I want to at least be able to join the game, and also want to try again to figure out the logic behind the game, so here we go again.

(Updated 4th Sep)

  • Obstacle at the starting line

Before I can start do anything, the global-metadata.dat isn’t a normal one.

Load up the UnityFramework in ida and go straight to string list, searching “global-metadata” I can immediately find a function with that string as parameter. Follow that function a few times and there is indeed a place where the data is xored with some in-memory key.

$in = fopen('global-metadata-enc.dat', 'rb');
$out = fopen('global-metadata.dat', 'wb');

$size = unpack('V', fread($in, 4));

$key = array_map('chr', [0xE, 0x75, 0x3B, 0x8E, 0, 0x14, 0xE6, 0xB2]);

$enc = fread($in, $size[1]);
for ($i=8; $i<strlen($enc); $i++) {
  $enc[$i] = $enc[$i] ^ $key[$i % 8];
}
fwrite($out, $enc);
  • Jailbreak detection

Before I can gather any information about the game, it just stuck at “touch to start” and won’t go any further. At this point I can guess it detected jailbreak and refused to enter the game. After searching through the dump.cs for almost two hours, I finally found TakashoAuthorizer::GamerStart(), in it with a call to SecurityChecker::ChkeckGameStart(), so suspicious.

And, although I didn’t find a direct call, but there are serveral usage of SecureLibAPI class, and there is a isRooted method, which calles is_p_jb_o_c. In that it uses __ccs __cf __cg __csid __cfc __detd __ch __cipl to check many parts of the envionment. But in fact, this dev team probablly grabbed some obscure detection code from some place, all of these checks passed with the exception of cfc, which check against file system.

All of these checks use a weird obfuscation, every char is turned into an int. When reading, read in int, & 0xff, and then ^ 0x19.

As for the fs check I got caught on, they straight up used nsstring to load a json. At first I tried to tar those path and delete them, and I’m able to start the game. Later I tried to hook the string, and return an empty json if the start matches with that json. It worked charmly, and I don’t need to delete everything again.

  • Resource encryption

The EncryptorManager class pops out as I was searching “encrypt” in dump.cs. It seems there are many combination of different keys for different purposes.

Two of the variants exists. One is RijndaelEncryptor, which uses Rijndael as the name suggests. Peek inside the ctor method, it uses RFC2898 to generate key and iv.

As for assets, they use a class named LightWeightEncryptor. This generates a xor table from a seed value at initialize time, and those values are from XorShift. There is also a salt value when actual asset data comes in, and it’s just an offset for table lookup.

This part of algorithm is at https://gist.github.com/esterTion/26e8500dbec9956fc89eef7daeb97e81


8/10 update

  • Embedded db

Originally I was going to dig out the actual judge timing for notes. However the embedded one has only score coeffiencients. It seems the judge values are downloaded and can be changed at any time? Not very sure.

Take Raw/db/master_gamedata.dat as example, use lwe(0x99737BBF, 11, 256), extract content from .tar.gz archive,and there is a json inside.

  • Local settings save(Documents/saves)

This one is also not much useful, but the salt for local manifest is stored in the save data.

Different save uses a different seed to initialize a XorShift instance, and use XorShift::SwizzleBytes to encrypt / decrypt it. Below is the seed table:

998741	abp
38741151	rgm
45782187	hako
100	dialog_show
200	boost_setting
300	gacha
400	nakayoshicloud
400	announcement_
500	playmode_
600	system_setting
700	friend
800	sort_filter
7784741	club
58714163	tmp
5458143	tos

Once decyprted, 0-15 is the save header, 16-31 is the checksum, and json data after 32 bytes.

  • asset manifest

This one has a really weird logic. First use lwe encryptor 1004 to modify bytes ( See EncryptorManager class for id meanings ), then use Rijndael encryptor 1001 to transform. Finally there is a FlatBuffer store inside a .tar.gz. Why so many layers?

And, as I said previously, the only thing unsure here is the salt used in 1004 lwe. But in fact, I can still bruteforce it. Take the first 16 bytes, modify with lwe, transform with rijn, and check whether the header is 1f8b.

Now with FlatBuffer data, I need to create a schema based on the generated classes. Properties are pretty clear here. Then I can choose to either let flatc to convert it for me, or use the generated class and use sdk to read it. (Why there are no quotation around the fields in json generated by flatc??)

namespace D4L.TakashoFes.Master;

table AssetPathRaw {
  entry:[AssetPathRawEntry];
}

table AssetPathRawEntry {
  ID:uint64;
  AssetPath:string;
  HashedPath:string;
  FileHash:string;
  FileSize:uint32;
  FileRev:uint32;
  FileType:uint32;
  DownloadOption:uint32;
}
file_identifier "D4AO";
root_type AssetPathRaw;

DownloadOption & 0xfff is the salt for each asset


For now I’ll just do these parts, and I’m feeling lazy to crack open the grpc api. It’s pretty complex, and I can just use Fiddler to get the current manifest and dump the new cards. And I don’t feel the need to create another db dumper, as nearly nobody is cared about those stuff anymore.


Bonus round:

14 thoughts on “TouHou Danmaku Kagura”

  1. Hi.
    I downloaded the files, but I am still confused about what to do next with the files extension like “awb”, “xab”
    Is there any way to decrypt them using another app?

  2. Which software did you use to extract the data (images, sound, etc)? Mostly saying because I’m using AssetStudio and when I open a file (from the files folder) it doesn’t do anything.

    1. I don’t want to bother setting up an online viewer, so I didn’t dump them on the website.
      The assets themselves are pretty easy to find, if you are on android just get all the assets and load them all, and find the largest Texture2D, this way you can find which file it is from and dump other live2d json files

      1. I just need the raw assets, so I don’t mind not having an online viewer, if you could dump them as well that would be awesome

  3. Thank you for your article. Can you tell us more about the decryption method of saves? I need some UI material files, but when I use swizzlebytes to decrypt the archive, the file is still in the state of garbled code, which may not be called correctly

      1. The resource paths of Android and IOS seem to be different. I try to replace the files in the path “1”, but the hashedpaths of the two are also different

  4. I can help with Idoly Pride if you want.
    I reversed the encryption.
    If you want feel free to contact me.

    1. Ah, thanks for your offering.
      I mentioned about that is because, after over a year away from mobile game disassemble, I picked one with such high difficulty.
      Since I don’t know how to play with models and such, I don’t actually have a lot to play with.
      I would suggest that, one day, like when they announce the end of service, publish the research to both help the preservation and spread the knowledge. Just my thoughts.

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax