文章目录
⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。
本章内容是对上一章学习内容的夯实,使用相似的案例进行强化记忆。
1. 基础概念
1.1 原生函数(Native函数)
原生函数指通过JNI(Java Native Interface)机制调用的、由C/C++等原生语言实现的函数。在Android开发中,这类函数通常被编译为动态链接库(.so文件),并通过System.loadLibrary()加载到Java进程中。原生函数常用于处理性能敏感、底层交互(如硬件操作)或需要保护逻辑(如加密验证)的场景,案例中的check_flag()就是典型的原生函数。
1.2 函数返回值修改的意义
修改原生函数的返回值是逆向工程和动态调试中的常用手段。当程序逻辑依赖原生函数的返回值(如验证结果、权限判断、数据校验等)时,通过hook技术强制修改返回值,可以绕过原有逻辑限制(如跳过验证、模拟成功状态),从而快速分析程序行为或实现特定功能(如案例中触发"成功"提示)。
2. 核心语法(修改原生函数返回值的hook)
使用Frida实现对原生函数返回值的修改,核心语法围绕Java层函数拦截展开,主要步骤如下:
2.1 获取目标类
通过Java.use(className)获取需要hook的类的引用,其中className为类的完整路径(包含包名)。
 示例:
// 获取MainActivity类引用
const MainActivity = Java.use('com.ad2001.a0x9.MainActivity');
 
2.2 定位目标原生函数
直接通过类引用访问原生函数(函数名需与Java层声明一致)。原生函数在Java层的声明与普通函数一致,仅多了native关键字,Frida无需区分其实现语言,统一按Java函数处理。
源码:
public class MainActivity extends AppCompatActivity {
    public native int check_flag();
    static {
        System.loadLibrary("a0x9");
    }
    // ...
}
 
Hook代码:
// 定位check_flag()原生函数
MainActivity.check_flag
 
2.3 重写函数实现
通过implementation属性重写函数逻辑,在自定义实现中直接返回目标值(无需调用原函数)。
 示例:
// 强制check_flag()返回1
MainActivity.check_flag.implementation = function () {
    return 1; // 自定义返回值
};
 
3. 案例分析
本章示例应用的链接:
https://pan.baidu.com/s/16EE2XE-OZS_xBRPlWUODbw?pwd=n2vb
提取码: n2vb
使用APK:Challenge 0x9.apk
3.1 代码分析(MainActivity)

Java层核心代码(MainActivity)
package com.ad2001.a0x9;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.ad2001.a0x9.databinding.ActivityMainBinding;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    Button btn;
    // 声明原生函数(实现位于liba0x9.so)
    public native int check_flag();
    static {
        System.loadLibrary("a0x9"); // 加载原生库
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding activityMainBindingInflate = ActivityMainBinding.inflate(getLayoutInflater());
        this.binding = activityMainBindingInflate;
        setContentView(activityMainBindingInflate.getRoot());
        Button button = (Button) findViewById(R.id.button);
        this.btn = button;
        // 按钮点击事件:依赖check_flag()的返回值判断逻辑
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
                if (MainActivity.this.check_flag() == 1337) { // 核心判断条件
                    try {
                        // AES解密逻辑(成功时执行)
                        Cipher cipher = Cipher.getInstance("AES");
                        SecretKeySpec secretKeySpec = new SecretKeySpec("3000300030003003".getBytes(), "AES");
                        cipher.init(2, secretKeySpec);
                        byte[] decryptedBytes = Base64.getDecoder().decode("hBCKKAqgxVhJMVTQS8JADelBUPUPyDiyO9dLSS3zho0=");
                        String decrypted = new String(cipher.doFinal(decryptedBytes));
                        Toast.makeText(MainActivity.this.getApplicationContext(), "You won " + decrypted, 1).show();
                        return;
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
                Toast.makeText(MainActivity.this.getApplicationContext(), "Try again", 1).show();
            }
        });
    }
}
 
案例中的MainActivity是一个简单的Android页面,核心逻辑如下:
-  
原生函数声明:
声明了native int check_flag(),该函数的实现位于liba0x9.so中(通过static代码块的System.loadLibrary("a0x9")加载)。 -  
按钮点击事件:
点击按钮时触发onClick事件,逻辑分为两步:- 调用
check_flag(),判断返回值是否为1337; - 若返回值为1337,使用AES算法解密预定义的Base64字符串(
hBCKKAqgxVhJMVTQS8JADelBUPUPyDiyO9dLSS3zho0=),密钥为3000300030003003,解密成功后显示"You won + 解密结果"; - 若返回值不是1337,显示"Try again"。
 
 - 调用
 -  
核心依赖:
程序的"成功"逻辑完全依赖check_flag()的返回值,因此只要修改该函数的返回值为1337,即可绕过原生层验证,直接触发解密流程。 
3.2 Hook思路
基于上述代码分析,hook的核心目标是让check_flag()强制返回1337,具体思路如下:
-  
确定hook点:
目标函数为com.ad2001.a0x9.MainActivity类中的check_flag(),无需关心其原生实现(无论liba0x9.so中如何计算返回值,直接覆盖即可)。 -  
编写hook脚本:
使用Frida的Java桥接API,获取MainActivity类引用,重写check_flag()的实现,固定返回1337。 -  
注入脚本到目标进程:
通过Python脚本连接设备、启动目标应用进程(com.ad2001.a0x9)、附加进程并注入JS脚本,实现动态hook。 
3.3 核心源码与脚本
3.3.1 Frida Hook脚本(JS)
import Java from 'frida-java-bridge';
Java.perform(function () {
    // 1. 获取目标类
    const MainActivity = Java.use('com.ad2001.a0x9.MainActivity');
    
    // 2. 重写原生函数check_flag(),强制返回1337
    MainActivity.check_flag.implementation = function () {
        console.log("[Hook] 拦截check_flag(),返回1337");
        return 1337; // 覆盖原返回值
    };
});
 
3.3.2 注入脚本(Python)
import frida
import sys
import time
def on_message(message, data):
    if message['type'] == 'send':
        print(f"[Hook 日志] {message['payload']}")
    elif message['type'] == 'error':
        print(f"[错误] {message['stack']}")
# 目标应用包名
PACKAGE_NAME = "com.ad2001.a0x9"
def main():
    try:
        # 连接USB设备
        device = frida.get_usb_device(timeout=10)
        print(f"已连接设备:{device.name}")
        # 启动目标进程
        print(f"启动进程 {PACKAGE_NAME}...")
        pid = device.spawn([PACKAGE_NAME])
        device.resume(pid)
        time.sleep(2)  # 等待进程启动
        # 附加到进程并注入脚本
        process = device.attach(pid)
        print(f"已附加到进程 PID: {pid}")
        with open("./js/compiled_hook.js", "r", encoding="utf-8") as f:
            js_code = f.read()
        script = process.create_script(js_code)
        script.on('message', on_message)
        script.load()
        print("JS 脚本注入成功,开始监控...(按 Ctrl+C 退出)")
        sys.stdin.read()
    except frida.TimedOutError:
        print("未找到USB设备")
    except frida.ProcessNotFoundError:
        print(f"应用 {PACKAGE_NAME} 未安装")
    except FileNotFoundError:
        print("未找到 js 脚本,请检查路径")
    except Exception as e:
        print(f"异常:{str(e)}")
    finally:
        if 'process' in locals():
            process.detach()
        print("程序退出")
if __name__ == "__main__":
    main()
 
3.4 成功效果
执行上述脚本后,点击应用按钮,check_flag()被hook强制返回1337,触发AES解密流程,成功显示flag:

4. 技术总结
-  
核心原理:修改原生函数返回值的本质是通过hook技术拦截函数调用,用自定义返回值覆盖原逻辑。对于依赖返回值的程序(如验证、权限判断),该方法可快速绕过限制。
 -  
Frida的优势:作为动态hook工具,Frida无需修改原程序代码或重打包,支持跨平台(Android、iOS等),且通过Java桥接API可轻松操作Java层函数(包括原生函数),降低了hook的技术门槛。
 -  
关键注意事项:
- 需准确获取类的完整路径(包名+类名)和函数名,否则无法定位目标;
 - 进程附加时机需合理(建议在进程启动初期注入,避免函数已被调用);
 - 若函数有参数,需在重写实现时保持参数列表一致(即使不使用参数)。
 
 -  
适用场景:适用于逆向分析中需要绕过原生层验证、调试函数依赖关系、或临时修改程序行为的场景,是移动端逆向工程的基础技能之一。
 
                  
                  
                  
                  
                            
      
          
                
                
                
                
              
                
                
                
                
                
              
                
                
                    
              
            
                  
					1715
					
被折叠的  条评论
		 为什么被折叠?
		 
		 
		
    
  
    
  
					
					
					


            