-
-
[讨论]一个使用luajava的flag题目,
-
发表于: 2025-9-27 23:28 413
-
package com.zhuotong.antiapk;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import org.keplerproject.luajava.LuaState;
/* loaded from: classes.dex */
public class MainActivity extends AppCompatActivity {
private LuaState lua;
private TextView tv;
public static native boolean eq(String str);
static {
System.loadLibrary("anti");
}
/* loaded from: classes.dex */
class MyLog {
MyLog() {
}
public void e(String str, String str2) {
Log.e(str, str2);
}
}
/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(2131427356);
final EditText editText = (EditText) findViewById(2131230863);
((Button) findViewById(2131230807)).setOnClickListener(new View.OnClickListener() { // from class: com.zhuotong.antiapk.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View view) {
if (editText.getText() == null || TextUtils.isEmpty(editText.getText().toString())) {
Toast.makeText(MainActivity.this, "null", 1).show();
return;
}
String obj = editText.getText().toString();
if (obj.length() == 16 && MainActivity.eq(obj)) {
Toast.makeText(MainActivity.this, "right", 1).show();
} else {
Toast.makeText(MainActivity.this, "wrong", 1).show();
}
}
});
}
}目标就是那个eq函数了,用ida打开搜索java发现有一个静态注册的,不过他是假的,所以直接看JNI_OnLoad,吧v6类型改成JNIEnv *v6,,就能十分清晰的找到注册函数了
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
__pid_t v2; // r8
FILE *v3; // r6
jclass v4; // r4
JNIEnv *v6; // [sp+4h] [bp-4002Ch] BYREF
char s[8]; // [sp+8h] [bp-40028h] BYREF
char v8[131062]; // [sp+12h] [bp-4001Eh] BYREF
__int64 v9; // [sp+20008h] [bp-20028h] BYREF
void *v10; // [sp+20010h] [bp-20020h]
((*vm)->GetEnv)(vm, &v6);
v2 = syscall(20);
sprintf(&v9, "/proc/%d/status", v2);
v3 = fopen(&v9, "r");
while ( fgets(s, 0x20000, v3) )
{
if ( !memcmp(s, "TracerPid", 9u) )
{
if ( atoi(v8) )
kill(v2, 9);
break;
}
}
syscall(6, v3);
v4 = (*v6)->FindClass(v6, "com/zhuotong/antiapk/MainActivity");
v10 = &sub_9950;
v9 = *off_1A568;
(*v6)->RegisterNatives(v6, v4, &v9, 1);
(*v6)->DeleteLocalRef(v6, v4);
return 65540;
}看一下v9,就是动态注册JNINativeMethod 结构体
.data.rel.ro:0001A568 FF 69 01 00 off_1A568 DCD aEq ; DATA XREF: JNI_OnLoad+A0↑o .data.rel.ro:0001A568 ; JNI_OnLoad+A4↑o ... .data.rel.ro:0001A568 ; "eq" .data.rel.ro:0001A56C 02 6A 01 00 DCD aLjavaLangStrin_1 ; "(Ljava/lang/String;)Z" .data.rel.ro:0001A570 51 99 00 00 off_1A570 DCD sub_9950+1 ; DATA XREF: JNI_OnLoad+A6↑r
int fastcall sub_9950(JNIEnv *a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9)ida分析出来的,我们重新定义一下int fastcall sub_9950(JNIEnv *a1, jclass a2, jstring a3);
根据最后一行 return (loc_9B98)(v25, v26, v23),分别找一下每个参数是啥,快捷键x查找引用,发现v25 =a1,v26=v2,v26=a3更改一下类型。
v27 = (*v9)->FindClass(v9, "org/keplerproject/luajava/LuaStateFactory"); v28 = (*v9)->GetStaticMethodID(v9, v27, "newLuaState", "()Lorg/keplerproject/luajava/LuaState;"); v11 = (sub_9E98)(v9, v27, v28); v10 = (*v9)->GetObjectClass(v9, v11); a1 = *v9;
sub_9E98点进去是这样的,结合上面的参数v9 env,v27 class ,v28 MethodID,好像是一个CallStaticObjectMethod 不知道为啥长这样
int __fastcall sub_9E98(int a1, int a2, int a3, int a4)
{
return (*(*a1 + 460))(a1);
}然后试着看一下这块代码,会打开一个文件,然后朝里面写入内容,最后在删除,所以现在想办法拿到这个lua脚本,可以通过hook write函数,或者nop remove 函数
syscall(6, v31);
doFile = (*v9)->GetMethodID(v9, v10, "LdoFile", "(Ljava/lang/String;)I");
v33 = open("/data/data/com.zhuotong.antiapk/cadhe", 66, 384);
write(v33, &unk_1C000, 0x5D6u);
close(v33);
cadhe = (*v9)->NewStringUTF(v9, "/data/data/com.zhuotong.antiapk/cadhe");
(sub_9F08)(v9, LuaState, doFile, cadhe);
remove("/data/data/com.zhuotong.antiapk/cadhe");
getGlobal = (*v9)->GetMethodID(v9, v10, "getGlobal", "(Ljava/lang/String;)V");
RSB_str = (*v9)->NewStringUTF(v9, "RSB");
(sub_9ED0)(v9, LuaState, getGlobal, RSB_str);
pushJavaObject = (*v9)->GetMethodID(v9, v10, "pushJavaObject", "(Ljava/lang/Object;)V");
v38 = (*v9)->FindClass(v9, "java/lang/Object");
(sub_9ED0)(v9, LuaState, pushJavaObject, v38);
pushString = (*v9)->GetMethodID(v9, v10, "pushString", "(Ljava/lang/String;)V");
v40 = (*v9)->NewStringUTF(v9, "82ns65ig1");
(sub_9ED0)(v9, LuaState, pushString, v40);
(sub_9ED0)(v9, LuaState, pushString, v47[65]);
pcall = (*v9)->GetMethodID(v9, v10, "pcall", "(III)I");
(sub_9F08)(v9, LuaState, pcall, 3, 1, 0);
v42 = (*v9)->GetMethodID(v9, v10, "toString", "(I)Ljava/lang/String;");
v43 = (sub_9F40)(v9, LuaState, v42, -1);
v44 = (*v9)->GetMethodID(v9, v10, "close", "()V");
(sub_9ED0)(v9, LuaState, v44);
v45 = (*v9)->GetStringUTFChars(v9, v43, 0);
strcmp("95 01 3C 79 F8 4A 84 85 B6 2F 2E 9A F4 AB A5 73 ", v45);直接nop remove,然后一运行闪退了(不知道为啥,也算是成功了吧,然后直接去对应路径去找就行了
function nop(addr) {
Memory.patchCode(ptr(addr), 4, code => {
const cw = new ThumbWriter(code, { pc: ptr(addr) });
cw.putNop();
cw.flush();
});
}
function bypass() {
let module = Process.findModuleByName("libanti.so")
nop(module.base.add(0x9CD2 + 1))
}hook write 函数
function hooklua() {
Interceptor.attach(Module.findExportByName(null, "open"), {
onEnter: function (args) {
var path = Memory.readUtf8String(args[0]);
this.path = path;
},
onLeave: function (retval) {
if (this.path.indexOf("cadhe") > 0) {
hookWrite(retval.toInt32())
}
}
});
}
function hookWrite(fd) {
Interceptor.attach(Module.findExportByName(null, "write"), {
onEnter: function (args) {
var fd1 = args[0].toInt32();
if (fd1 == fd) {
console.log("成功读取!", args[1], args[2])
var file = new File("/data/data/com.zhuotong.antiapk/script.lua", "wb");
file.write(Memory.readByteArray(args[1], args[2].toUInt32()))
file.flush()
}
}
});
}然后用hex看了一下好像是lua的字节码

然后用反编译工具拖进去就能编译了,看着像是rc4的
function setText(arg_1_0, arg_1_1)
arg_1_0:setText("set by Lua: " .. arg_1_1)
arg_1_0:setTextSize(50)
end
function tog(arg_2_0, arg_2_1)
return
end
function add(arg_3_0, arg_3_1)
return arg_3_0 + arg_3_1
end
function RSA(arg_4_0)
local var_4_0 = string.len(arg_4_0)
local var_4_1 = {}
local var_4_2 = {}
for iter_4_0 = 0, 255 do
var_4_1[iter_4_0] = iter_4_0
end
var_4_1[3] = 23
var_4_1[8] = 57
for iter_4_1 = 1, var_4_0 do
var_4_2[iter_4_1 - 1] = string.byte(arg_4_0, iter_4_1, iter_4_1)
end
local var_4_3 = 0
for iter_4_2 = 0, 255 do
var_4_3 = (var_4_3 + var_4_1[iter_4_2] + var_4_2[iter_4_2 % var_4_0]) % 256
var_4_1[iter_4_2], var_4_1[var_4_3] = var_4_1[var_4_3], var_4_1[iter_4_2]
end
return var_4_1
end
function PRGA(arg_5_0, arg_5_1)
local var_5_0 = 0
local var_5_1 = 0
local var_5_2 = {}
for iter_5_0 = 1, arg_5_1 do
var_5_0 = (var_5_0 + 1) % 256
var_5_1 = (var_5_1 + arg_5_0[var_5_0]) % 256
arg_5_0[var_5_0], arg_5_0[var_5_1] = arg_5_0[var_5_1], arg_5_0[var_5_0]
var_5_2[iter_5_0] = arg_5_0[(arg_5_0[var_5_0] + arg_5_0[var_5_1]) % 256]
end
return var_5_2
end
local function var_0_0(arg_6_0)
arg_6_0 = string.gsub(arg_6_0, "(.)", function(arg_7_0)
return string.format("%02X ", string.byte(arg_7_0))
end)
return arg_6_0
end
function RSB(arg_8_0, arg_8_1, arg_8_2)
local var_8_0 = string.len(arg_8_2)
local var_8_1 = RSA(arg_8_1)
local var_8_2 = PRGA(var_8_1, var_8_0)
return var_0_0(sxor(arg_8_0, var_8_2, arg_8_2))
end
function sxor(arg_9_0, arg_9_1, arg_9_2)
local var_9_0 = string.len(arg_9_2)
local var_9_1
local var_9_2 = {}
for iter_9_0 = 1, var_9_0 do
local var_9_3 = string.byte(arg_9_2, iter_9_0, iter_9_0)
var_9_2[iter_9_0] = string.char(bxor(arg_9_1[iter_9_0], var_9_3))
end
return table.concat(var_9_2)
end
local var_0_1 = {
cond_and = function(arg_10_0, arg_10_1)
return arg_10_0 + arg_10_1 == 2 and 1 or 0
end,
cond_xor = function(arg_11_0, arg_11_1)
return arg_11_0 + arg_11_1 == 1 and 1 or 0
end,
cond_or = function(arg_12_0, arg_12_1)
return arg_12_0 + arg_12_1 > 0 and 1 or 0
end
}
function var_0_1.base(arg_13_0, arg_13_1, arg_13_2)
if arg_13_1 < arg_13_2 then
arg_13_1, arg_13_2 = arg_13_2, arg_13_1
end
local var_13_0 = 0
local var_13_1 = 1
while arg_13_1 ~= 0 do
r_a = arg_13_1 % 2
r_b = arg_13_2 % 2
var_13_0 = var_13_1 * var_0_1[arg_13_0](r_a, r_b) + var_13_0
var_13_1 = var_13_1 * 2
arg_13_1 = math.modf(arg_13_1 / 2)
arg_13_2 = math.modf(arg_13_2 / 2)
end
return var_13_0
end
function bxor(arg_14_0, arg_14_1)
return var_0_1.base("cond_xor", arg_14_0, arg_14_1)
end
function band(arg_15_0, arg_15_1)
return var_0_1.base("cond_and", arg_15_0, arg_15_1)
end
function bor(arg_16_0, arg_16_1)
return var_0_1.base("cond_or", arg_16_0, arg_16_1)
end然后直接丢给豆包吧分析一下
-- 1. 文本设置函数(UI 相关,若没有 UI 环境,可注释或保留)
function setText(view, text)
view:setText("set by Lua: " .. text)
view:setTextSize(50)
end
-- 2. 空函数(预留,可忽略)
function tog()
return
end
-- 3. 简单加法
function add(a, b)
return a + b
end
-- 4. 类 RC4 密钥调度(KSA)
--加密盒子
function RSA(key)
local keyLen = string.len(key)
local sBox = {}
local keyBytes = {}
-- 初始化 S 盒
for i = 0, 255 do
sBox[i] = i
end
-- 硬编码扰乱(逆向得到的固定值)
sBox[3] = 23
sBox[8] = 57
-- 转换密钥为字节数组
for i = 1, keyLen do
keyBytes[i - 1] = string.byte(key, i)
end
local j = 0
-- 扰乱 S 盒
for i = 0, 255 do
j = (j + sBox[i] + keyBytes[i % keyLen]) % 256
sBox[i], sBox[j] = sBox[j], sBox[i]
end
return sBox
end
-- 5. 类 RC4 伪随机生成(PRGA)
function PRGA(sBox, length)
local i = 0
local j = 0
local keystream = {}
for _ = 1, length do
i = (i + 1) % 256
j = (j + sBox[i]) % 256
-- 交换 S 盒元素
sBox[i], sBox[j] = sBox[j], sBox[i]
-- 生成密钥流字节
keystream[#keystream + 1] = sBox[(sBox[i] + sBox[j]) % 256]
end
return keystream
end
-- 6. 字节转十六进制格式化
local function formatHex(data)
return string.gsub(data, "(.)", function(char)
return string.format("%02X ", string.byte(char))
end)
end
-- 7. 核心加密函数(RSB,整合 KSA + PRGA + 异或)
-- @param input: 待加密的输入字符串
-- @param key: 加密密钥
-- @param context: 上下文字符串(参与异或的辅助数据)
function RSB(input, key, context)
local contextLen = string.len(context)
-- 加密盒子
local sBox = RSA(key)
local keystream = PRGA(sBox, contextLen)
return formatHex(sxor(input, keystream, context))
end
-- 8. 异或操作(核心加密步骤)
function sxor(input, keystream, context)
local contextLen = string.len(context)
local result = {}
for i = 1, contextLen do
local contextByte = string.byte(context, i)
-- 异或运算(依赖 bxor 函数)
result[i] = string.char(bxor(keystream[i], contextByte))
end
return table.concat(result)
end
-- 9. 位运算工具(通过条件函数模拟)
local BitOps = {
cond_and = function(a, b)
return (a + b) == 2 and 1 or 0
end,
cond_xor = function(a, b)
return (a + b) == 1 and 1 or 0
end,
cond_or = function(a, b)
return (a + b) > 0 and 1 or 0
end,
-- 通用位运算入口
base = function(opName, a, b)
if a < b then
a, b = b, a
end
local result = 0
local bitWeight = 1
while a ~= 0 do
local bitA = a % 2
local bitB = b % 2
-- 调用对应的条件函数
result = bitWeight * BitOps[opName](bitA, bitB) + result
bitWeight = bitWeight * 2
a = math.floor(a / 2)
b = math.floor(b / 2)
end
return result
end
}
-- 10. 位异或(对外暴露的简洁接口)
function bxor(a, b)
return BitOps.base("cond_xor", a, b)
end
-- 11. 位与(可选,脚本中未直接用到,保留接口)
function band(a, b)
return BitOps.base("cond_and", a, b)
end
-- 12. 位或(可选,脚本中未直接用到,保留接口)
function bor(a, b)
return BitOps.base("cond_or", a, b)
end
-- ===================== 测试入口 =====================
-- 替换为逆向得到的真实参数
local input = "" -- arg_8_0:空字符串(或实际输入)
local encryptionKey = "82ns65ig1" -- arg_8_1:固定密钥
local contextStr = "" -- arg_8_2:上下文字符串(需 Hook 得到真实值)
local encrypted = RSB(input, encryptionKey, contextStr)
print("加密结果(十六进制):", encrypted)
print("预期 Flag:95 01 3C 79 F8 4A 84 85 B6 2F 2E 9A F4 AB A5 73 ")然后解密脚本
def RSA(key: str):
key_len = len(key)
sbox = list(range(256))
key_bytes = [ord(c) for c in key]
# 特殊扰动
sbox[3] = 23
sbox[8] = 57
j = 0
for i in range(256):
j = (j + sbox[i] + key_bytes[i % key_len]) % 256
sbox[i], sbox[j] = sbox[j], sbox[i]
return sbox
def PRGA(sbox, length: int):
i = j = 0
keystream = []
for _ in range(length):
i = (i + 1) % 256
j = (j + sbox[i]) % 256
sbox[i], sbox[j] = sbox[j], sbox[i]
keystream.append(sbox[(sbox[i] + sbox[j]) % 256])
return keystream
def bxor(a, b):
return a ^ b
def decrypt(cipher_hex: str, key: str):
# 解析密文
cipher_bytes = bytes.fromhex(cipher_hex)
# 初始化 RC4-like
sbox = RSA(key)
keystream = PRGA(sbox, len(cipher_bytes))
# 异或还原 context
result = bytes([bxor(c, k) for c, k in zip(cipher_bytes, keystream)])
return result
if __name__ == "__main__":
key = "82ns65ig1"
cipher_hex = "95 01 3C 79 F8 4A 84 85 B6 2F 2E 9A F4 AB A5 73 "
plain = decrypt(cipher_hex, key)
print("解密得到的上下文字符串 (context):", plain.decode("utf-8", errors="replace"))
print("原始字节:", plain)解密得到的上下文字符串 (context): flag{aj5oyjs1kf}原始字节: b'flag{aj5oyjs1kf}'
最后,就是不知道为啥hook不到动态注册,脚本是这个,和返回的时候return (loc_9B98)(v25, v26, v23);不知道这是干啥的,跳过去F5的逻辑感觉不对,萌新一枚勿喷,有错误的地方求指正,谢了
function hook_RegisterNatives() {
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
}
}
if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3]);
var env = args[0];
var java_class = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
}
}
});
}
}
function hook_dlopen() {
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
onEnter: function (args) {
var pathPtr = args[0];
var path = ptr(pathPtr).readCString();
if (path.indexOf("libanti.so") > 0) {
hook_RegisterNatives()
console.log(path, "hook成功")
}
},
onLeave: function (retval) {
console.log("[android_dlopen_ext] Library loaded successfully.");
}
});
}
setImmediate(hook_dlopen);
setImmediate(hook_RegisterNatives);