前言
我并不是什么逆向大佬,我只是利用我曾经学习过的内容做了我自己可以做到的事情,不会的就查呗,学习不就是这样
破解这个也只是因为我朋友在地铁上想玩这个游戏但是被要求强制更新,网络又不好,所以想要逆向解决一下,然后就有了我一系列的操作
明确需求
强制更新,网络不好,无法更新
- 解决方法一:用流量更新,本末倒置,本来就是单机游戏,为了没网的时候玩的
- 解决方法二:换一个游戏,这个方法我考虑了很多次,但是都没敢和朋友说
- 解决方法三:既然要求更新,那就是当前版本小于最小版本,改大版本就好了
更改版本
拿到安装包然后用MT管理器提取一下安装包,查看安装包,没有加固,也没有混肴,那么就简单很多了,修改版本号大多时候直接看安装包内的
AndroidManifest.xml就好了,不出意外的,再文件头部找到如下代码:android:versionName="16.3.1"具体我有点忘记了,不过应该是这个,然后更新的提示显示是16.3.4,于是我就修改成16.3.4,没有提示了,再次修改成99.9.9,发现闪退了,看了是做了校验,或者版本过大的原因,如果是直接校验版本,那么我前面的修改肯定是不可能成功,修改成16.3.4可以不用更新,那就只有一种可能,这个参数小范围改动不影响程序,大范围改动和其他值结合倒置程序闪退,和版本有关的还有一个参数,如下:
android:versionCode="xxxxx"这个我是真忘记了,所以就用
x来代替了,然后将它同样修改成最大999999,成功不更新进入游戏且不闪退
新需求
我朋友告诉我,的确不更新了,但是如果安装因为签名不同,不是覆盖安装,之前的游戏存档都会消失,又因为非root用户,无法修改应用的数据目录的内容,所以还是没用,然后我朋友不太了解,其实到这里他就放弃了,不过我比较犟,所以仍在继续
- 解决方法一:使用框架或虚拟机,但是朋友手机的性能不确定
- 解解方法二:逆向游戏代码,找到通关逻辑,然后尝试绕过
- 解决方法三:封装为直装包,无需root,直接开挂
解决方法二我尝试了一番,发现相关搜索结果都是按w来计算的,想要找到相关代码,可能需要很长的时间,所以我毫不犹豫选择了方法三
直装原理
我一开始也不是很了解这个东西,然后学习了之后就了解了,但也就略微懂一点
第一就是要开启游戏的debug调试,第二就是将自己的so(类似于windows的exe)放到游戏的lib(通常用于存储so二进制文件的地方),然后通过我们自己的悬浮窗(在游戏启动前加载),然后通过这个悬浮窗的按钮来调用游戏lib目录下的so文件
这个我看到的大多的教程都是32位的,64位的没怎么看到,所以也有可能我后面修改不成功就是这个问题,只是可能,并不一定
新目标
既然知道怎么做了,接下来就是具体实现,想要做直装,主要的是so文件,其他的网上有很多的教程教学,b站的汪泰,他的蓝奏云网盘提供了直装模板,然后b站也有他的教学视频,这个感兴趣的可以自己去看一下,这里就开始将so了
首先.so 文件的全称为共享对象文件(Shared Object file) ,是一种用于存储程序可执行代码和数据的二进制文件。与静态链接库不同,它在程序运行时以动态链接的方式与其他程序或库进行连接。这意味着,在程序运行时,才会将.so 文件中的代码和数据加载到内存中,并与程序进行链接,而不是在编译时就将所有代码都包含在程序中。这种动态链接的方式,使得程序在运行时更加灵活,也方便了代码的更新和维护
这种文件通常是C和C++编写后编译而成,手机的话多是C4droid,所以我们想要一个so文件,前提是有cpp(C++),然后利用C4droid转换成so,怎么转换,将写好的代码利用这个编译导出,导出成二进制而非apk就可以了
然后cpp里面就是我们想要干的事情——>存档
注意,我们最主要的需求是存档无法保存 ,无root权限,所以只需要保存存档就可以了,不管干什么,都是以这个位基础
如何用cpp保存存档
很简单,但是我并不打算直接讲,主要是思路,首先我想到的方法并不是保存存档,而是直接用gg修改器修改游戏,然后通关全部关卡,看到值是A内存的时候我就知道,要找特征码来修改了,然后多次保存附近的值进行对比(具体可以看b站的教程,直接搜gg修改器找特征码)
import os def read_file(filename): """读取文件内容""" with open(filename, 'r', encoding='utf-8') as f: return f.read().strip() def parse_data(content): """解析数据,返回值的列表""" if not content: return [] values = [] pairs = content.split('|') for pair in pairs: if ',' in pair: value, offset = pair.split(',', 1) values.append((value.strip(), offset.strip())) return values same_results = [] # 读取两个文件 script_dir = os.path.dirname(os.path.abspath(__file__)) file1_content = read_file(os.path.join(script_dir, '特征码1.txt')) file2_content = read_file(os.path.join(script_dir, '特征码2.txt')) # 解析数据 file1_data = parse_data(file1_content) file2_data = parse_data(file2_content) # 获取较短的长度,避免索引越界 min_length = min(len(file1_data), len(file2_data)) # 逐一对比 for i in range(min_length): value1, offset1 = file1_data[i] value2, offset2 = file2_data[i] if value1 == value2 and offset1 == offset2: if value1 not in ("0", "1"): result_line = f"{value1},{offset1}|{value2},{offset2}" same_results.append(result_line) print(f"【相同】{result_line}") # 保存结果 with open(os.path.join(script_dir, '比对结果.txt'), 'w', encoding='utf-8') as f: f.write('比对相同的数据\n') for result in same_results: f.write(f'【相同】{result}\n')然后我是传给电脑用电脑写的python脚本比对的,保存的特征码格式是
值|偏移量,值|偏移量,...,然后提取出相同的,发现有一个值多次都没有变化,值位-1701531433,于是利用这个值偏移280,16进制就是0x118,就能指向我们要修改的值的内存地址,修改成110即可全部通关然后就是编写lua脚本尝试了
function main() menu = gg.multiChoice({ "1.通关全部关卡", "2.退出脚本"},nil,"LawKing独家制作") if menu == nil then else if menu[1] == true then PASS() end if menu[2] == true then EXIT() end end LW1 = 0 LW2 = 0 end function PASS() gg.clearResults() gg.setRanges(gg.REGION_ANONYMOUS) gg.searchNumber("-1701531433", gg.TYPE_DWORD, false, gg.SIGN_EQUAL, 0, -1) jg=gg.getResults(1) sl=gg.getResultCount() for i = 1, sl do dzy=jg[i].address+280 gg.addListItems({[1] = {address = dzy,flags = gg.TYPE_DWORD,freeze = true,value = 110}}) end gg.toast("开启成功") gg.clearResults() end function EXIT() gg.toast("脚本已结束运行") print("LawKing独家制作") print(sj) os.exit() end while true do if gg.isVisible(true) then LW1 = nil gg.setVisible(false) end if LW1 == nil then main() end end测试后发现没有问题,直接将关键值复制到cpp工具生成cpp,用的也是汪泰网盘提供的cpp工具箱,生成后用C4droid导出成so文件,放到游戏lib目录下,中间调试以及各种测试浪费了很多时间,然后效果还是不行,修改没有效果,搜索页搜索不到,因为游戏是64位的
然后我就转变思路,既然找不到这个值,那我就找存档文件,先是gg修改器修改成
110,这个时候只是内存中是通关了,并没有存档,还需要去把最后一关手动打通关一遍,存档文件才会保存,然后反复比对应用程序的目录,发现在应用程序的数据目录下(需root,我用的虚拟机),有一个files文件夹,这个里面存放的文件有变动,然后不断替换原有文件和新文件进入游戏,成功找到存档文件——beacon.registry直接打开是乱码,一开始我是想直接复制,然后和直装一起发给要用的人,只需要把文件放到对应目录就可以剪切过去,不过安卓的权限做的很严格,就是应用程序无法直接访问非应用程序的目录,而非root用户又无法直接访问和修改应用程序的数据目录,到这一步被卡了一段时间,大概1天左右
然后我发现应用程序lib目录下的文件会在应用安装后自动放到应用程序数据目录下的lib文件中,这不就巧了,和另一个放存档的数据目录都是属于这个应用程序的,用so调用剪切过去就好了,理想很美满,现实很果敢
虚拟机测试了几遍都没问题,实机测试就是不通过,难道要就此放弃,我感觉就差一步了,想了一段时间,突然一到灵光闪现
我既然无法直接剪切(权限),但是应用程序肯定是可以对它自己的数据目录写入数据的(要不然用户通关一个关卡后它是怎么保存的),所以我要么找到写入存档的那个代码,前面也说过,代码太多,所以我就用的第二种方法,直接写入文件到这个目录,但是怎么写入呢,文件内容是乱码,想了一会,想起可以使用十六进制写入,于是就将内容硬编码写入到cpp,完整代码如下
#include <stdio.h> #include <string.h> #include <unistd.h> int main() { const unsigned char beacon_data[] = { 0x58,0x3E,0x03,0xE9,0x6D,0x82,0xF5,0x7D, 0x2F,0x4A,0x74,0x0B,0x32,0xA6,0xBC,0x77, 0x06,0xF2,0x64,0xDC,0xE3,0xD2,0x6F,0xC3, 0xFF,0x26,0x1F,0xF4,0x2E,0x5D,0x08,0xE1, 0xF1,0xCD,0x7F,0x0F,0xB4,0xF6,0x9A,0xE8, 0xBA,0xBC,0x7D,0xD0,0x8F,0x48,0x15,0x92, 0x8E,0x19,0x07,0xAD,0x03,0x1B,0x9D,0x6F, 0x34,0x64,0xD6,0x77,0x1E,0xB7,0x4E,0xEC, 0x29,0xA3,0x46,0x74,0xD3,0x6A,0x7D,0x34, 0x80,0xFC,0x52,0x6D,0x29,0x6D,0x9E,0x94, 0xBC,0x7C,0x5D,0xD0,0xEB,0x1D,0xAD,0xAF, 0x7F,0x0E,0x6B,0x47,0xE4,0xBD,0x08,0x92, 0x72,0xAD,0x19,0xFD,0x32,0xE8,0x01,0x15, 0xE0,0xC7,0x9E,0xBE,0x38,0xA4,0x20,0xBC, 0xC5,0x55,0x1A,0x39,0x28,0x0C,0x50,0xF7, 0xBE,0xC9,0x0C,0x1E,0x59,0xE9,0xDB,0x83, 0x44,0x17,0x31,0xBC,0xBC,0xE3,0x9E,0x75, 0xA0,0xD0,0x83,0x50,0x30,0xF0,0x1D,0x08, 0x24,0xE7,0x1F,0x3C,0x6E,0x2F,0xDD,0x65, 0x06,0x8C,0xFE,0x58,0xEB,0xEC,0xE8,0x03, 0xD5,0x5C,0xF9,0x93,0x82,0x61,0x82,0x5E, 0x8F,0xEA,0x91,0xE7,0x61,0xE6,0x4F,0x5C, 0x28,0x93,0x43,0x38,0xEC,0xAF,0x19,0xBD, 0xE8,0x1E,0x77,0x49,0xBD,0x36,0xC3,0x0E, 0xD2,0xC5,0xCB,0xE2,0x46,0xAD,0xD4,0x5B, 0xC3,0xDF,0xDA,0xCD,0xBE,0x03,0x6B,0x21, 0x73,0x05,0x06,0xB2,0xDA,0x1A,0xE6,0xF4, 0x8E,0x5F,0xC8,0x81,0x43,0xC3,0x3B,0xDB, 0x55,0xC5,0x10,0xB3,0xFD,0xC3,0x1F,0xD6, 0xBE,0x46,0xD9,0xFC,0x75,0x16,0x40,0x42, 0x35,0xD9,0xC8,0xF7,0xC8,0x92,0xE3,0x61, 0xE9,0x50,0x22,0x26,0x70,0x86,0xCC,0x4C, 0xD2,0xBF,0x48,0x65,0xFA,0xF9,0x53,0x4F, 0xD6,0x6A,0x73,0x0E,0x0A,0x09,0xB2,0xDF, 0x39,0xB2,0xC9,0x5C,0x46,0x09,0x0E,0xB5, 0x43,0x72,0x23,0x78,0xC9,0x67,0xCF,0xCF, 0xF3,0x9D,0x7C,0x85,0xB7,0x16,0xAD,0x80, 0x41,0xB4,0xAA,0xF1,0x1A,0xAC,0x98,0x5F, 0xDC,0xE8,0x0A,0x3C,0x4E,0x4E,0x15,0x6B, 0x49,0xB3,0x3F,0xC9,0xD4,0xCE,0x88,0x2D, 0x90,0xB8,0xE3,0x9F,0x26,0xE6,0x65,0xCD, 0x8E,0xC0,0x9E,0x3E,0x4B,0x50,0xFD,0x0F, 0x75,0x14,0xC5,0x35,0x47,0xB6,0xF2,0x9E, 0x8C,0x87,0xD6,0x06,0x1E,0xB9,0x6F,0xE1, 0x58,0x9E,0xF8,0xA4,0x53,0x73,0x9B,0xCB, 0x57,0x01,0x26,0x6B,0x6A,0x7D,0xC6,0xED, 0xAC,0xFD,0x3E,0x13,0x44,0x17,0x37,0x8C, 0x51,0x3E,0x5A,0xB8,0x40,0x01,0x25,0xEF, 0xB7,0x6E,0xA0,0xFE,0x0C,0x75,0x3A,0x51, 0xE7,0xCC,0x7D,0x25,0xBC,0xAA,0x45,0x8B, 0x48,0x01,0x26,0x15,0x68,0xE3,0x9D,0x60, 0x51,0xBC,0x34,0xAC,0xA1,0x0E,0xB3,0xF6, 0xA0,0x6B,0x5F,0xBF,0x01,0x4E,0xF8,0xCC, 0x5F,0xCA,0x4F,0xA3,0x53,0xE9,0x4E,0x59, 0x43,0x28,0x84,0xDC,0xC0,0x9E,0x6B,0xCE, 0xFE,0x52,0x00,0xA7,0x94,0x94,0xD3,0x1D, 0x54,0x56,0xCF,0x63,0x6F,0xD9,0x29,0xEB, 0xBF,0xC9,0x41,0xC1,0xCA,0x87,0x5A,0x4F, 0x1B,0xC8,0xC4,0x9A,0xD2,0xE2,0xD7,0x1C, 0xAF,0xFA,0x66,0xB6,0x0F,0xD6,0x7C,0x60, 0xBB,0x69,0xFF,0xBD,0x38,0xE3,0x2D,0xC5, 0x7D,0xFC,0x29,0xBF,0x54,0xD5,0x25,0x23, 0xD2,0x5D,0x94,0xA5,0x0F,0x08,0xC2,0x19, 0x57,0x81,0x02,0xE1,0x5A,0x4B,0x1B,0xA4, 0x56,0x95,0x14,0x20,0x2F,0x8E,0x22,0xE3, 0x71,0x5C,0x88,0x56,0x6D,0x2B,0xF0,0xB3, 0x40,0xE0,0xDE,0xCD,0x1D,0xF5,0x2C,0x26, 0x02,0xD8,0xE3,0xD3,0x14,0xD6,0xF2,0xBD, 0xC8,0x5A,0x46,0xB0,0xA9,0x2D,0x33,0x21, 0x7A,0x7C,0xA1,0x0B,0x2B,0x91,0x31,0x1A, 0xBE,0xFD,0x91,0x86,0x01,0x83,0xBB,0x26, 0xC1,0x34,0x94,0x2D,0x04,0xA7,0x28,0x45, 0xA7,0xC2,0x23,0xB8,0x46,0x36,0xAA,0xF7, 0x80,0xE7,0x12,0x8A,0x8E,0xD3,0xC5,0x74, 0xE2,0x26,0x44,0x8C,0xD8,0x27,0xFB,0x32, 0x40,0xAA,0xAE,0xF4,0x25,0x72,0x52,0x55, 0x03,0x90,0x18,0xB6,0x02,0x90,0x05,0x38, 0xEE,0xD2,0x56,0x2B,0xD5,0x1F,0x87,0xDA, 0x58,0xA9,0xAB,0x06,0xEC,0x4E,0xCD,0x78, 0xCB,0x40,0x7E,0x19,0xC6,0xD8,0x32,0xB5, 0xC0,0x6E,0x7B,0xE9,0x0D,0xCF,0x7E,0x25, 0x46,0x2A,0xA7,0x4C,0x40,0x90,0x97,0x47, 0xEA,0x04,0xEC,0x56,0x7B,0x37,0x22,0xC3, 0x1D,0xEB,0x26,0x7A,0x8D,0xF7,0x61,0x38 }; const int beacon_len = sizeof(beacon_data); const char* dst_path = "/data/user/0/com.rovio.angrybirdsfriends/files/beacon.registry"; FILE* fp = fopen(dst_path, "wb"); if (!fp) return 1; fwrite(beacon_data, 1, beacon_len, fp); fflush(fp); ftruncate(fileno(fp), (off_t)beacon_len); fclose(fp); return 0; }
调用so保存存档
这一步菌丝来的,因为我不会写smail代码,但是原有的直装模板只适合32位的应用,因为32位的应用程序安装后只会有一个数据目录,然后lib目录就在这个数据目录下,而64位不同,会有两个数据目录,且lib目录不在这两个目录,而是/data/app/应用程序的包名+随机字符串(看着像base64编码)/lib这里面,然后他通过smail代码来获取应用程序的这个目录,然后拼接相对路径来调用so的,具体代码如下(java版,直接用np管理器转换的,容易查看):
// // Decompiled by Jadx (from NP Manager) // package mxxy.game.mod; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; class FloatContentView$100000000 implements OnClickListener { private final FloatContentView this$0; @Override public void onClick(View view) { String str = view.getContext().getApplicationInfo().nativeLibraryDir; if (str.startsWith("/")) { str = str.substring(1); } String str2 = "AB Friends Cheating"; Log.d(str2, str); String concat = str.concat("/lib1.so"); Log.d(str2, concat); RunShell(concat); RunShell(concat); FloatContentView.access$1000021(this.this$0, "解锁全部关卡执行完成"); } private String getFilesDir() { return (String) null; } FloatContentView$100000000(FloatContentView floatContentView) { this.this$0 = floatContentView; } private void RunShell(String str) { } static FloatContentView access$0(FloatContentView$100000000 floatContentView$100000000) { return floatContentView$100000000.this$0; } }NP管理器就这点比较好,不用像MT管理器一样,什么都要会员,但是我也经常用MT,转成
java后就非常清晰易懂了,获取路径,然后拼接,再去调用这个路径下的lib1.so文件,给大家看看没有转换成java的smail代码# classes9.dex .class Lmxxy/game/mod/FloatContentView$100000000; .super Ljava/lang/Object; .source "FloatContentView.java" # interfaces .implements Landroid/view/View$OnClickListener; # annotations .annotation system Ldalvik/annotation/EnclosingClass; value = Lmxxy/game/mod/FloatContentView; .end annotation .annotation system Ldalvik/annotation/InnerClass; accessFlags = 0x20 name = "100000000" .end annotation # instance fields .field private final this$0:Lmxxy/game/mod/FloatContentView; # direct methods .method constructor <init>(Lmxxy/game/mod/FloatContentView;)V .registers 7 move-object v0, p0 move-object v1, p1 move-object v3, v0 invoke-direct {v3}, Ljava/lang/Object;-><init>()V move-object v3, v0 move-object v4, v1 iput-object v4, v3, Lmxxy/game/mod/FloatContentView$100000000;->this$0:Lmxxy/game/mod/FloatContentView; return-void .end method .method private RunShell(Ljava/lang/String;)V .registers 2 .annotation system Ldalvik/annotation/Signature; value = { "(", "Ljava/lang/String;", ")V" } .end annotation return-void .end method .method static access$0(Lmxxy/game/mod/FloatContentView$100000000;)Lmxxy/game/mod/FloatContentView; .registers 5 move-object v0, p0 move-object v3, v0 iget-object v3, v3, Lmxxy/game/mod/FloatContentView$100000000;->this$0:Lmxxy/game/mod/FloatContentView; move-object v0, v3 return-object v0 .end method .method private getFilesDir()Ljava/lang/String; .registers 4 .prologue .line 217 move-object v0, p0 const/4 v2, 0x0 check-cast v2, Ljava/lang/String; move-object v0, v2 return-object v0 .end method # virtual methods .method public onClick(Landroid/view/View;)V .registers 14 .annotation system Ldalvik/annotation/Signature; value = { "(", "Landroid/view/View;", ")V" } .end annotation .annotation runtime Ljava/lang/Override; .end annotation .prologue .line 206 move-object v0, p0 move-object v1, p1 invoke-virtual {v1}, Landroid/view/View;->getContext()Landroid/content/Context; move-result-object v5 invoke-virtual {v5}, Landroid/content/Context;->getApplicationInfo()Landroid/content/pm/ApplicationInfo; move-result-object v6 iget-object v7, v6, Landroid/content/pm/ApplicationInfo;->nativeLibraryDir:Ljava/lang/String; const-string v8, "/" invoke-virtual {v7, v8}, Ljava/lang/String;->startsWith(Ljava/lang/String;)Z move-result v9 if-eqz v9, :cond_19 const/4 v10, 0x1 invoke-virtual {v7, v10}, Ljava/lang/String;->substring(I)Ljava/lang/String; move-result-object v7 :cond_19 const-string p0, "AB Friends Cheating" invoke-static {p0, v7}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I const-string v3, "/lib1.so" invoke-virtual {v7, v3}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String; move-result-object v11 invoke-static {p0, v11}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I invoke-static {v11}, Lmxxy/game/mod/Miscellaneous;->RunShell(Ljava/lang/String;)V invoke-static {v11}, Lmxxy/game/mod/Miscellaneous;->RunShell(Ljava/lang/String;)V .line 208 move-object v3, v0 iget-object v3, v3, Lmxxy/game/mod/FloatContentView$100000000;->this$0:Lmxxy/game/mod/FloatContentView; const-string v4, "解锁全部关卡执行完成" invoke-static {v3, v4}, Lmxxy/game/mod/FloatContentView;->access$1000021(Lmxxy/game/mod/FloatContentView;Ljava/lang/String;)V return-void .end methodwordpress我不知道支不支持,反正typora是不支持
smail的语法高亮
实机效果演示
视频我就不放了这里了,不然你们加载这篇文章会耗费非常长的时间,感兴趣的可以进我的CTF交流群查看群相册-逆向里面
群号:2151029071
备注:博客/b站直播 (避免机器人)
