12月安卓折腾
Table of Contents
“你怎么搞安卓逆向连个能抓包的机器都没有”
呵呵,花了两天把吃灰的pixel掏出来折腾,难也不难就是折腾
感觉基础知识差好多啊
设备介绍 #
pixel3 安卓12 版本号:blueline-sp1a.210812.016.c1
啊还得是pixel官网就有资料终于不用导出找一加的包了
已安装环境:(点点点工程师)
暂时用magisk了 装不上apatch(有bug) 没时间搞kernelsu +=
reqable 证书
很普通的 frida 16
tips #
- 开机检查时区和时间不然大概率连不上网,也可以运行
adb shell settings put global captive_portal_https_url https://www.google.cn/generate_204
- fastboot刷magisk boot先用
fastboot boot .\magisk_patched-28000_KhnpS.img
再用
fastboot flash boot .\magisk_patched-28000_KhnpS.img
flash的修改是永久的,boot的修改只会作用于当前启动
- pixel3安卓版本12 sdk版本是31,在用安卓死丢丢的时候要改以下参数
在app目录下的build.gradle.kts修改 targetSdk = 31
android {
namespace = "com.example.myapplication"
compileSdk = 34 编译时使用的sdk版本
defaultConfig {
applicationId = "com.example.myapplication"
minSdk = 31
targetSdk = 31 目标机器使用的sdk版本,这两个不一样的
versionCode = 1
versionName = "1.0"
抓到包了 #
教程很详细但有几个注意事项
- 建议magisk一件安装reqable官方模块
- 扫码链接的时候注意网段选择,不然扫半天二维码扫不上很沙比
- 用户证书的安装位置在 设置->安全->加密与凭证->安装证书 or 用户凭证
复现文章草稿 #
原文已经相当详细了
https://bbs.kanxue.com/thread-284444.htm#msg_header_h2_4
- frida模板x2
- 内存映射
“iRrL63tve+H72wjr/HHiwlVu5RZU9XDcI7A=”
native code 想要访问java VM的特性就需要调用JNI函数,调用JNI函数需要JNI interface pointer
JNI interface pointer是一个pointer to pointer,具体来说就是一个指针数组,这个数组保存着JNI函数的地址,包括: JNI 接口的组织方式类似于 C++ 虚拟函数表或 “COM 接口”?
所以,ida看到的table实际上是一个类似于虚表的东西,表嘛,有个表头+偏移然后引用就好了
其中一个libnative的 LOAD segment
LOAD:0000000000000288 ; ELF Symbol Table
LOAD:0000000000000288 Elf64_Sym <0>
LOAD:00000000000002A0 Elf64_Sym <aCxaFinalize - byte_608, 0x12, 0, 0, dword_0, 0> ; "__cxa_finalize"
LOAD:00000000000002B8 Elf64_Sym <aCxaAtexit - byte_608, 0x12, 0, 0, dword_0, 0> ; "__cxa_atexit"
LOAD:00000000000002D0 Elf64_Sym <aRegisterAtfork - byte_608, 0x12, 0, 0, dword_0, 0> ; "__register_atfork"
LOAD:00000000000002E8 Elf64_Sym <aSrand - byte_608, 0x12, 0, 0, dword_0, 0> ; "srand"
LOAD:0000000000000300 Elf64_Sym <aMalloc - byte_608, 0x12, 0, 0, dword_0, 0> ; "malloc"
LOAD:0000000000000318 Elf64_Sym <aMemcpyChk - byte_608, 0x12, 0, 0, dword_0, 0> ; "__memcpy_chk"
LOAD:0000000000000330 Elf64_Sym <aRand - byte_608, 0x12, 0, 0, dword_0, 0> ; "rand"
LOAD:0000000000000348 Elf64_Sym <aStackChkFail - byte_608, 0x12, 0, 0, dword_0, 0> ; "__stack_chk_fail"
LOAD:0000000000000360 Elf64_Sym <aFmod - byte_608, 0x12, 0, 0, dword_0, 0> ; "fmod"
LOAD:0000000000000378 Elf64_Sym <aFree - byte_608, 0x12, 0, 0, dword_0, 0> ; "free"
LOAD:0000000000000390 Elf64_Sym <aMar0ssaa7ze9 - byte_608, 0x12, 0, 0xD, MaR0Ssaa7zE9, \ ; "MaR0Ssaa7zE9"
LOAD:0000000000000390 0x2B0>
LOAD:00000000000003A8 Elf64_Sym <aBegy76tpey0a - byte_608, 0x12, 0, 0xD, beGy76TPEy0a, \ ; "beGy76TPEy0a"
LOAD:00000000000003A8 0x274>
LOAD:00000000000003C0 Elf64_Sym <aHtxbdoojl03v - byte_608, 0x12, 0, 0xD, htxBdOoJL03V, \ ; "htxBdOoJL03V"
LOAD:00000000000003C0 0x270>
LOAD:00000000000003D8 Elf64_Sym <aInit - byte_608, 0x12, 0, 0xD, init, 0x1C> ; "init"
LOAD:00000000000003F0 Elf64_Sym <aIusp9avayomi - byte_608, 0x12, 0, 0xD, iusp9aVAyoMI, 0xB4> ; "iusp9aVAyoMI"
LOAD:0000000000000408 Elf64_Sym <aEeg0quqiztro - byte_608, 0x12, 0, 0xD, eeg0QuqIZtRO, 0xD0> ; "eeg0QuqIZtRO"
LOAD:0000000000000420 Elf64_Sym <aGenerateChaosS - byte_608, 0x12, 0, 0xD, \ ; "generate_chaos_sequence"
LOAD:0000000000000420 generate_chaos_sequence, 0xA0>
LOAD:0000000000000438 Elf64_Sym <aDoubleToByte - byte_608, 0x12, 0, 0xD, double_to_byte, \ ; "double_to_byte"
LOAD:0000000000000438 0x3C>
LOAD:0000000000000450 Elf64_Sym <aZwfl19atrzaj - byte_608, 0x12, 0, 0xD, zWfl19ATrZaj, 0xD0> ; "zWfl19ATrZaj"
LOAD:0000000000000468 Elf64_Sym <aSz3pmtldta7q - byte_608, 0x12, 0, 0xD, SZ3pMtlDTA7Q, \ ; "SZ3pMtlDTA7Q"
LOAD:0000000000000468 0x2BC>
LOAD:0000000000000480 Elf64_Sym <aH4aqfgsoe2df - byte_608, 0x12, 0, 0xD, H4AQFGSOe2Df, \ ; "H4AQFGSOe2Df"
LOAD:0000000000000480 0x284>
LOAD:0000000000000498 Elf64_Sym <aUqhyy0f049n5 - byte_608, 0x12, 0, 0xD, UqhYy0F049n5, \ ; "UqhYy0F049n5"
LOAD:0000000000000498 0x2FC>
LOAD:00000000000004B0 Elf64_Sym <aT6aahj6zpxwi - byte_608, 0x12, 0, 0xD, T6AAHJ6ZpxWI, \ ; "T6AAHJ6ZpxWI"
LOAD:00000000000004B0 0x2FC>
LOAD:00000000000004C8 Elf64_Sym <aZkgafawta806 - byte_608, 0x12, 0, 0xD, zkGaFawtA806, \ ; "zkGaFawtA806"
LOAD:00000000000004C8 0x2DC>
LOAD:00000000000004E0 Elf64_Sym <aNcajgg9kvem4 - byte_608, 0x12, 0, 0xD, NCaJGG9kvem4, \ ; "NCaJGG9kvem4"
LOAD:00000000000004E0 0x364>
LOAD:00000000000004F8 Elf64_Sym <aAe7kmlpkuubb - byte_608, 0x12, 0, 0xD, aE7KMLpKuUbB, \ ; "aE7KMLpKuUbB"
frida:
1 版本需要一致(搞半天又忘看版本了)
2 download目录没有执行权限 传到 /data/local 就行
输入 n1ctf{111111}
多按几次出现
GetStringUTFChars called from:
0x7099fcf17c libnative1.so!0x1b17c
0x711f0d9a48 libart.so!art_quick_generic_jni_trampoline+0x98
0x711f0d0168 libart.so!art_quick_invoke_stub+0x228
0x711f256c50 libart.so!_ZN3art17InvokeWithVarArgsIP10_jmethodIDEENS_6JValueERKNS_33ScopedObjectAccessAlreadyRunnableEP8_jobjectT_St9__va_list+0x1b8
0x711f3b4b00 libart.so!_ZN3art3JNIILb0EE26CallNonvirtualObjectMethodEP7_JNIEnvP8_jobjectP7_jclassP10_jmethodIDz+0x15c
0x71055d07e0
进libNative1里面找 000000000001B17C 是在000000000001B148里面调用的
.text:000000000001B148
.text:000000000001B148 ; =============== S U B R O U T I N E =======================================
.text:000000000001B148
.text:000000000001B148 ; Attributes: bp-based frame fpd=0x20
.text:000000000001B148
.text:000000000001B148 ; __int64 __fastcall sub_1B148(__int64, __int64, __int64)
.text:000000000001B148 sub_1B148 ; DATA XREF: .data:0000000000040F10↓o
.text:000000000001B148
.text:000000000001B148 var_20 = -0x20
.text:000000000001B148 var_18 = -0x18
.text:000000000001B148 var_10 = -0x10
.text:000000000001B148 var_s0 = 0
.text:000000000001B148 var_s8 = 8
.text:000000000001B148 var_s10 = 0x10
.text:000000000001B148 var_s18 = 0x18
.text:000000000001B148
.text:000000000001B148 ; __unwind {
.text:000000000001B148 STP X29, X30, [SP,#-0x20+var_20]!
.text:000000000001B14C STR X23, [SP,#0x20+var_10]
.text:000000000001B150 STP X22, X21, [SP,#0x20+var_s0]
.text:000000000001B154 STP X20, X19, [SP,#0x20+var_s10]
.text:000000000001B158 MOV X29, SP
.text:000000000001B15C LDR X8, [X0]
.text:000000000001B160 MOV X1, X2
.text:000000000001B164 MOV X2, XZR
.text:000000000001B168 MOV W21, #0x1094
.text:000000000001B16C MOV X19, X0
.text:000000000001B170 MOVK W21, #0xB9F5,LSL#16
.text:000000000001B174 LDR X8, [X8,#0x548]
.text:000000000001B178 BLR X8
.text:000000000001B17C ADRL X8, dword_40FA0
libhook
[Pixel 3::com.n1ctf2024.ezapk ]-> enc called with input: 111111
enc returned: 9VKJjnks
enc called with input: 111111
libnative2.so function called at 0x7102fa506c iusp9aVAyoMI
libnative2.so function returned at 0x7102fa506c iusp9aVAyoMI
libnative2.so function called at 0x7102fa52c0 SZ3pMtlDTA7Q
libnative2.so function returned at 0x7102fa52c0 SZ3pMtlDTA7Q
libnative2.so function called at 0x7102fa5ab0 UqhYy0F049n5
libnative2.so function returned at 0x7102fa5ab0 UqhYy0F049n5
调用顺序
iusp9aVAyoMI xor
SZ3pMtlDTA7Q rc4
UqhYy0F049rc base64
关于rand函数的替换为返回固定值233,这会影响xor
.init_array:000000000003F930 ; ORG 0x3F930
.init_array:000000000003F930 DCQ sub_3679C
.init_array:000000000003F938 DCQ sub_36C6C
.init_array:000000000003F940 DCQ sub_1B540 改rand
读 /proc/self/maps 最基本的so注入的操作
啊但是后边一堆结构体的偏移操作看不太懂了
sub_1B6C4(filename);
v0 = fopen(filename, "r");
if ( v0 )
{
while ( fgets(filename, 4096, v0) )
{
if ( strstr(filename, "libnative2.so") )
{
v1 = strtok(filename, "-");
v2 = strtoull(v1, 0LL, 16);
goto LABEL_6;
}
}
}
v2 = 0LL;
ok然后让ai帮忙写个rc4
脚本存档 #
rc4脚本
def rc4_init(key: bytes) -> tuple:
"""
初始化RC4的状态向量S和临时向量T。
:param key: RC4秘钥,bytes类型
:return: (S, T) 状态向量和临时向量
"""
S = list(range(256))
T = [key[i % len(key)] for i in range(256)]
j = 0
for i in range(256):
j = (j + S[i] + T[i]) & 0xFF
S[i], S[j] = S[j], S[i]
return S, T
def rc4_cipher(data: bytes, S: list, T: list) -> bytes:
"""
执行RC4加密或解密操作。
:param data: 明文或密文,bytes类型
:param S: 状态向量
:param T: 临时向量
:return: 加密后的密文或解密后的明文
"""
result = bytearray()
i = j = 0
for byte in data:
i = (i + 1) & 0xFF
j = (j + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
t = (S[i] + S[j]) & 0xFF
k = S[t]
result.append(byte ^ k)
return bytes(result)
def rc4_encrypt(plaintext: bytes, key: bytes) -> bytes:
"""
RC4加密函数。
:param plaintext: 明文,bytes类型
:param key: 秘钥,bytes类型
:return: 密文,bytes类型
"""
S, _ = rc4_init(key)
return rc4_cipher(plaintext, S, [])
def rc4_decrypt(ciphertext: bytes, key: bytes) -> bytes:
"""
RC4解密函数。
:param ciphertext: 密文,bytes类型
:param key: 秘钥,bytes类型
:return: 明文,bytes类型
"""
# RC4是对称加密,加密和解密使用相同的函数
S, _ = rc4_init(key)
return rc4_cipher(ciphertext, S, [])
# 示例用法
if __name__ == "__main__":
# key = b'example_key'
# plaintext = bytes([233])*16
# enc = b"iRrL63tve+H72wjr/HHiwlVu5RZU9XDcI7A="
# import base64
# dec1 = base64.b64decode(enc)
# print(dec1)
# b'\x89\x1a\xcb\xeb{o{\xe1\xfb\xdb\x08\xeb\xfcq\xe2\xc2Un\xe5\x16T\xf5p\xdc#\xb0'
key = b'\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9'
encrypted = b'\x89\x1a\xcb\xeb{o{\xe1\xfb\xdb\x08\xeb\xfcq\xe2\xc2Un\xe5\x16T\xf5p\xdc#\xb0'
# encrypted = rc4_encrypt(plaintext, key)
# print(f"Encrypted: {encrypted}")
decrypted = rc4_decrypt(encrypted, key)
tmp = b''
for i in decrypted:
tmp += bytes([i^233])
print(tmp)
# b'MysT3r10us_C0d3_2024N1CTF!'
hook脚本
Java.perform(() => { // 进入Java上下文,确保可以访问Java类和方法
const MainActivity = Java.use("com.n1ctf2024.ezapk.MainActivity"); // 使用Frida的Java.use方法来获取目标类的引用
MainActivity.enc.implementation = function(input) { // 重写enc方法的实现
console.log("enc called with input:", input); // 打印调用enc方法时的输入参数
const result = this.enc(input); // 调用原始的enc方法,并保存返回值
// startHook(); // 调用自定义的startHook函数
startHooklib(); //
console.log("enc returned:", result); // 打印enc方法的返回值
return result; // 返回enc方法的原始结果
};
});
function startHook() { // 定义startHook函数
const lib_art = Process.findModuleByName('libart.so'); // 使用Frida的Process模块找到libart.so模块
const symbols = lib_art.enumerateSymbols(); // 枚举libart.so模块中的所有符号
for (let symbol of symbols) { // 遍历所有符号
var name = symbol.name; // 获取符号名称
if (name.indexOf("art") >= 0) { // 如果符号名称包含"art"
if ((name.indexOf("CheckJNI") == -1) && (name.indexOf("JNI") >= 0)) { // 并且不包含"CheckJNI"但包含"JNI"
if (name.indexOf("GetStringUTFChars") >= 0) { // 并且包含"GetStringUTFChars"
console.log('start hook', symbol.name); // 打印开始hook的符号名称
Interceptor.attach(symbol.address, { // 使用Frida的Interceptor模块附加到符号地址
onEnter: function (arg) { // 定义进入时的回调函数
console.log('GetStringUTFChars called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n'); // 打印调用栈信息
},
onLeave: function (retval) { // 定义离开时的回调函数
console.log('onLeave GetStringUTFChars:', ptr(retval).readCString()) // 打印返回值
}
})
}
}
}
}
}
function startHooklib(){
// 枚举 libnative1.so 模块导出的所有函数
var functions_lib1 = Module.enumerateExports("libnative1.so");
// 将 functions_lib1 重新赋值为空数组(这行代码似乎是多余的,可能是逻辑错误)
functions_lib1 = []
// 枚举 libnative2.so 模块导出的所有函数
var functions_lib2 = Module.enumerateExports("libnative2.so");
// 为 libnative1.so 中的每个函数添加模块名称属性
functions_lib1 = functions_lib1.map(item => {
return { ...item, module: "libnative1.so" };
})
// 为 libnative2.so 中的每个函数添加模块名称属性
functions_lib2 = functions_lib2.map(item => {
return { ...item, module: "libnative2.so" };
})
// 合并两个模块的函数数组
var functions = [...functions_lib1,...functions_lib2];
// 遍历所有函数
functions.forEach(function(func) {
// 查找 libnative1.so 模块的基础地址
var moduleBase_lib1 = Module.findBaseAddress(func.module);
// 查找 libnative2.so 模块的基础地址(这里重复查找,可能是逻辑错误,应该根据模块名称区分查找)
var moduleBase_lib2 = Module.findBaseAddress(func.module);
// 如果两个模块的基础地址都找到了
if ( moduleBase_lib1 && moduleBase_lib2) {
// 获取函数的地址
var address = func.address
// 使用 Interceptor.attach 附加到函数地址上,以便监控函数的调用和返回
Interceptor.attach(address, {
// 函数调用进入时的回调函数
onEnter: function(args) {
// 打印函数所属模块、调用地址和函数名称
console.log(func.module + " function called at " + func.address + " " + func.name);
},
// 函数调用返回时的回调函数
onLeave: function(retval) {
// 打印函数所属模块、返回地址和函数名称
console.log(func.module + " function returned at "+ func.address + " " + func.name);
}
});
} else {
// 如果模块未找到,打印提示信息
console.log("Module " + func.module + " not found!");
}
});
}
tips:看arm的可以先关注
BL BLX BLR BX 等函数调用的命令 其余的查字典
看起来手动注入不是个轻松活,能用框架还是用框架吧,纯c\cpp写着太难受了55