HUApp逆向分析技术文章大纲
技术背景与目标
基于Android Studio对HUAPP进行断点调试,解码分析登录接口。对apk进行修改调试
抓包分析
打开目标app点击会员登录,随便输入账号密码触发请求。里面路径是/v2/login.参数由公共参数和data的加密参数组成。公共参数里面处理riskToken是接口返回。blackbox是设备采集。别的字段可以模拟。blackbox可以看往期文章获取这次对data里面的进行解码分析。里面number是账号。pin是密码。tosave是默认true
脱壳技术实践
1.利用adb拉取手机上的apk
查看apk路径
adb shell pm path com.rytong.hnair
拉取apk
adb pull /data/app/com.rytong.hnair-1/base.apk C:\\Users\\Administrator\\Desktop\\
2.修改apk
利用Apktool Box对拉取的apk进行查壳
反编译apk后修改AndroidManifest.xml。对application节点加上android:debuggable="true"
然后点击回编apk,回编之后点击zipalign进行apk对齐。最后进行签名
3.安装修改后的apk
卸载旧版并安装修改后的apk
adb uninstall com.rytong.hnair
adb install "C:\\Users\\Administrator\\Desktop\\hnair_current.apk"
登录流程调试
1.静态分析
全局搜索/v2/login,找到接口声明位置。往上稍微翻一下。找到方法的声明
找到声明方法是loginV2
搜一下里面的参数UserLoginRequest。里面是数据类型,对应data里面的参数
2.动态调试
全局搜索loginV2并打上断点进行触发
进入loginV2跟栈,找到serialize
进入serialize方法,可以看到加密声明
继续进入方法,可以看到密码现在明文被传入HttpRequestRsaFieldEncrypt方法进行处理
继续跟栈找到base64处理那里,已经生成值了。
往上一步,这里进行了加密。看断点内容。里面是rsa加密。往上翻一番可以找到公钥
invoke-virtual {v2, p1}, Ljavax/crypto/Cipher;->doFinal([B)[B

RSA公钥
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCziHP+DnK5og1PAxmgKbrN/dChW6Pq1/8TxKKnc2e2qifoMZbsYy+/I2o6HnzGm3QNxA6ernkU9DptfQ9j+mIv6nmEmMXu0qusTs8hKVzOaX9z7BgwlpvgoN6ZN++lWc2DQVhJs8fafcG0hJuQmsbaKH3mvEQBqJnE0vQHAuBe8wIDAQAB

AES加密
继续跟栈。密码加密后会重新组装。进入外层加密。
继续跟栈。跟到这个方法进去
Lcom/hnair/airlines/base/utils/AES256Encryption

进去方法会发现确实是AES
在return打上断点,此时已经加密结束
往上翻占堆,会发现aes的key
iv生成:继续跟栈。iv是在这个方法生成。将comm的公共数据里面的akey进行截取前16位
invoke-static {p1, v0}, Lkotlin/text/StringsKt;->a1(Ljava/lang/String;I)Ljava/lang/String;
在这个方法拼接上时间戳
sget-object v0, Lkotlin/text/Charsets;->b:Ljava/nio/charset/Charset;
跳出外层AES加密。这里对数据进行“hna”处理。对number和sens进行处理

apk内部防护
正常修改apk后可以自主调试。HUAPP有反调试。回编后的apk调试的时候会触发自我检测。跳出检测到测试连接字段然后退出。是因为apk运行的时候会启动这个函数检测。有反调试
invoke-static {}, Landroid/os/Debug;->isDebuggerConnected()Z
解决办法:hook注入js来跳过检测
// anti.js – 绕过常见的 Java 层反调试检测
Java.perform(function() {
console.log("[+] 反调试脚本已注入…");
// 1. 绕过 isDebuggerConnected() 检测
try {
var Debug = Java.use("android.os.Debug");
Debug.isDebuggerConnected.implementation = function() {
return false;
};
console.log("[+] Hook isDebuggerConnected 成功");
} catch(e) {}
// 2. 绕过 /proc/self/status 中的 TracerPid 检测
try {
var FileInputStream = Java.use("java.io.FileInputStream");
FileInputStream.$init.overload('java.io.File').implementation = function(file) {
var path = file.getAbsolutePath();
if (path.indexOf("/proc/") !== -1 && path.indexOf("/status") !== -1) {
console.log("[+] 检测到读取 status 文件: " + path);
// 注意:真正的完整绕过需要重写 read 方法,此处为简化版,主要针对常见检测
}
return this.$init(file);
};
} catch(e) {}
console.log("[+] 脚本加载完成,等待操作…");
});
python实现
RSA加密源码
PUBLIC_KEY_BASE64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCziHP+DnK5og1PAxmgKbrN/dChW6Pq1/8TxKKnc2e2qifoMZbsYy+/I2o6HnzGm3QNxA6ernkU9DptfQ9j+mIv6nmEmMXu0qusTs8hKVzOaX9z7BgwlpvgoN6ZN++lWc2DQVhJs8fafcG0hJuQmsbaKH3mvEQBqJnE0vQHAuBe8wIDAQAB"
def rsa_encrypt(password: str) -> str:
der_data = base64.b64decode(PUBLIC_KEY_BASE64)
public_key = serialization.load_der_public_key(der_data, backend=default_backend())
ciphertext = public_key.encrypt(
password.encode('utf-8'),
padding.PKCS1v15()
)
return base64.b64encode(ciphertext).decode('utf-8')
AES加密源码
def aes_encrypt(plaintext: str, stime: str, akey: str, add_prefix: bool = True) -> str:
"""
plaintext: 待加密明文
stime/akey: 请求 common 中的字段
add_prefix: 是否添加 APK 使用的 %hna% 前缀
"""
iv_text = (stime + akey)[:16]
iv = iv_text.encode("utf-8")
if len(iv) != 16:
raise ValueError("stime + akey 截取后必须正好生成 16 字节 IV")
cipher = AES.new(KEY, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(pad(plaintext.encode("utf-8"), AES.block_size))
result = base64.b64encode(encrypted).decode("ascii")
return "%hna%" + result if add_prefix else result
安全防护绕过
分析HUApp的反调试机制(如ptrace检测、签名校验等)
使用Frida脚本绕过常见防护措施
修改Smali代码或重打包App,绕过关键校验逻辑
成功验证

法律与伦理边界
- 逆向技术的合法使用范围
- 企业防护与用户隐私的平衡
- 个人观点,禁止商业行为,侵权请联系下架





