SO逆向之轻小说sfsecurity分析

抓包

image-20211216102133093

postman发起请求

image-20211216103231917

脱壳

image-20211216132725105

1
2
3
proxychains git clone https://github.com/hluwa/FRIDA-DEXDump.git  移到~/.objection/plugins重命名为dexdump
objection -g com.sfacg explore -P ~/.objection/plugins
plugin dexdump dump

image-20211216105100379

image-20211216145531852

上图可见,脱壳效果并不好。不过不影响,搜索sfsecurity

image-20211221195054622

1
2
plugin wallbreaker objectsearch com.sf.security.AuthConfig
plugin wallbreaker objectdump --fullname 0x36d6

image-20211221195033520

1
android hooking watch class_method com.sf.security.AuthConfig.getSFSecurity --dump-args --dump-backtrace --dump-return

image-20211223172329906

入参为context和token,根据输出的结果,包含了nonce和sign两个加密键值对,timestamp时间戳,devicetoken是第二个参数。

Unidbg

通过IDA的导出函数找到getSFSecurity的起始地址,尝试主动调用

image-20211227122841406

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class qxs extends AbstractJni{
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;

qxs() {
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.sfacg").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

vm = emulator.createDalvikVM(new File("轻小说4.7.24.apk")); // 创建Android虚拟机
DalvikModule dm = vm.loadLibrary(new File("libsfdata.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
module = dm.getModule(); //

// 先把JNI Onload跑起来,里面做了大量的初始化工作
vm.setJni(this);
vm.setVerbose(true);
dm.callJNI_OnLoad(emulator);

}

public static void main(String[] args) throws Exception {
qxs test = new qxs();
System.out.println(test.getSFsecurity());
}

public String getSFsecurity(){
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv()); // 第一个参数是env
list.add(0); // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到
Object custom = null;
DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(custom);// context
list.add(vm.addLocalObject(context));
list.add(vm.addLocalObject(new StringObject(vm, "95048C92-C0A4-388E-923F-36861451DE6E")));

Number number = module.callFunction(emulator, 0xA944 + 1, list.toArray())[0];
String result = vm.getObject(number.intValue()).getValue().toString();
return result;
}
}

运行在调用callStaticObjectMethodV时报错:java.lang.UnsupportedOperationException: java/util/UUID->randomUUID()Ljava/util/UUID;,补环境

1
2
3
4
5
6
7
8
9
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "java/util/UUID->randomUUID()Ljava/util/UUID;":{
return dvmClass.newObject(UUID.randomUUID());
}
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}

运行在调用callObjectMethodV时报错:java.lang.UnsupportedOperationException: java/util/UUID->toString()Ljava/lang/String;,补环境

1
2
3
4
5
6
7
8
9
10
@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature) {
case "java/util/UUID->toString()Ljava/lang/String;":{
String uuid = dvmObject.getValue().toString();
return new StringObject(vm, uuid);
}
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

image-20211227123438535

正常获取到结果,可以证明nonce就是通过uuid生成的随机数,timestamp就是当前时间戳,deviceToken就是传入的参数,sign就是生成的结果。

Sign

本so进行了保护,无法反编译,findHash也没有结果,但是unidbg可以正常跑出加密后的sign,尝试使用traceCode追踪下汇编指令流。

image-20211227124110144

默认的unidbg的trace是不保存寄存器值的信息的,修改ARM32下的AbstractARMEmulator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void printAssemble(PrintStream out, Capstone.CsInsn[] insns, long address, boolean thumb) {
StringBuilder sb = new StringBuilder();
for (Capstone.CsInsn ins : insns) {
sb.append("### Trace Instruction ");
sb.append(ARM.assembleDetail(this, ins, address, thumb));
// 打印每条汇编指令里参与运算的寄存器的值
Set<Integer> regset = new HashSet<Integer>();

Arm.OpInfo opInfo = (Arm.OpInfo) ins.operands;
for(int i = 0; i<opInfo.op.length; i++){
regset.add(opInfo.op[i].value.reg);
}

String RegChange = ARM.SaveRegs(this, regset);
sb.append(RegChange);
sb.append('\n');
address += ins.size;
}
out.print(sb);
}

ARM.java中新建SaveRegs方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public static String SaveRegs(Emulator<?> emulator, Set<Integer> regs) {
Backend backend = emulator.getBackend();
StringBuilder builder = new StringBuilder();
builder.append(">>>");
Iterator it = regs.iterator();
while(it.hasNext()) {
int reg = (int) it.next();
Number number;
int value;
switch (reg) {
case ArmConst.UC_ARM_REG_R0:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r0=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R1:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r1=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R2:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r2=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R3:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r3=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R4:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r4=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R5:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r5=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R6:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r6=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R7:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r7=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R8:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r8=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R9: // UC_ARM_REG_SB
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " sb=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R10: // UC_ARM_REG_SL
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " sl=0x%x", value));
break;
case ArmConst.UC_ARM_REG_FP:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " fp=0x%x", value));
break;
case ArmConst.UC_ARM_REG_IP:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " ip=0x%x", value));
break;
case ArmConst.UC_ARM_REG_SP:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " SP=0x%x", value));
break;
case ArmConst.UC_ARM_REG_LR:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " LR=0x%x", value));
break;
case ArmConst.UC_ARM_REG_PC:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " PC=0x%x", value));
break;
}
}
return builder.toString();
}

开始跑traceCode并将结果保存在qxstrace.txt中,由于sign是32位的十六进制数,猜测是md5相关,关键特征有4个魔数和64个K。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 魔数
A = 0x67452301
B = 0xefcdab89
C = 0x98badcfe
D = 0x10325476

# K表
Ktable = [
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf,
0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af,
0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e,
0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6,
0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122,
0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, 0xd9d4d039,
0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97,
0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d,
0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
]

通过检索这些关键特征,可以断定这是一个md5或者魔改后的md5

image-20211227125024339

文章作者: J
文章链接: http://onejane.github.io/2021/12/16/SO逆向之轻小说sfsecurity分析/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏