年初的时候搞的一个游戏,一直陆陆续续搞,弄了很久,那个时候还没离职,现在换了新公司之后,专心搞逆向,就把这个游戏找出来,练习一下。
游戏原版样本链接在这
https://share.weiyun.com/0DVjK9xK
是一个比较好玩的单机游戏,当时找的时候是 google 商店 单机排行第一。
简单尝试
当时的需求已经忘记了,只记得这个游戏有个壳,是没见过的海外壳,有点恶心人。这次就尝试脱壳还原。
拿到 apk 直接重签名安装,会无法进入游戏界面
很明显是有一个签名校验的,然后打开 进入 manifest 看看,application 明显是一个 壳入口,不是真实的游戏 application,到这里就可以分析出 游戏加壳了。
想从 java 层看看逻辑,结果发现 java 的字符串全部被加密了,写了一个解密脚本
import java.io.*;
import java.util.*;
public class LiAPPAna {
public static void write(String path, List<String> list) {
try {
BufferedWriter out = new BufferedWriter(new FileWriter(path));
for (String s : list) {
out.write(s);
out.write("\n");
}
out.close();
} catch (IOException e) {
}
}
public static List<String> readTxtFileIntoStringArrList(String filePath) {
List<String> list = new ArrayList<String>();
try {
String encoding = "UTF-8";
File file = new File(filePath);
if (file.isFile() && file.exists()) { // 判断文件是否存在
InputStreamReader read = new InputStreamReader(
new FileInputStream(file), encoding);// 考虑到编码格式
BufferedReader bufferedReader = new BufferedReader(read);
String lineTxt = null;
while ((lineTxt = bufferedReader.readLine()) != null) {
list.add(lineTxt);
}
bufferedReader.close();
read.close();
} else {
System.out.println("找不到指定的文件");
}
} catch (Exception e) {
System.out.println("读取文件内容出错");
e.printStackTrace();
}
return list;
}
public static void readFile(String path) {
List<String> list = readTxtFileIntoStringArrList(path);
for (int i = 0; i < list.size(); i++) {
String line = list.get(i);
if (line.contains("mmmmmmmm")) {
String keyLine = list.get(i - 4);
String valueLine = list.get(i - 2);
if (valueLine.isEmpty()) {
System.out.println(path);
System.out.println(i);
return;
}
if (keyLine.isEmpty()) {
System.out.println(path);
System.out.println(i);
return;
}
list.set(i - 4, getTargetFromLine(line, keyLine, valueLine));
}
}
// for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
//// System.out.println(entry.getKey());
//// System.out.println(entry.getValue());
// String a = entry.getKey();
// int b = entry.getValue();
// String c = decryption(a, b);
// String resultKey = Integer.toHexString(b);
// result.put(a, c);
// }
//
//
// for (Map.Entry<String, String> entry : result.entrySet()) {
// System.out.println(entry.getKey());
// System.out.println(entry.getValue());
// int i = 0;
// for (String line : list) {
//// getTargetFromLine(line);
// if (line.contains(entry.getKey()) && line.contains("jumbo")) {
// String result = entry.getValue();
// String temp = line.replace(entry.getKey(), strToUnicode(result, true)) + "#" + entry.getKey();
// System.out.println(temp);
// list.set(i, temp);
// }
// i++;
// }
// }
// //修改后的文件
String fileName = new File(path).getName();
String resultOut = "/Users/licuisong/Downloads/dec_out/" + fileName;
write(resultOut, list);
}
static Map<String, Integer> hashMap = new HashMap();
static String key = "";
static int value = -1;
private static void getKey(String line) {
String[] res = line.split("\"");
if (res.length > 2) {
for (int i = 2; i < res.length; i++) {
res[1] = res[1] + res[i];
}
}
key = res[1];
}
private static void getValue(String line) {
String prex = "0x";
String[] res = line.split("0x");
if (res[0].charAt(res[0].length() - 1) == '-') {
prex = "-" + prex;
}
if (res[1].split(" ").length > 1) {
res[1] = res[1].split(" ")[0];
}
String pre_data = prex + res[1];
value = Integer.decode(pre_data);
}
private static String getTargetFromLine(String line, String keyLine, String valueLine) {
//反查目标字符串
getKey(keyLine);
getValue(valueLine);
//
// if (!key.isEmpty() && value != -1) {
// String c = decryption(key, value);
// String temp = keyLine.replace(key, strToUnicode(c, false));
// return temp;
// }
return "";
}
public static List<String> getAllFile(String path) {
ArrayList<String> files = new ArrayList<String>();
File file = new File(path);
File[] tempList = file.listFiles();
for (int i = 0; i < tempList.length; i++) {
if (tempList[i].isFile()) {
if (tempList[i].toString().contains("$")) {
// continue;
}
System.out.println("文 件:" + tempList[i]);
files.add(tempList[i].toString());
}
if (tempList[i].isDirectory()) {
// System.out.println("文件夹:" + tempList[i]);
}
}
return files;
}
static HashMap<String, String> result = new HashMap<>();
public static void main(String... args) {
//
// List<String> smaliFile = getAllFile("/Users/licuisong/Downloads/djtf_xx/smali/com/lockincomp/liapp/");
// for (String s : smaliFile) {
// System.out.println("=============");
// System.out.println("=============");
// System.out.println("=============");
// System.out.println(s);
// readFile(s);
// }
// String s = "a b";
// System.out.println(s.split(" ")[0]);
// const-string/jumbo v4, "\\\u00ca\u00141"
//
// const v5, 0xfbcd19
String argx1 = "\\ubf1b\\ubf17\\ubf16\\ubf16\\ubf1d\\ubf1b\\ubf0c\\ubf11\\ubf0e\\ubf11\\ubf0c\\ubf01";
int argx2 = -0x55914088;
for (int i = 0; i < 1000000; i++) {
String s = decryption(argx1, argx2, i);
if (s.contains("c")) {
System.out.println(s);
}
}
// connectivity
}
public static String decryption(String arg4, int arg5, int change) {
arg4 = unicodeToStr(arg4);
// System.out.println("decryption: " + arg4);
// System.out.println("decryption: " + arg5);
int v1 = 0;
String v0 = "";
if (arg5 != 0) {
try {
while (v1 < arg4.length()) {
v0 = String.valueOf(v0) + (((char) (LiAPPAna.util_char3(arg4.charAt(v1), ((char) (arg5 >> v1 % 4)), v1) ^ change)));
++v1;
}
} catch (Exception v0_1) {
v0 = null;
}
}
// System.out.println(v0);
// System.out.println("=============");
// System.out.println("=============");
return v0;
}
static char util_char_0(char arg1, int arg2) {
return ((char) ((1 << arg2 & arg1) >> arg2));
}
static char util_char_1(char arg1, int arg2) {
return ((char) (1 << arg2 | arg1));
}
static char util_char_2(char arg1, int arg2) {
return ((char) ((1 << arg2 ^ -1) & arg1));
}
static char util_char3(char arg6, char arg7, int arg8) {
char v0;
int v1;
int v5 = 8;
if (arg8 % 3 == 0) {
v1 = 0;
v0 = arg6;
while (v1 < v5) {
if ((LiAPPAna.util_char_0(arg6, v1) ^ LiAPPAna.util_char_0(arg7, v1)) == 0) {
v0 = LiAPPAna.util_char_2(v0, v1);
} else if ((LiAPPAna.util_char_0(arg6, v1) ^ LiAPPAna.util_char_0(arg7, v1)) == 1) {
v0 = LiAPPAna.util_char_1(v0, v1);
}
v1 += 2;
}
} else {
if (arg8 % 3 == 1) {
v1 = 1;
v0 = arg6;
while (true) {
if (v1 >= v5) {
return v0;
}
if ((LiAPPAna.util_char_0(arg6, v1) ^ LiAPPAna.util_char_0(arg7, v1)) == 0) {
v0 = LiAPPAna.util_char_2(v0, v1);
} else if ((LiAPPAna.util_char_0(arg6, v1) ^ LiAPPAna.util_char_0(arg7, v1)) == 1) {
v0 = LiAPPAna.util_char_1(v0, v1);
}
v1 += 2;
}
}
if (arg8 % 3 == 2) {
v1 = 0;
v0 = arg6;
while (true) {
if (v1 >= v5) {
return v0;
}
if ((LiAPPAna.util_char_0(arg6, v1) ^ LiAPPAna.util_char_0(arg7, v1)) == 0) {
v0 = LiAPPAna.util_char_2(v0, v1);
} else if ((LiAPPAna.util_char_0(arg6, v1) ^ LiAPPAna.util_char_0(arg7, v1)) == 1) {
v0 = LiAPPAna.util_char_1(v0, v1);
}
++v1;
}
}
v0 = arg6;
}
return v0;
}
/**
* 字符串转 Unicode 编码
*
* @param string 原字符串
* @param halfWith 是否转换半角字符
* @return 编码后的字符串
*/
public static String strToUnicode(String string, boolean halfWith) {
if (string == null || string.isEmpty()) {
// 传入字符串为空返回原内容
return string;
}
StringBuilder value = new StringBuilder(string.length() << 3);
String prefix = "\\u", zerofix = "0", unicode;
char c;
for (int i = 0, j; i < string.length(); i++) {
c = string.charAt(i);
if (!halfWith && c > 31 && c < 127) {
// 不转换半角字符
value.append(c);
continue;
}
value.append(prefix);
// 高 8 位
j = c >>> 8;
unicode = Integer.toHexString(j);
if (unicode.length() == 1) {
value.append(zerofix);
}
value.append(unicode);
// 低 8 位
j = c & 0xFF;
unicode = Integer.toHexString(j);
if (unicode.length() == 1) {
value.append(zerofix);
}
value.append(unicode);
}
return value.toString();
}
/**
* Unicode 编码转字符串
*
* @param string 支持 Unicode 编码和普通字符混合的字符串
* @return 解码后的字符串
*/
public static String unicodeToStr(String string) {
String prefix = "\\u";
if (string == null || string.indexOf(prefix) < 0) {
// 传入字符串为空或不包含 Unicode 编码返回原内容
return string;
}
StringBuilder value = new StringBuilder(string.length() >> 2);
String[] strings = string.split("\\\\u");
String hex, mix;
char hexChar;
int ascii, n;
if (strings[0].length() > 0) {
// 处理开头的普通字符串
value.append(strings[0]);
}
try {
for (int i = 1; i < strings.length; i++) {
hex = strings[i];
if (hex.length() > 3) {
mix = "";
if (hex.length() > 4) {
// 处理 Unicode 编码符号后面的普通字符串
mix = hex.substring(4, hex.length());
}
hex = hex.substring(0, 4);
try {
Integer.parseInt(hex, 16);
} catch (Exception e) {
// 不能将当前 16 进制字符串正常转换为 10 进制数字,拼接原内容后跳出
value.append(prefix).append(strings[i]);
continue;
}
ascii = 0;
for (int j = 0; j < hex.length(); j++) {
hexChar = hex.charAt(j);
// 将 Unicode 编码中的 16 进制数字逐个转为 10 进制
n = Integer.parseInt(String.valueOf(hexChar), 16);
// 转换为 ASCII 码
ascii += n * ((int) Math.pow(16, (hex.length() - j - 1)));
}
// 拼接解码内容
value.append((char) ascii).append(mix);
} else {
// 不转换特殊长度的 Unicode 编码
value.append(prefix).append(hex);
}
}
} catch (Exception e) {
// Unicode 编码格式有误,解码失败
return null;
}
return value.toString();
}
}
用这个脚本可以解析 Java 层被加密的逻辑,它所有的函数调用都是通过反射,所以不解密字符串,根本看不了逻辑。
解密后发现逻辑都在 assets 中的 jar 和 sc 中,是一个抽取壳。
尝试脱壳
先上 frida 看看壳逻辑 ,直接 启动原游戏 frida -UF
遗憾,这个壳对 frida 有防御,换成 spawn 方式 注入,发现也是被检测
那就不能用 frida 来脱壳了,先尝试脱壳机,但是 脱壳机也被检测了特征,和 frida 一样,这就比较麻烦了。直接分析壳的话工作量太大了。
不过想了一下,这是国外的壳(https://liapp.lockincomp.com/),特征采集基本都是对比较出名的框架进行特征匹配。那我找一个国内比较小众的脱壳工具试试
https://github.com/CodingGay/BlackDex
成功脱壳,这个工具的作者我算是比较熟悉,微信上经常向他请教问题。非常 nice 的人
脱壳后调试
脱壳后发现 关键逻辑也被字符串加密了
加密方法是个 native 函数, smali 代码在下面
.class public La/a/al/a;
.super Ljava/lang/Object;
.source "a.java"
# direct methods
.method public constructor <init>()V
.registers 1
.prologue
.line 10
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public static mmmmmm(Ljava/lang/String;I)Ljava/lang/String;
.registers 3
.param p0, "s1" # Ljava/lang/String;
.param p1, "j" # I
.prologue
.line 14
invoke-static {p0, p1}, La/a/al/a;->mmmmmmm(Ljava/lang/String;I)Ljava/lang/String;
move-result-object v0
.line 16
.local v0, "strRet":Ljava/lang/String;
return-object v0
.end method
.method public static mmmmmmm(Ljava/lang/String;Ljava/lang/String;)I
.registers 3
.param p0, "tag" # Ljava/lang/String;
.param p1, "msg" # Ljava/lang/String;
.prologue
.line 20
const/4 v0, 0x1
return v0
.end method
.method public static native mmmmmmm(Ljava/lang/String;I)Ljava/lang/String;
.end method
脱壳后的 dex 运行会报 so 没有链接的报错 UnsatisfieldLinkError,然而这个 so 是在壳里面的 动态解密出
来的,我是没有这个 so 的,也就是说我是没有用来解密的 native 函数的。
到这里又卡住了,因为游戏初始化的的核心逻辑都被字符串加密了,字符串是必须要还原的。但是字符串解密算法又在 so 里面。现在又不能用 frida 去 hook,一下就束手无策了。
新的方案
兜兜转转 还是找到了一个 过 frida 的工具
https://github.com/hzzheyang/strongR-frida-android/releases/tag/15.2.2
正好我这里用的也是 1522 版本的 frida ,那就直接推进去手机用就可以了。
尝试了一下非常好,原包已经检测不到 frida 了,那就可以为所欲为了,包括脱壳都可以用 frida 来进行。
那现在就要找到这个 native 函数了,因为 so 都加壳了,那就不能直接ida 分析
- 首先扫描全部 jni 函数
- 找到名字匹配 mmmmmm 的 native 函数的偏移
- 根据偏移 hook 函数入口
- 打印返回值
- 把 入参 和返回值打印成一个 java 语法的 map ,直接放进 smali 里面
frida js 代码如下
const STD_STRING_SIZE = 3 * Process.pointerSize;
class StdString {
constructor() {
this.handle = Memory.alloc(STD_STRING_SIZE);
}
dispose() {
const [data, isTiny] = this._getData();
if (!isTiny) {
Java.api.$delete(data);
}
}
disposeToString() {
const result = this.toString();
this.dispose();
return result;
}
toString() {
const [data] = this._getData();
return data.readUtf8String();
}
_getData() {
const str = this.handle;
const isTiny = (str.readU8() & 1) === 0;
const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer();
return [data, isTiny];
}
}
function prettyMethod(method_id, withSignature) {
const result = new StdString();
Java.api['art::ArtMethod::PrettyMethod'](result, method_id, withSignature ? 1 : 0);
return result.disposeToString();
}
function readStdString(str) {
if ((str.readU8() & 1) === 1) { // size LSB (=1) indicates if it's a long string
return str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}
return str.add(1).readUtf8String();
}
var mmdoule
var isFinded = false
var liapp_dic = {}
var k,v
function attach(addr) {
Interceptor.attach(addr, {
onEnter: function (args) {
this.arg0 = args[0]; // this
},
onLeave: function (retval) {
var modulemap = new ModuleMap()
modulemap.update()
var module = modulemap.find(retval)
// var string = Memory.alloc(0x100)
// ArtMethod_PrettyMethod(string, this.arg0, 1)
if(isFinded){
return
}
if (module != null) {
var mthod_name = prettyMethod(this.arg0, 1)
console.log('<' + module.name + '> method_name =>',mthod_name,',offset=>', ptr(retval).sub(module.base), ',module_name=>', module.name)
if(mthod_name.indexOf('mmmmmm')!=-1){// jni 函数特征
isFinded = true//找到了需要 hook 的函数
mmdoule = module
//开始 hook 目标 jni 函数
Interceptor.attach(ptr(retval),{
onEnter:function(args){
// console.log("sss")
// console.log()
console.log(args[3])
k = args[3]
},
onLeave:function(retval){
var result = Java.vm.getEnv().getStringUtfChars(retval,null).readCString()
console.log("result==>",result)
v = result
liapp_dic[k] = v
}
})
}
} else {
console.log('<anonymous> method_name =>', readStdString(string), ', addr =>', ptr(retval))
}
}
});
}
function dic22file() {
// body...
var content = JSON.stringify(liapp_dic)
console.log(content)
}
function hook_RegisterNative() {
var libart = Process.findModuleByName('libart.so')
var symbols = libart.enumerateSymbols()
for (var i = 0; i < symbols.length; i++) {
if (symbols[i].name.indexOf('RegisterNative') > -1 && symbols[i].name.indexOf('ArtMethod') > -1 && symbols[i].name.indexOf('RuntimeCallbacks') < 0) {
//art::RuntimeCallbacks::RegisterNativeMethod(art::ArtMethod*, void const*, void**)
attach(symbols[i].address)
}
}
}
function main() {
hook_RegisterNative()
}
function dump_memory(base,size,name) {
Java.perform(function () {
var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
var dir = currentApplication.getApplicationContext().getFilesDir().getPath();
var file_path = "/sdcard/" + name;
// var file_path = "/data/data/com.stormx.castle/files/"+name
var file_handle = new File(file_path, "wb");
if (file_handle && file_handle != null) {
Memory.protect(ptr(base),size, 'rwx');
var libso_buffer = ptr(base).readByteArray(size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log("[dump]:", file_path);
}
});
}
function dic2javamap() {
// body...
for (var key in liapp_dic) {
var item = liapp_dic[key];
var result = "map.put(\"" + key+"\"" +",\""+item + "\");\n"
console.log(result)
}
}
setImmediate(main)
frida 启动游戏注入后,会 成功 hook 到,然后持续解密,解密逻辑我不关心,直接把 对应的表拷贝出来
放到 java 工程中 转成 smali ,替换脱壳后的 dex 中 的 a.smali ,成功完成 字符串解密逻辑,当调用解密字符串函数 的时候 ,会调用到我打表的 函数里面,java 逻辑如下
package a.a.al;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
public class a {
// static String liapp_dic = "{\n" +
// " \"0x1267a03e\": \"FOREGROUND\",\n" +
// " \"0x19f5bfa3\": \"isSupportLowLatency:\",\n" +
// " \"0x1b90a2eb\": \"product=\",\n" +
// " \"0x1c38e36a\": \"title\",\n" +
// " \"0x1e5d4506\": \"SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION\",\n" +
// " \"0x1ecb133c\": \"alarm\",\n" +
// " \"0x232f7fe1\": \"message\",\n" +
// " \"0x23330095\": \"SYSTEM_UI_FLAG_LAYOUT_STABLE\",\n" +
// " \"0x26ae6740\": \"vibrator\",\n" +
// " \"0x28c2ef43\": \"message\",\n" +
// " \"0x28e35160\": \",\",\n" +
// " \"0x2c6de18a\": \"URL\",\n" +
// " \"0x2d056d0b\": \"notification_id\",\n" +
// " \"0x32e5a696\": \"alarm\",\n" +
// " \"0x33db7b52\": \"GT-I9100\",\n" +
// " \"0x361364d2\": \"SYSTEM_UI_FLAG_FULLSCREEN\",\n" +
// " \"0x393acb4d\": \"repeatInterval\",\n" +
// " \"0x3b798d68\": \"sensor\",\n" +
// " \"0x3c429fa8\": \"/\",\n" +
// " \"0x3dc97506\": \".obb\",\n" +
// " \"0x4950c86\": \"onWindowFocusChanged() hasFocus=\",\n" +
// " \"0x4a6b3c28\": \"\\n\",\n" +
// " \"0x4b1b7e15\": \"Accept-Encoding\",\n" +
// " \"0x53846b71\": \"getProperty\",\n" +
// " \"0x5621f975\": \"SYSTEM_UI_FLAG_HIDE_NAVIGATION\",\n" +
// " \"0x575d0ca9\": \"INSTALL_REFERRER\",\n" +
// " \"0x5da5e0f6\": \"repeatInterval\",\n" +
// " \"0x62b8cc73\": \"System.currentTimeMillis():\",\n" +
// " \"0x62e526c7\": \"SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN\",\n" +
// " \"0x6313dd9d\": \"notification\",\n" +
// " \"0x65ac92f8\": \"sampleRate: \",\n" +
// " \"0x6982f03d\": \"Cocos2dxPrefsFile\",\n" +
// " \"0x6e868dc3\": \"identity\",\n" +
// " \"0x747efb45\": \"Cocos2dxPrefsFile\",\n" +
// " \"0x7e4acbdd\": \"/Android/obb/\",\n" +
// " \"0x834e8007\": \"RETURNED_TO_FOREGROUND\",\n" +
// " \"0x846f33b0\": \"com.enhance.gameservice\",\n" +
// " \"0x848fe79a\": \"BACKGROUND\",\n" +
// " \"0x8f419bd8\": \"onBackPress\",\n" +
// " \"0x9072c7ca\": \"PROPERTY_OUTPUT_FRAMES_PER_BUFFER\",\n" +
// " \"0x951061ab\": \":\",\n" +
// " \"0x96d4012e\": \"onPause()\",\n" +
// " \"0x9a651812\": \"\\n\",\n" +
// " \"0xa0657936\": \"android.hardware.audio.low_latency\",\n" +
// " \"0xa2e8f806\": \",\",\n" +
// " \"0xa5be7a34\": \"onPause\",\n" +
// " \"0xa6f91b68\": \"/main.\",\n" +
// " \"0xa8710416\": \"getProperty\",\n" +
// " \"0xa8794ccf\": \"/\",\n" +
// " \"0xa97ed97d\": \"onResume()\",\n" +
// " \"0xa9ec5914\": \"PROPERTY_OUTPUT_SAMPLE_RATE\",\n" +
// " \"0xaeb11c82\": \"sdk\",\n" +
// " \"0xb042a019\": \"audio\",\n" +
// " \"0xb392da5a\": \"PUT\",\n" +
// " \"0xb9be22eb\": \"POST\",\n" +
// " \"0xbbdc1085\": \"model=\",\n" +
// " \"0xbc64b0dc\": \" input time:\",\n" +
// " \"0xbee58438\": \"notification_id\",\n" +
// " \"0xc9299ca4\": \"title\",\n" +
// " \"0xcfa4387c\": \"isEmulator=\",\n" +
// " \"0xd2f8a13f\": \"sdk_\",\n" +
// " \"0xd30cde09\": \"INSTALL_REFERRER\",\n" +
// " \"0xd37b1b62\": \".\",\n" +
// " \"0xdbc3cab2\": \"_sdk\",\n" +
// " \"0xdbe69384\": \"setSystemUiVisibility\",\n" +
// " \"0xdc299331\": \"window\",\n" +
// " \"0xe1e41da6\": \"URL\",\n" +
// " \"0xe752feb1\": \", framesPerBuffer: \",\n" +
// " \"0xea14440a\": \"onResume\",\n" +
// " \"0xeea521b0\": \"cancelLocalNotification\",\n" +
// " \"0xf83f716d\": \"android.app.lib_name\",\n" +
// " \"0xfaee6d31\": \"SYSTEM_UI_FLAG_IMMERSIVE_STICKY\"\n" +
// "}";
static boolean isDeal = false;
static Map<String, String> map = new HashMap();
public static String mmmmmm(String a, int b) {
if (!isDeal) {
map.put("0x848fe79a","BACKGROUND");
map.put("0x834e8007","RETURNED_TO_FOREGROUND");
map.put("0x1267a03e","FOREGROUND");
map.put("0x1e5d4506","SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION");
map.put("0x62e526c7","SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN");
map.put("0x5621f975","SYSTEM_UI_FLAG_HIDE_NAVIGATION");
map.put("0x361364d2","SYSTEM_UI_FLAG_FULLSCREEN");
map.put("0xfaee6d31","SYSTEM_UI_FLAG_IMMERSIVE_STICKY");
map.put("0x23330095","SYSTEM_UI_FLAG_LAYOUT_STABLE");
map.put("0xdbe69384","setSystemUiVisibility");
map.put("0xf83f716d","android.app.lib_name");
map.put("0xa0657936","android.hardware.audio.low_latency");
map.put("0x19f5bfa3","isSupportLowLatency:");
map.put("0xb042a019","audio");
map.put("0xa9ec5914","PROPERTY_OUTPUT_SAMPLE_RATE");
map.put("0xa8710416","getProperty");
map.put("0x9072c7ca","PROPERTY_OUTPUT_FRAMES_PER_BUFFER");
map.put("0x53846b71","getProperty");
map.put("0x65ac92f8","sampleRate: ");
map.put("0xe752feb1",", framesPerBuffer: ");
map.put("0x7e4acbdd","/Android/obb/");
map.put("0xa6f91b68","/main.");
map.put("0xd37b1b62",".");
map.put("0x3dc97506",".obb");
map.put("0x3b798d68","sensor");
map.put("0xdc299331","window");
map.put("0x33db7b52","GT-I9100");
map.put("0x26ae6740","vibrator");
map.put("0x846f33b0","com.enhance.gameservice");
map.put("0xbbdc1085","model=");
map.put("0x1b90a2eb","product=");
map.put("0xaeb11c82","sdk");
map.put("0xdbc3cab2","_sdk");
map.put("0xd2f8a13f","sdk_");
map.put("0xcfa4387c","isEmulator=");
map.put("0x6313dd9d","notification");
map.put("0xd30cde09","INSTALL_REFERRER");
map.put("0xe1e41da6","URL");
map.put("0xa97ed97d","onResume()");
map.put("0x4950c86","onWindowFocusChanged() hasFocus=");
map.put("0x4b1b7e15","Accept-Encoding");
map.put("0x6e868dc3","identity");
map.put("0xb9be22eb","POST");
map.put("0xb392da5a","PUT");
map.put("0x747efb45","Cocos2dxPrefsFile");
map.put("0x575d0ca9","INSTALL_REFERRER");
map.put("0x2c6de18a","URL");
map.put("0xa2e8f806",",");
map.put("0x9a651812","\n");
map.put("0x951061ab",":");
map.put("0x28e35160",",");
map.put("0x4a6b3c28","\n");
map.put("0xa8794ccf","/");
map.put("0xeea521b0","cancelLocalNotification");
map.put("0xbee58438","notification_id");
map.put("0xc9299ca4","title");
map.put("0x232f7fe1","message");
map.put("0x393acb4d","repeatInterval");
map.put("0x32e5a696","alarm");
map.put("0x3c429fa8","/");
map.put("0x96d4012e","onPause()");
map.put("0xa5be7a34","onPause");
map.put("0x62b8cc73","System.currentTimeMillis():");
map.put("0xbc64b0dc"," input time:");
map.put("0x2d056d0b","notification_id");
map.put("0x1c38e36a","title");
map.put("0x28c2ef43","message");
map.put("0x5da5e0f6","repeatInterval");
map.put("0x1ecb133c","alarm");
map.put("0x6982f03d","Cocos2dxPrefsFile");
map.put("0xea14440a","onResume");
}
// hashMap.put("0xabcc", "123");
// System.out.println("0x"+Integer.toHexString(b));
String key = "0x" + Integer.toHexString(b);
String result = map.get(key);
if (result == null) {
System.out.println("null");
// Log.d("dec_in","result into == null:");
// Log.d("dec_in", a);
// Log.d("dec_in", "0x" + Integer.toHexString(b));
return "";
}
return result;
}
public static void main(String... args) {
mmmmmm("",0x834e8007);
}
}
smali 文件替换后 重新编译签名安装,一切正常。
接下来就可以按需处理 gm 提权,无敌插件。之类的操作了,因为游戏太久远了,我已经不玩了,就有空再搞吧,我再看看最新的 单机是什么壳,挑战一下国外这些壳,其实蛮有意思的。
总的来说遇到的问题比较多,主要原因还是不会自定义系统,可以考虑针对这个壳专门开发一个脱壳镜像。
到这里,整个壳就被脱了下来。脱壳后的程序已经可以直接运行了。