2023-03-26 1,275 ℃
原本打算下个月再水的,不过转念一想,反正也不可能做到每个月都水一篇,而且现在放开以后准备到处去玩了,所以还是赶紧写完了发出来。
偷梁换柱
思路的产生其实还是在上一篇搞去年年底那个刚出的游戏的时候,虽然在模拟器里用NativeBridge载入了arm的so实现了修改,但是马上就发现了一个小问题,这种使用系统函数载入的so会在maps里显示路径,加固只要检测一下maps就能发现这个异常的so。不过因为以前写Riru模块的时候看过部分源码,所以很快就想起了Riru的那个hide函数,先看看那个函数是怎么做的:
- 遍历maps取出带有要隐藏的so路径的段
- 解析段的起始地址,大小等信息
mmap
一块相同大小的backupmemcpy
把段复制到backupmunmap
段- 在相同起始地址上
mmap
一块匿名映射 memcpy
从backup复制回起始地址
不过这跟绕过.text的校验有什么关系呢?首先思考下反作弊是怎么校验.text段的,通常的做法就是:从本地lib中打开要校验的so,然后从maps中找到要校验的so在内存中的位置,分别解析后对比其中的.text段,你会发现它其实也是通过在maps中找so的路径来确认so在内存中的位置。所以回到上面Riru的hide函数,如果我们把要修改的so在maps中隐藏,然后让backup拥有so的路径信息,就能骗过反作弊,让它去检测backup而我们就可以在原本的so内存上为所欲为。而要让backup带上路径信息也很简单,创建的时候通过源文件映射就行。所以稍微修改一下hide函数就能实现了:
bool riru_hide(const std::set<std::string_view> &names) {
procmaps_iterator *maps = pmparser_parse(-1);
if (maps == nullptr) {
LOGE("cannot parse the memory map");
return false;
}
procmaps_struct **data = nullptr;
size_t data_count = 0;
procmaps_struct *maps_tmp;
while ((maps_tmp = pmparser_next(maps)) != nullptr) {
bool matched = false;
matched = names.count(maps_tmp->pathname);
if (!matched) continue;
auto start = (uintptr_t) maps_tmp->addr_start;
auto end = (uintptr_t) maps_tmp->addr_end;
if (maps_tmp->is_r) {
if (data) {
data = (procmaps_struct **) realloc(data,
sizeof(procmaps_struct *) * (data_count + 1));
} else {
data = (procmaps_struct **) malloc(sizeof(procmaps_struct *));
}
data[data_count] = maps_tmp;
data_count += 1;
}
LOGD("%" PRIxPTR"-%" PRIxPTR" %s %ld %s", start, end, maps_tmp->perm, maps_tmp->offset,
maps_tmp->pathname);
}
auto fd = open(data[0]->pathname, O_RDONLY);
struct stat sb;
if (fstat(fd, &sb) == -1)
LOGE("fstat");
auto fileLength = sb.st_size;
size_t copySize = 0;
for (int i = 0; i < data_count; ++i) {
auto procstruct = data[i];
auto start = (uintptr_t) procstruct->addr_start;
auto end = (uintptr_t) procstruct->addr_end;
auto length = end - start;
copySize += length;
}
LOGI("file length : %jd", fileLength);
LOGI("copySize : %zu", copySize);
auto backup_address = (uintptr_t) FAILURE_RETURN(
mmap(nullptr, copySize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE, fd, 0),
MAP_FAILED);
close(fd);
for (int i = 0; i < data_count; ++i) {
auto procstruct = data[i];
auto start = (uintptr_t) procstruct->addr_start;
auto end = (uintptr_t) procstruct->addr_end;
auto length = end - start;
int prot = get_prot(procstruct);
// backup
LOGD("%" PRIxPTR"-%" PRIxPTR" %s %ld %s is backup to %" PRIxPTR, start, end,
procstruct->perm, procstruct->offset, procstruct->pathname, backup_address);
if (!procstruct->is_r) {
LOGD("mprotect +r");
FAILURE_RETURN(mprotect((void *) start, length, prot | PROT_READ), -1);
}
LOGD("memcpy -> backup");
memcpy((void *) backup_address, (void *) start, length);
// munmap original
LOGD("munmap original");
FAILURE_RETURN(munmap((void *) start, length), -1);
// restore
LOGD("mmap original");
FAILURE_RETURN(mmap((void *) start, length, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0),
MAP_FAILED);
LOGD("mprotect +w");
FAILURE_RETURN(mprotect((void *) start, length, prot | PROT_WRITE), -1);
LOGD("memcpy -> original");
memcpy((void *) start, (void *) backup_address, length);
if (!procstruct->is_w) {
LOGD("mprotect -w");
FAILURE_RETURN(mprotect((void *) start, length, prot), -1);
}
LOGD("mprotect backup");
FAILURE_RETURN(mprotect((void *) backup_address, length, prot), -1);
backup_address += length;
}
if (data) free(data);
pmparser_free(maps);
return true;
}
完整流程就是,hook dlopen
,等待要修改的so加载后,调用riru_hide
,完成偷梁换柱。
拿了个之前用其他方法搞定的带.text校验的游戏测试,成功绕过,证明了这个思路至少在部分游戏上是可行的。
你好!
Hello,
Can you update il2cpp to support v31 please
Initializing metadata…
System.NotSupportedException: ERROR: Metadata file supplied is not a supported version[31].
at Il2CppDumper.Metadata..ctor(Stream stream) in C:\projects\il2cppdumper\Il2CppDumper\Il2Cpp\Metadata.cs:line 57
at Il2CppDumper.Program.Init(String il2cppPath, String metadataPath, Metadata& metadata, Il2Cpp& il2Cpp) in C:\projects\il2cppdumper\Il2CppDumper\Program.cs:line 124
at Il2CppDumper.Program.Main(String[] args) in C:\projects\il2cppdumper\Il2CppDumper\Program.cs:line 98
Press any key to exit…
时机应该在init_array 之前是最稳定的。 不用riru也能实现 参考Linker原理
是不是直接hook dlsymbol就完事了….
很想学这方面 可惜是个小白 楼主有没有推荐的好路子学习一下
不好意思没有推荐,基本都是自学的
void hack_start(const char *game_data_dir) {
bool load = false;
for (int i = 0; i < 10; i++) {
void *handle = xdl_open("libil2cpp.so", 0);
if (handle) {
std::set names = {“libil2cpp.so”};
riru_hide(names);
dalao,我这样调用有错么,添加hide后,游戏直接闪退了
05-30 17:10:50.461 4023 4031 I Perfare : nb 0xe9899
05-30 17:10:50.461 4023 4031 I Perfare : NativeBridgeLoadLibrary 0xcd5e7270
05-30 17:10:50.461 4023 4031 I Perfare : NativeBridgeLoadLibraryExt 0xcd5e7860
05-30 17:10:50.461 4023 4031 I Perfare : NativeBridgeGetTrampoline 0xcd5e72b0
05-30 17:10:50.461 4023 4031 I Perfare : arm path /proc/self/fd/96
05-30 17:10:50.462 4023 4031 I Perfare : arm handle : 0xe30f9180
05-30 17:10:50.462 4023 4031 I Perfare : JNI_OnLoad 0xedd8a320
不能这样,大大已经告诉你怎么用了
你不能等il2cpp运行的时候再去hide它,请看文章倒数第二行
谢谢,关键在于hook dlopen,直接去抄代码解决了
大佬怎么在模拟器上hook dlopen,加QQ聊一下啊 925300138
好家伙 秒数据异常被迫下线
哪个游戏,让我认识下
可以脱离riru吗各位,脱离ritu怎么实现,riru_hide 传递什么参数 期待一个实例demo
这除了函数名跟riru有啥关系吗?传你要操作的so名称
munmap original 之后就闪退 是恢复部分 类似替换的操作出错了吗
munmap的那块代码是不是正在运行?
我尝试隐藏自己,所以他应该在运行中
你肯定不能对正在运行的代码执行munmap啊
2023-08-02 13:39:12.227 5056-5132 Mod_Menu com.xiakemeng.tf.ldd E munmap original
2023-08-02 13:39:12.227 5056-5132 libc com.xiakemeng.tf.ldd A Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x78d1900e7c in tid 5132 (iakemeng.tf.ldd), pid 5056 (iakemeng.tf.ldd)
是内存页属性的问题吗
一样
procmaps_iterator* pmparser_parse(int pid);
procmaps_struct* pmparser_next(procmaps_iterator* p_procmaps_it);
void pmparser_free(procmaps_iterator* p_procmaps_it);
在pmparser.h里并没有写它们的具体实现,仅仅只是声明,riru源码里也没有其他相关代码,那riru_hide是怎么跑起来的,能否解答下
android studio 一直报错ld: error: undefined symbol: pmparser_parse 这几个
因为是用依赖的方式引用的
https://github.com/RikkaW/proc-maps-parser-prefab
非常感谢
先抢个沙发,一周发两篇blog,你肯定不是本人