
简介这是一份针对安卓平台Gamex木马的分析报告与配套源码资料面向移动安全初学者、逆向分析人员及关注安卓恶意软件防护的开发者。报告从安卓系统基础架构讲起详细拆解Gamex木马的自启动、图标隐藏、数据窃取、广告注入与后门功能等行为特征并结合源码中的变量名混淆、代码跳跃、动态加载等技术说明逆向分析方法。压缩包共7个文件包含Python解密脚本、Java解密代码、Word分析报告、文本说明同时附有原始apk样本与rar压缩包总计约626KB目录安排便于按分析流程逐步查阅。目前已有127人学习下载。通过阅读源码和运行解密脚本读者可以了解恶意APK的静态分析思路掌握加密数据还原、动态加载样本定位等实用技能并据此制定有效的设备检测与防护策略适合作为移动安全入门和恶意代码分析的实战参考资料。1. 为什么一个伪装成SD-Booster的Gamex值得拆一遍我手上这份《安卓Android源码——Gamex木马分析报告.zip》是老样本了但每次翻出来都有新东西。压缩包里除了分析报告doc还有gamex.rar样本、三个解密脚本decrypt_apk.py、decrypt_url.py、decrypt.java以及一个SD-Booster_1.5.2原版.apk。这个木马伪装成安卓系统清理工具装完就隐藏图标自动开机自启把通讯录和短信往C2服务器传。最大的坑在于它的URL和标识符全部加密静态看APK只能看到一串看似随机的字节。写这篇东西是想把从压缩包到明文的完整链路讲清楚哪些文件是干什么的、解密脚本怎么用、以及最后怎么把结论变成可用的检测特征。适合正在做安卓逆向、恶意样本分析或威胁狩猎的同行。2. Gamex源码结构与Android层隐藏机制拆解2.1 压缩包文件布局对应分析阶段拿到zip先别急着解压先看目录。这份包里的文件不是随手放的基本对应了一个标准分析流程文件类型分析阶段用途Gamex木马分析报告.doc文档报告说明记录行为概述gamex.rar压缩包原始样本或加密后的样本SD-Booster_1.5.2原版.apkAPK木马伪装的原版App用于对比decrypt_apk.pyPython脚本从APK中提取加密字符串或解密dexdecrypt_url.pyPython脚本解密C2 URLdecrypt.javaJava源码Java层对应的解密逻辑icon.txt文本隐藏图标相关数据或伪装图标配置注意gamex.rar里的内容可能是经过二次加密的dex也可能是原始smali。通常rar里还有一层密码后面会在第五章用zip压缩包密码破解工具来说。我是先用原版SD-Booster和样本APK做diff的。因为木马在植入时总会在原版基础上加东西diff一下能快速找到新增的类和方法不过这类隐藏型木马会把恶意代码放在assets或加密目录下所以diff之后还要接着看dex加载逻辑。2.2 Android源码层的自启动与图标隐藏原理从源码角度看Gamex实现隐藏图标的方式并不罕见通过PackageManager把入口Activity的enabled状态设为0让图标从桌面消失但组件仍然可以响应系统广播。样本里通常在Application.onCreate里先启动一个后台服务服务里再调用下面的逻辑// 隐藏桌面图标 ComponentName component new ComponentName(getPackageName(), getPackageName() .MainActivity); getPackageManager().setComponentEnabledSetting( component, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP ); // 注册开机自启广播 IntentFilter filter new IntentFilter(); filter.addAction(Intent.ACTION_BOOT_COMPLETED); registerReceiver(new BootReceiver(), filter);这段代码的逻辑是先拿到入口组件的ComponentName然后通过setComponentEnabledSetting把它禁用桌面图标就消失了。注意这里的DONT_KILL_APP参数禁用组件时不会杀掉进程这样后台服务还在跑。紧接着注册一个监听BOOT_COMPLETED的广播接收器所以重启后又能自启。对于分析人员来说在模拟器上复现时会发现pm list packages仍能看到这个应用但启动器里没有入口。所以判断一个APK是否隐藏图标不能只看桌面要看AndroidManifest里是否有ACTION_BOOT_COMPLETED权限和接收器以及Application类是否在启动时调用了类似上面的代码。这部分正好用到安卓源码里常见的PackageManager机制理解了它后面看动态加载和字符串解密才不迷糊。2.3 混淆与动态加载Gamex对抗静态检测的手段报告里说的三种混淆我拆的时候都遇到了。变量名和函数名随机化是基础比如反编译出来的类名是a.a.a方法名是a()控制流通过if/else套while来实现“代码跳跃”让静态阅读很难跟上最后还用了动态加载真正的恶意逻辑不在主dex里而是运行时从assets目录读一段加密数据在native层解密后再用DexClassLoader加载。我一般先跑一遍公开的脱壳工具但Gamex没壳只是伪装得比较深。在jadx里看就是几个类在互相调用真正有用的字符串全被塞到一个native方法里返回。这时候别硬跟native优先把decrypt_url.py拿出来因为它解的就是native层返回的那串密文。先把静态分析放一放直接看脚本要解什么。比如我在jadx里跟踪a.a.a时它循环了200多次才拼接出一个字符串实际上每次循环只是在做无效运算真正结果早就在寄存器里了。遇到这种代码跳跃别一行行跟直接在smali里搜索const-string和invoke-direct把可疑方法入口记下来再用frida动态调。Gamex选择动态加载而不是直接写进dex说明作者预期到静态分析会先扫dex。3. 解密脚本落地从APK中把C2域名剥出来3.1 decrypt_apk.py定位加密字符串的通用套路解密的第一步不是猜算法而是先把APK里所有可疑的长字符串捞出来。decrypt_apk.py的思路是先以zipfile方式打开APK遍历每个dex和资源文件用正则把形如[A-Za-z0-9/]{32,}的字符串全提取出来再交给decrypt_url.py判断是不是那一层加密。#!/usr/bin/env python3 # decrypt_apk.py 用于从APK中提取候选密文 import zipfile, re, sys def extract_candidates(apk_path): candidates [] with zipfile.ZipFile(apk_path) as z: for name in z.namelist(): if not (name.endswith(.dex) or name.endswith(.xml) or name.endswith(.txt)): continue data z.read(name) # 常见base64变体和十六进制串 pat re.compile(rb[A-Za-z0-9/]{32,}) for m in pat.findall(data): s m.decode(latin1) candidates.append((name, s)) return candidates if __name__ __main__: for name, s in extract_candidates(sys.argv[1]): print(name, s)这个脚本有几个参数可以调整正则里的{32,}是最小长度太短会捞到一堆无关字符串太长会漏掉短KEY按文件类型过滤如果目标木马把密文放在资源文件里就把resources.arsc也加入过滤条件。拿到候选串后不要盲目去解先统计长度分布Gamex的C2 URL一般加密后是64字节或96字节看到这类长度优先尝试。3.2 decrypt_url.pyGamex的URL解密算法还原从样本里挖出来的解密逻辑是“变换过的Base64 单字节异或”。我先还原了Python版本方便直接在命令行里批量验证#!/usr/bin/env python3 # decrypt_url.py 还原Gamex的URL加密 import sys, base64 def custom_b64_decode(s): # Gamex把标准表换过映射关系见decrypt.java中的b64_table alt_table vwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstu0123456789/ std_table ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/ s s.translate(str.maketrans(alt_table, std_table)) return base64.b64decode(s) def decrypt_url(cipher_b64, xor_key0x5A): raw custom_b64_decode(cipher_b64) # 单字节异或按奇偶位用不同key也是常见变体 plain bytes([b ^ xor_key for b in raw]) return plain.decode(utf-8, errorsignore) if __name__ __main__: # 用法: python3 decrypt_url.py 密文 print(decrypt_url(sys.argv[1]))逻辑说明首先把Gamex自定义的Base64字母表映射回标准表再做标准base64解码然后对每个字节异或同一个key。参数xor_key是脚本中最重要的开关样本里是0x5A但不同变体可能换0x3C或0x77。遇到解出来是乱码先用len()看长度再试异或key 0到255通常能找到落点。解密后得到的URL通常带有/gate.php?id之类的路径这就对应上后面流量侧的特征了。3.3 decrypt.javaJava层解密逻辑与Smali补丁报告附带的decrypt.java其实就是上面Python函数的Java实现。分析时保留这份Java版本很有用因为你最终要在smali里插入日志或者直接调用它验证运行值。下面是一段核心代码对应C2 URL的AES解密场景// decrypt.java 片段用于AES/CBC解密Gamex下发的配置 import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class Decrypt { static byte[] key GamexK3y#2024.getBytes(); // 实际样本中是硬编码 static byte[] iv 0102030405060708.getBytes(); public static String aesDecrypt(byte[] cipher) throws Exception { Cipher c Cipher.getInstance(AES/CBC/PKCS5Padding); c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, AES), new IvParameterSpec(iv)); return new String(c.doFinal(cipher), UTF-8); } }注意这个Java版本和Python版本不是同一套Java版用于解密运行时服务端下发的指令Python版用于离线解密静态URL。两者最好不要混用。在smali里需要调用时直接把这段代码编译得到classes.jar再用d2j-dex2jar转dex或者用jadx把修改后的Java编译成smali后替换原类。4. 实战复现用SD-Booster原版APK跑一遍完整还原流程4.1 工具链准备与APK校验这一步需要在ubuntu或macOS上操作windows也行但很多命令不一样。我一般在android studio android sdk环境下另外再装apktool、jadx、python3和jemalloc调试工具。先对原版APK做哈希确保对比的基准没问题sha256sum SD-Booster_1.5.2原版.apk Gamex.apk # 输出两个哈希值如果样本本身是重打包的哈希一定不同参数说明sha256sum是Linux自带命令windows可用certutil -hashfile替代。拿到两个哈希后记录下来后续重打包验证时能快速判断APK是否被改动过。工具版本注意用新版apktool老版本对v2签名和资源混淆支持不好。4.2 提取dex并定位Gamex特征用apktool解包样本然后直接grep特征字符串。虽然核心字符串加密了但权限、包名、接收器名称这些还留在Manifest里apktool d -f Gamex.apk -o gamex_out grep -r BOOT_COMPLETED gamex_out/AndroidManifest.xml grep -r flurry\|umeng\|adjust gamex_out --include*.smali这里-f是强制覆盖输出目录grep第一行定位开机广播权限第二行看第三方统计SDK特征。Gamex经常会伪装成统计SDK的名字来命名自己的类所以搜到flurry或umeng时不要直接判定为广告SDK要打开smali看里面是否有loadClass和DexClassLoader调用。若看到DexClassLoader基本可以确定有动态加载下一步就去找assets里加载的文件名。4.3 还原动态加载的DEX与整体行为链动态加载的dex往往以assets/xxx.bin形式存在。我在脚本里定位到是assets/gamex.dat用下面的Python脚本拉出来解密# 提取并还原动态加载dex from Crypto.Cipher import AES with open(assets/gamex.dat, rb) as f: data f.read() # 前16字节是IV后面是密文 iv data[:16] body data[16:] cipher AES.new(bGamexK3y#2024, AES.MODE_CBC, iv) dex cipher.decrypt(body) dex dex[:dex.rfind(b\x00) 1] # 去掉尾部填充 open(gamex_decrypted.dex, wb).write(dex)说明这里假设加密算法是AES/CBCkey和decrypt.java里一致。尾部用rfind(b\x00)去填充只是一种简化遇到PKCS5Padding时应该根据最后一个字节的值来截断。解密后把dex拖进jadx可以看到真正的远程指令处理逻辑比如接收cmdupload_contact就上传通讯录cmdshow_ad就注入广告。行为链基本就清晰了开机→自启→解密assets→加载dex→连接C2→按指令执行。这种从服务端下发指令串的做法本质上和一句话木马按参数执行命令的思路一样只是载体从PHP文件变成了DEX。4.4 绕过签名校验与重打包验证在模拟器里验证时经常会遇到“应用已停止”或“签名不符”。Gamex会对自身签名做一次校验方法多半是调用PackageManager.getPackageInfo后和硬编码的signature比较。我在smali里找到校验点直接把条件判断反转# 原逻辑: if-eqz v0, :cond_no_sig # 改为: if-nez v0, :cond_no_sigsafe路径即为原签名失败分支改if-eqz为if-nez后只要签名不匹配就走安全分支绕过校验。改完用apktool重打包apktool b gamex_out -o gamex_patched.apk keytool -genkey -v -keystore test.keystore -alias test -keyalg RSA -keysize 2048 -validity 10000 apksigner sign --ks test.keystore --out gamex_signed.apk gamex_patched.apk注意重打包后的APK不要直接装到主力机上这个操作已经改变了应用完整性只在隔离环境分析用。apksigner是android sdk build-tools里的命令如果报错Failed to read signer说明你先要zipalign对齐。整个流程走完你可以动态看它被解密后的DEX流量。5. 进阶技巧把分析结论变成可复用的检测特征5.1 从解密后的明文中提炼YARA规则解密得到的C2 URL和指令字符串是很好的检测源。我一般会把明文特征和加密特征各做一条规则让新样本即使改了URL也能被加密表特征命中rule Gamex_dynamic_dex { meta: author analyst strings: $a { 76 77 78 79 7A 41 42 } // vwxyzAB $b gate.php?id ascii $c upload_contact ascii condition: any of them }这段规则匹配了自定义Base64表头、URL路径和指令名。注意$a在解密前的DEX中可能被编码最好同时放一份用于assets密文的字节特征。在VT上拉长阈值可以减少误报。5.2 带密码样本与zip压缩包密码破解工具的应用gamex.rar如果设置了密码直接用unrar解不开。在授权分析时我会先用rar2john把rar包转成john可读的hash再跑字典。如果换到zip样本再用zip压缩包密码破解工具里的zip2john。命令# rar样本 rar2john gamex.rar gamex.hash # zip样本 zip2john gamex.zip gamex_hash_zip.txt # 跑字典 john --wordlist/usr/share/wordlists/rockyou.txt gamex.hash说明rar2john和zip2john是hashcat/john生态里的提取工具本质是把压缩包的加密头转成可被john爆破的格式。参数--wordlist指定字典路径如果没有rockyou.txt先执行gunzip /usr/share/wordlists/rockyou.txt.gz解压。Gamex的rar有时密码就是infected或virus这类安全人员常用词先试弱口令再上字典效率更高。zip密码移除这类工具更多用于文档winrar样本用它不划算不如john好使。5.3 用HTTP流量特征反推C2当动态样本起来后抓包能直接看到C2通信。Gamex的URL参数固定命令用tcpdump就能看到tcpdump -i eth0 -A tcp port 80 and (dst port 80) | grep gate.php通常它会在POST请求的body里用data传设备信息头部有自定义的x-gamex-token字段。把这两个特征加到IDS规则里和YARA形成互补。现在很多变体会改用HTTPS那时还需要额外在frida里hookSSL_write层拿明文这步就到工具链的另一块了。本文还有配套的精品资源点击获取