Skip to content

Instantly share code, notes, and snippets.

@esterTion
Last active April 28, 2025 19:06
Show Gist options
  • Save esterTion/768c646a83a089af9e5fbb83b77a59fc to your computer and use it in GitHub Desktop.
Save esterTion/768c646a83a089af9e5fbb83b77a59fc to your computer and use it in GitHub Desktop.
Bang Dream proto dumper
gist title place holder

Used to dump proto defination from BangDream, meant to dump ios binary

First, modify Il2Cpp (like using dnspy) to dump Properties. (Config.dumpProperty = true)
Then dump the game as usual
Modify generateMessage.php to match your binary and dump.cs file
And run php generateMessage.php <messageClass>

Example proto output attached

Notice: Dictionary is outputed as two elements message, 1 for key, and 2 for value


用于导出BangDream proto定义文件,可用在ios程序上
首先修改Il2Cpp(比如用dnspy)使之导出属性(Config.dumpProperty = true)
然后导出程序内容
修改 generateMessage.php内的程序与 dump.cs 文件位置
然后运行 php generateMessage.php <messageClass>

后附样例proto输出

注意:Dictionary 键值对输出为两元素结构,1为键,2为值

<?php
/**
* https://gist.github.com/esterTion/768c646a83a089af9e5fbb83b77a59fc
* Updated for il2cpp-dumper v6.1.2
* https://github.com/Perfare/Il2CppDumper/releases
*/
$dumpcs = file_get_contents('D:\\ida_workplace\\bang\\dump.cs');
$prog = fopen('D:\\ida_workplace\\bang\\UnityFramework','rb');
$definedClass=[];
$class = $argv[1];
$outputProtoFile = fopen(__DIR__.'/'.$class.'_gen.proto', 'w');
fwrite($outputProtoFile, "syntax = \"proto2\";\n");
function stripClass($s) {
$idx = strrpos($s, '.');
if ($idx === false) return $s;
return substr($s, $idx + 1);
}
function writeMessage($class, $message) {
global $outputProtoFile;
$class = stripClass($class);
fwrite($outputProtoFile, "message ${class} {\n");
foreach ($message as $item=>$info) {
$type = stripClass($info[0]);
$tag = $info[1];
$hint = $info[2];
$comment = $info[3];
fwrite($outputProtoFile, " ${hint} ${type} ${item} = ${tag}; // ${comment}\n");
}
fwrite($outputProtoFile, "}\n");
}
function readClass($level, $class) {
global $dumpcs;
global $definedClass;
if (($shownClass = array_search($class, $definedClass)) === false) {
$definedClass[] = $class;
}
$message = [];
//echo str_repeat(" ", $level).$class."\n";
if (preg_match("(\[ProtoContract\].*\n\w+ class ".$class." [^{}]*?(\{((?>[^{}]+)|(?-2))*\}))", $dumpcs, $classDef) !== 0) {
$classDef=$classDef[0];
preg_match_all('(\[ProtoMember\((\d+)\)\].*\n \w+ ([^\ \<]+(\<((?>[^\<\>]+)|(?-2))*\>)?) ([^\ ;]+))', $classDef, $propList);
for($i=0;$i<count($propList[0]); $i++) {
$type = jumpType(
$level,
$propList[2][$i],
$propList[5][$i]
);
$message[$type[0]] = [$type[1], $propList[1][$i], $type[2], $type[3]];
}
if ($class == 'MasterActionSet') {
$message['areaName'] = [
'string',
6,
'optional',
'manual add'
];
}
if (!$shownClass) {
echo $class."\n";
//print_r($message);
writeMessage($class, $message);
}
unset($message);
} else {
echo $class.' not found';
exit;
}
}
// 1012ECB5C 1
// 1012ECB70 2
function jumpType ($level, $type, $name) {
if (substr($type, -2, 2) == '[]') {
// array entry
//echo str_repeat(" ", $level+1).$type."\n";
$sub = jumpType($level+2, substr($type, 0, -2), 'entry');
return [$name, $sub[1], 'repeated', 'array'];
} else if (substr($type, 0, 11) == 'Dictionary<') {
// dictionary
preg_match('(<(\w+), (\w+)>)', $type, $subType);
//echo str_repeat(" ", $level+1).'dictarr_'.$name."[]\n";
$prefix = $subType[1].'_'.$subType[2];
global $definedClass;
if (($shownClass = array_search($prefix, $definedClass)) === false) {
$definedClass[] = $prefix;
}
$message = [];
$sub = jumpType($level+1, $subType[1], $prefix.'_key');
$message[$sub[0]] = [$sub[1], 1, $sub[2], $sub[3]];
$sub = jumpType($level+1, $subType[2], $prefix.'_value');
$message[$sub[0]] = [$sub[1], 2, $sub[2], $sub[3]];
if (!$shownClass) {
writeMessage($prefix, $message);
}
return [$name,$prefix, 'repeated', 'dictionary'];
} else if (substr($type, 0, 5) == 'List<') {
// array entry
preg_match('(<(\w+)>)', $type, $subType);
//echo str_repeat(" ", $level+1).'arr_'.$name."[]\n";
$sub = jumpType($level+1, $subType[1], 'entry');
return [$name,$sub[1], 'repeated', 'list'];
} else if (array_search($type,
['uint','string','ulong','float','int','double', 'bool','long']
) !== false){
// normal type
//echo str_repeat(" ", $level+1).'<'.$type .'> '. $name."\n";
return [$name,array('uint'=>'uint32','string'=>'string','ulong'=>'uint64','float'=>'float','int'=>'int32','double'=>'double', 'bool'=>'bool','long'=>'int64')[$type], 'optional', 'normal type'];
} else if (substr($type, 0, 9) == 'Nullable<') {
// array entry
//echo str_repeat(" ", $level+1).'<'.$type .'> '. $name."\n";
//return [$name,$type, 'optional', 'nullable type'];
preg_match('(<(\w+)>)', $type, $subType);
$return = jumpType($level, $subType[1], $name);
$return[3] = 'nullable';
return $return;
} else {
// sub message
readClass($level+1, $type);
return [$name,$type, 'optional', 'sub class'];
}
}
readClass(0, $class);
@esterTion
Copy link
Author

@YumeMichi 不知道为什么更新了一下评论不见了……
现在新版本的il2cpp dumper会直接帮你读属性值,所以直接匹配dump.cs里面[ProtoMembere(\d+)]就能拿key了。
同理,原本的[ProtoContractAttribute]都变成[ProtoContract()]

@YumeMichi
Copy link

@YumeMichi 不知道为什么更新了一下评论不见了…… 现在新版本的il2cpp dumper会直接帮你读属性值,所以直接匹配dump.cs里面[ProtoMembere(\d+)]就能拿key了。 同理,原本的[ProtoContractAttribute]都变成[ProtoContract()]

是的我注意到了就改了一下本地的代码,所以才把评论删掉了哈哈哈

@dlbuhtig4096
Copy link

最近在逆向 Pokémon TCG Pocket,它也用了 il2cpp 和 grpc
才剛寫完封包加密,看了一下生成出的 dump.cs
發現裡面好像沒幾個 ProtoMemberProtoContract 屬性

我是第一次逆向用 grpc 協議的遊戲,不怎麼熟悉
想問一下 il2cpp 現在還能用這種方式導出 proto 定義嗎
還是說我漏掉了什麼

@esterTion
Copy link
Author

@dlbuhtig4096 grpc只是使用了protobuf作为传输格式,你找一下它自己是怎么定义message的。
ProtoMember是protobuf库才会用到的属性,grpc那边应该有自己的属性或标记

@dlbuhtig4096
Copy link

@dlbuhtig4096 grpc只是使用了protobuf作为传输格式,你找一下它自己是怎么定义message的。 ProtoMember是protobuf库才会用到的属性,grpc那边应该有自己的属性或标记

我好像知道要怎麼從 dump.cs 抓出原本的 proto 了
所有 message 都繼承了 IMessage<T>‵ 這個 interface
應該能寫個 parser 想辦法還原出原本的樣子

不過你在這份 code 裡用了 preg_match("(\[ProtoContract\].*\n\w+ class ... 做比對
這讓我有點困惑,既然 protobuf 生成出的 class 不一定有這些屬性
那為什麼這個遊戲可以用單用這個方式來抓出它的 proto 呢?

@dlbuhtig4096
Copy link

dlbuhtig4096 commented Jan 27, 2025

@esterTion 抱歉又來打擾

我大致上已經自己解決了 dump proto 的部分
不過這次換成抓取封包有點問題
在實際寫請求之前,我想先抓幾個封包來看 metadata 和我猜的是不是一致
還有驗證我 dump 的 proto 是不是正確的

我試過了 mitmproxy 和 Charles proxy 抓取,但是抓不到東西
起初我以為是 certificate pinning 的關係
但是就算我用 JustTrustMe 繞開 pinning 看起來也沒用
我比較驚訝的是 r0capture 之類的工具,直接從應用層 dump 也還是抓不到封包
據我所知,grpc 應該是基於 http2 的,沒道理會抓不到東西
而且我從靜態分析也沒看到除了加密以外還有什麼特別的技術

我看了你的 blog,你似乎也曾經逆向過使用 DeNA 的 Takasho 庫的遊戲
DeNA 的 Takasho 庫大概從 2022 年之後就沒什麼改,這點從你寫的網加密就能看出來
所以我想請教,你有嘗試過抓 grpc 的封包嗎?包括請求路徑和 metadata
或者其實是 grpc 需要用什麼特別的方法才能 dump 封包嗎?

謝謝

@esterTion
Copy link
Author

@dlbuhtig4096
一般来说,grpc同时涉及dns拦截和证书绑定,而且不认系统库。
也就是,只使用proxy(包括socks)、及mitm关闭pinning,是没有用的。
以dankagu时候的例子,需要手动修改dns,将player-api-production.danmakujp-server.com指向局域网内的电脑,同时还需要修改绑定的证书。dankagu的证书公钥是存放在resources.assets里面的sc TextAsset内加密存储的(ServerConfig)
图片
图片
这个如果不是当时要关服了,我也不会去搞这个抓包存自己的玩家数据。
如果同样是 Takasho 库的话,可能有个类似的加密服务器配置项目,需要自己修改后重新打包进assets内

@dlbuhtig4096
Copy link

dlbuhtig4096 commented Jan 27, 2025

@esterTion 原來如此,怪不得都抓不到
我還奇怪為什麼代理設定看起來都沒有作用
ServerConfig 這個 class 我再研究看看,謝謝

我好像有在 /1cc/ 看過你和其他人的討論
如果你對這遊戲有興趣的話,我之後可以分享 proto dumper 和加解密的 code

@0xS4D
Copy link

0xS4D commented Jan 31, 2025

@esterTion 原來如此,怪不得都抓不到 我還奇怪為什麼代理設定看起來都沒有作用 ServerConfig 這個 class 我再研究看看,謝謝

我好像有在 /1cc/ 看過你和其他人的討論 如果你對這遊戲有興趣的話,我之後可以分享 proto dumper 和加解密的 code

I’ve been working hard trying to capture the messages from Pokémon TCG Pocket. I tried everything—Charles, MITM, Burp. I got the global-metadata and managed to extract the DLLs (both iOS and Android), but I couldn’t create a Frida script that hooks the functions correctly without crashing, nor could I extract data from the sent/received packets.

With IDA, I found the certificates, but I couldn’t make any further progress.

If you managed to read the packets, could you give me a hand?

My Discord is 0xs4d.

@dlbuhtig4096
Copy link

dlbuhtig4096 commented Jan 31, 2025

@esterTion 原來如此,怪不得都抓不到 我還奇怪為什麼代理設定看起來都沒有作用 ServerConfig 這個 class 我再研究看看,謝謝
我好像有在 /1cc/ 看過你和其他人的討論 如果你對這遊戲有興趣的話,我之後可以分享 proto dumper 和加解密的 code

I’ve been working hard trying to capture the messages from Pokémon TCG Pocket. I tried everything—Charles, MITM, Burp. I got the global-metadata and managed to extract the DLLs (both iOS and Android), but I couldn’t create a Frida script that hooks the functions correctly without crashing, nor could I extract data from the sent/received packets.

With IDA, I found the certificates, but I couldn’t make any further progress.

If you managed to read the packets, could you give me a hand?

My Discord is 0xs4d.

@0xS4D I haven't get packets capture working yet.
Was working on dumping proto from the game.
And I am almost there, dumped protos are publicly available in this repo.

Actually I have tried Frida few days ago and it crashed as well.
I will probably give frida-il2cpp-bridge a try few days later.
Have send you friend request on discord.

@esterTion 就像上面說的,我開了一個 public repo
現在只有 proto,順利的話之後會做個 python client 然後開源在上面
不過我通常只逆向單機遊戲,不太碰網遊
所以別說 grpc 了,我連一般的 rpc 都沒怎麼碰過,只知道個大概
其他人也都是第一次碰 grpc

如果有個熟悉通訊的人在,相信這個 project 容易很多
有興趣一起研究的話,請再通知我們一聲

@andreacanes
Copy link

andreacanes commented Feb 16, 2025

Hey guys very interested in your work, I was wondering if you'd like to connect and talk about this, I know a few weeks went by but I would to discuss this with someone like you, I have gotten pretty far, and I managed to decompile the apk and dump dlls and classes and methods with il2cpp-bridge.
If you want to use frida I have been able to only successfully set it up on 1 emulator, everything else seem to not work or if it does it still won't let you access the emulated realm for il2cpp-bridge.
@dlbuhtig4096 Would it be possible to access the dumped proto you mentioned? I'm about to dive into that and it would save me some time. I would also love to work with you on the replicating request part as that is where I have lots of previous experience.
@0xS4D I also sent you a friend request if you'd be interesting in working on this

My expertise is more on the side of rebuilding and replicating requests but I had lots of fun getting my hands on something new.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment