首页
社区
课程
招聘
[讨论]一个使用luajava的flag题目,
发表于: 2025-9-27 23:28 413

[讨论]一个使用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);



传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回