MANWE脱壳机阅读笔记

背景

pdd出事之后,我尝试逆向过mw01.bin的还原逻辑,但是花了很多时间也没有头绪。现在既然davinci大佬把脱壳机放出来了,自然要好好理解一下,也为以后遇到的VMP壳做准备。

ManweVmpLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ManweVmpLoader {

public static void main(String[] args) throws Throwable {
String firmwarePath = "/tmp/mw1.bin";
ManweVmpDataInputStream inputStream = new ManweVmpDataInputStream(Files.newInputStream(Paths.get(firmwarePath)));
ManweVmpDex manweVmpDex = new ManweVmpDex(inputStream);
System.out.printf("Load %d class%n", manweVmpDex.manweVmpClazzes.length);
if (inputStream.available() != 0) {
throw new RuntimeException(String.format("%d bytes remaining", inputStream.available()));
}
inputStream.close();
if (Files.notExists(Paths.get("/tmp/final_java/"))) {
new File("/tmp/final_java/").mkdirs();
}
manweVmpDex.writeClazzes("/tmp/final_java/");
}
}

这部分是脱壳机的起点,代码也比较短,容易理解。
首先将文件内容转化为ManweVmpDataInputStream,这个类支持对文件内容进行不同类型(int,double,UTF等等)的读取,跟核心逻辑关系不大,不再赘述。
之后把输入流导入ManweVmpDex,将文件还原为dex文件,并逐class写回文件系统。因此接下来的关键类就是ManweVmpDex。

ManweVmpDex

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
public ManweVmpDex(final ManweVmpDataInputStream in) throws IOException {
final ManweVmpDataInputStream inStream = in;
// 读3字节的magic
inStream.read(this.magic);
if (magic[0] != 0x1A || magic[1] != 0x0C || magic[2] != 0x06) {
throw new IOException("Check Magic Fail");
}

// 读1字节的版本号
this.vmpVersion = inStream.read();
System.out.println(this.vmpVersion);
// 读 headers
if (this.vmpVersion == 5)
this.headers = inStream.readStrBytesMap();
else if (this.vmpVersion < 5)
this.headers = new HashMap<>();
// 读 ConstantPool
this.constantPool = new ManweVmpConstantPool(inStream);
Pool
.init(this.constantPool);
// 读 N 个class
int clazzCount = inStream.readUnsignedShort();
this.manweVmpClazzes = new ManweVmpClazz[clazzCount];
for (int i = 0; i < clazzCount; i++) {
this.manweVmpClazzes[i] = new ManweVmpClazz(inStream, constantPool, this.vmpVersion);
System.out.printf("VmpClass [%d/%d] success, %s%n", i, clazzCount, this.manweVmpClazzes[i]);
}
Arrays.stream(this.manweVmpClazzes).forEach(RuntimeTypeFixer::parseVmpClazz);
this.constantPool.records = PoolFixer.moveZeroToLast(this.constantPool, this.manweVmpClazzes);
this.constantPool.records = RuntimeTypeFixer.addSingleStringToPool(this.constantPool, "Code");
this.constantPool.records = RuntimeTypeFixer.addStringToPool(this.constantPool);

PoolFixer.analyzeUtf8ToClazz(this.constantPool.records);
PoolFixer.patchForFieldAndMethod(this.constantPool.records);
Arrays.stream(this.manweVmpClazzes).forEach(clazz -> {
PoolFixer.patchConstantPoolForClassMeta(clazz, this.constantPool.records.length);
});
this.constantPool.records = PoolFixer.doFinal(this.constantPool.records);
}

在ManweVmpDex的构造函数中,程序首先读取is中的magic number、版本号、headers、常量池(ManweVmpConstantPool),然后再解析其中的class。
常量池开头为两字节,指明常量的数量,之后的每一条都是一个字节指明下一个常量的类型(字节数),然后紧跟着该常量的值。常量的类型包括:

1
2
3
4
5
6
7
8
9
10
11
12
BOOL
INTEGER
LONG
FLOAT
DOUBLE
CONSTANT_UTF8
VMP_CLAZZ_REF
VMP_FIELD_REF
VMP_METHOD_REF
ANNOTATION
ENUM_REF
USHORT_ARRAY

ManweVmpClazz

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
public ManweVmpClazz(ManweVmpDataInputStream inStream, ManweVmpConstantPool _constantPool, int _version) throws IOException {
version = _version;
constantPool = _constantPool;
clazzNameIdx = inStream.readUnsignedShort(); //TODO need to be fixed
parentNameIdx = inStream.readUnsignedShort(); //TODO need to be fixed
clazzName = constantPool.getItemAt(clazzNameIdx).asString();
parentName = constantPool.getItemAt(parentNameIdx).asString();

interfaceIdxes = new int[inStream.readUnsignedByte()]; //TODO need to be fixed
interfaceNames = new String[interfaceIdxes.length];
for (int j = 0; j < interfaceIdxes.length; j++) {
interfaceIdxes[j] = inStream.readUnsignedShort();
interfaceNames[j] = constantPool.getItemAt(j).asString();
}

//remove when interface equals to itself
List<Integer> ints = new ArrayList<>();
List<String> strs = new ArrayList<>();
for (int j = 0; j < interfaceIdxes.length; j++) {
if (!interfaceNames[j].equals(clazzName)) {
ints.add(interfaceIdxes[j]);
strs.add(interfaceNames[j]);
}
}

interfaceIdxes = ints.stream().mapToInt(i -> i).toArray();
interfaceNames = strs.toArray(new String[0]);

int annotationCount = inStream.readUnsignedByte();
annotationMap = new HashMap<>(annotationCount);
annotationIdxes = new ArrayList<>();
for (int j = 0; j < annotationCount; j++) {
int annotationIdx = inStream.readUnsignedShort();
annotationIdxes.add(annotationIdx);
ManweVmpConstantPool.VmpAnnotation vmpAnnotation = (ManweVmpConstantPool.VmpAnnotation) constantPool.getItemAt(annotationIdx).getValue();
// System.out.println(vmpAnnotation.annotationName);
annotationMap.put(vmpAnnotation.annotationName, vmpAnnotation);
}

int unknownIdx = inStream.readUnsignedShort();
if (unknownIdx < 0xFFFF) {
unknown = constantPool.getItemAt(unknownIdx).asString();
} else {
unknown = "VMP-UNKNOWN";
}

access_flag = inStream.readUnsignedShort();

// read field
fieldMap = readField(inStream);
// 读method
methodMap = readMethod(inStream, clazzNameIdx);

// read class metadata
if (version == 5)
extraMap = inStream.readStrBytesMap();
else
extraMap = new HashMap<>();
}

构造常量池之后,开始通过ManweVmpClazz构造class。程序依次读取类名、父类名、接口名、注释所对应的index,并从常量池中取出对应的值进行填充。然后通过readField和readMethod读取成员变量和成员函数的信息。

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
private Map<String, ManweVmpField> readField(ManweVmpDataInputStream inStream) throws IOException {
HashMap<String, ManweVmpField> ret = new HashMap<>();
int fieldCount = inStream.readUnsignedShort();
for (int i = 0; i < fieldCount; i++) {
ManweVmpField manweVmpField = new ManweVmpField(inStream, constantPool);
ret.put(manweVmpField.fieldName, manweVmpField);
// System.out.printf("VmpField [%d/%d] success, %s%n", i, fieldCount, vmpField);
}
return ret;
}

private Map<String, ManweVmpMethod> readMethod(ManweVmpDataInputStream inStream, int clazzNameIdx) throws IOException {
HashMap<String, ManweVmpMethod> ret = new HashMap<>();
int methodCount = inStream.readUnsignedShort();
for (int i = 0; i < methodCount; i++) {
ManweVmpMethod manweVmpMethod = new ManweVmpMethod(inStream, constantPool, version);
manweVmpMethod.setClazzNameIdx(clazzNameIdx);
ret.put(manweVmpMethod.methodName+ manweVmpMethod.description, manweVmpMethod);
//System.out.printf("VmpMethod [%d/%d] success, %s%n", i, methodCount, vmpMethod);
}
if (ret.size() != methodCount) {
throw new RuntimeException("wtf method duplicate??");
}
return ret;
}

成员变量池和成员函数池的构造类似于常量池,由两字节指明数量,然后是各成员的信息,具体的解析方法在ManweVmpField和ManweVmpMethod中。
读取完成后,返回一个Map,键为变量名/函数签名,值为该成员对应的ManweVmpField/ManweVmpMethod类。

ManweVmpField

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
public ManweVmpField(ManweVmpDataInputStream inStream, ManweVmpConstantPool _constantPool) throws IOException {
/*
field_info {
u2 access_flags;
u2 name_index; -> utf8
u2 descriptor_index; -> utf8
u2 attributes_count;
attribute_info attributes[attributes_count];
}
*/
constantPool = _constantPool;
nameIdx = inStream.readUnsignedShort();
fieldName = constantPool.getItemAt(nameIdx).asString();
fieldTypeIdx = inStream.readUnsignedShort();
fieldType = constantPool.getItemAt(fieldTypeIdx).asString();
access_flag = inStream.readUnsignedShort();

int annotationCount = inStream.readUnsignedByte();
annotationMap = new HashMap<>(annotationCount);
annotationIdxes = new ArrayList<>();
for (int j = 0; j < annotationCount; j++) {
int annotationIdx = inStream.readUnsignedShort();
annotationIdxes.add(annotationIdx);
ManweVmpConstantPool.VmpAnnotation vmpAnnotation = (ManweVmpConstantPool.VmpAnnotation) constantPool.getItemAt(annotationIdx).getValue();
annotationMap.put(vmpAnnotation.annotationName, vmpAnnotation);
}

int unknownIdx = inStream.readUnsignedShort();
if (unknownIdx < 0xFFFF) {
unknown = constantPool.getItemAt(unknownIdx).asString();
} else {
unknown = "VMP-UNKNOWN";
}
}

构造函数从输入流中读取变量名、变量类型、读取权限和注释所对应的index,然后从常量池中取出对应的值。

ManweVmpMethod

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
public ManweVmpMethod(ManweVmpDataInputStream inStream, ManweVmpConstantPool _constantPool, int version) throws IOException {

/*
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;[ref]
u2 name_and_type_index;[ref]
}
CONSTANT_Class_info {
u1 tag;
u2 name_index;[UTF8]
}
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;[UTF8]
u2 descriptor_index;[UTF8]
}
*/
constantPool = _constantPool;
nameIdx = inStream.readUnsignedShort();
methodName = constantPool.getItemAt(nameIdx).asString();
descriptionIdx = inStream.readUnsignedShort();
description = constantPool.getItemAt(descriptionIdx).asString();
access_flag = inStream.readUnsignedShort();

int annotationCount = inStream.readUnsignedByte();
annotationMap = new HashMap<>(annotationCount);
annotationIdxes = new ArrayList<>();
for (int j = 0; j < annotationCount; j++) {
int annotationIdx = inStream.readUnsignedShort();
annotationIdxes.add(annotationIdx);
ManweVmpConstantPool.VmpAnnotation vmpAnnotation = (ManweVmpConstantPool.VmpAnnotation) constantPool.getItemAt(annotationIdx).getValue();
annotationMap.put(vmpAnnotation.annotationName, vmpAnnotation);
}
// read data
vmpCodeLen = inStream.readUnsignedShort();
if (vmpCodeLen > 0) {
byte[] bytecode = new byte[vmpCodeLen];
inStream.readFully(bytecode, 0, vmpCodeLen);
manweCode = new ManweCode(new ManweVmpDataInputStream(new ByteArrayInputStream(bytecode)));
} else {
manweCode = null;
}
if (version == 5)
extraMap = inStream.readStrBytesMap();
else
extraMap = new HashMap<>();
// Example: MethodRef{clazzName='com/xunmeng/pinduoduo/alive/base/ability/common/AliveAbility',
// methodName='isAbilityDisabled2022Q3',
// methodDescription='(Ljava/lang/String;)Z'}
}

成员方法除了类似于成员变量的方法名、签名、读取权限和注释之外,还包含一个额外的ManweCode类,由长度和内容组成,具体解析方法在ManweCode内。

ManweCode太长了,不放代码了,解析过程大致如下:
两字节指明最大临时变量数,两字节指明最大栈长度(但是这两个参数是final的,感觉意义不明),两字节指明指令数量,接下来逐条解析指令(ManweVmpInstruction),设置其JVM偏移为目前的输出位置;
一字节指明opcode,其中共有以下类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
OP_WIDE_PREFIX = 0xc4;					    
OP_IINC = 0x84;
OP_GOTO_W = 0xc8;
OP_JSR_W = 0xc9;
OP_INVOKE_INTERFACE = 0xB9;
OP_NEW = 0xBB;
OP_ANEWARRAR = 0xBD;
OP_CHECKCAST = 0xC0;
OP_INSTANCEOF = 0xC0;
OP_BIPUSH = 16;
OP_SIPUSH = 17;
OP_NEWARRAY = 188;
OP_NOP = 0;
OP_LDC = 0x12;
OP_LDC_W = 0x13;

根据opcode类型的不同,在输出流中的处理也不同,以下枚举其中一部分:

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
case 0: {
// case 0 的长度是1 0-15可原样写回(实际上只有NOP是这一种)
outStreamCntOnly.writeByte(opcode);
break;
}
case 1: {
if (opcode == OP_BIPUSH || opcode == OP_SIPUSH) {
// bipush是byte ipush,sipush是 short ipush
// 因为oper 是个short,存在溢出的现象,将二者合并
outStreamCntOnly.writeByte(OP_SIPUSH);
instruction.opcode = OP_SIPUSH;
} else if (opcode == OP_NEWARRAY) {
outStreamCntOnly.writeByte(opcode);
} else {
throw new UnreachableException("not reachable");
}
int oper = inStream.readUnsignedShort();
outStreamCntOnly.writeShort(oper);
instruction.setOperand1(oper);
break;
}
case 3: {
// OP_NEW, OP_ANEWARRAR, OP_CHECKCAST, OP_INSTANCEOF
outStreamCntOnly.writeByte(opcode);
int oper = inStream.readUnsignedShort();
outStreamCntOnly.writeShort(oper);
instruction.setOperand1(oper);
break;
}

通过这种方式记录指令的操作数,同时将VMP的code写为正常dalvik虚拟机的bytecode之后,还需要对异常处理进行解析

1
2
3
4
5
6
7
8
9
10
exceptionTableCount = inStream.readUnsignedByte();
exceptionTables = new VmpExceptionTable[exceptionTableCount];
for (int i = 0; i < exceptionTableCount; i++) {
VmpExceptionTable vmpExceptionTable = new VmpExceptionTable();
vmpExceptionTable.i1 = inStream.readUnsignedShort();
vmpExceptionTable.i2 = inStream.readUnsignedShort();
vmpExceptionTable.i3 = inStream.readUnsignedShort();
vmpExceptionTable.i4 = inStream.readUnsignedShort(); // this entry may need to be fixed
exceptionTables[i] = vmpExceptionTable;
}

每个表的四个参数分别为try catch的开始地址,结束地址,异常的处理起始位,异常类名称。

PoolFixer & RuntimeTypeFixer

本来以为到这里解析的部分就结束了,不过仔细一看构造ManweVmpDex的部分最后还有一段儿

1
2
3
4
5
6
7
8
9
10
11
Arrays.stream(this.manweVmpClazzes).forEach(RuntimeTypeFixer::parseVmpClazz);
this.constantPool.records = PoolFixer.moveZeroToLast(this.constantPool, this.manweVmpClazzes);
this.constantPool.records = RuntimeTypeFixer.addSingleStringToPool(this.constantPool, "Code");
this.constantPool.records = RuntimeTypeFixer.addStringToPool(this.constantPool);

PoolFixer.analyzeUtf8ToClazz(this.constantPool.records);
PoolFixer.patchForFieldAndMethod(this.constantPool.records);
Arrays.stream(this.manweVmpClazzes).forEach(clazz -> {
PoolFixer.patchConstantPoolForClassMeta(clazz, this.constantPool.records.length);
});
this.constantPool.records = PoolFixer.doFinal(this.constantPool.records);

moveZeroToLast:新建了一个patchedPool,拷贝了常量池的内容,但是把第0个item放在了末尾,并且把所有原本指向0的index改为指向末尾(这是在干嘛?)
addSingleStringToPool:往patchedPool里加了一个CONSTANT_UTF8类型的item,值为”Code”
addStringToPool:往patchedPool里加了很多CONSTANT_UTF8类型的item,其中的值包括:(啊?这些都是哪来的?)

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
add("android/accounts/Account", "name", DESC_STRING);
add("android/accounts/Account", "type", DESC_STRING);
add("android/content/pm/ActivityInfo", "enabled", DESC_BOOLEAN);
add("android/content/pm/ActivityInfo", "exported", DESC_BOOLEAN);
add("android/content/pm/ActivityInfo", "name", DESC_STRING);
add("android/content/pm/ActivityInfo", "packageName", DESC_STRING);
add("android/content/pm/ActivityInfo", "permission", DESC_STRING);
add("android/content/pm/ApplicationInfo", "flags", DESC_INT);
add("android/content/pm/ApplicationInfo", "sourceDir", DESC_STRING);
add("android/content/pm/ApplicationInfo", "uid", DESC_INT);
add("android/content/pm/ApplicationInfo", "targetSdkVersion", DESC_INT);
add("android/content/pm/PackageInfo", "versionCode", DESC_INT);
add("android/content/pm/PackageInfo", "versionName", DESC_STRING);
add("android/content/pm/ResolveInfo", "activityInfo", wrap("android/content/pm/ActivityInfo"));
add("android/content/pm/ResolveInfo", "resolvePackageName", DESC_STRING);
add("android/content/pm/ResolveInfo", "serviceInfo", wrap("android/content/pm/ServiceInfo"));
add("android/content/pm/ServiceInfo", "metaData", wrap("android/os/Bundle"));
add("android/content/pm/ServiceInfo", "name", DESC_STRING);
add("android/content/pm/ServiceInfo", "packageName", DESC_STRING);
add("android/graphics/Bitmap$Config", "ARGB_8888", wrap("android/graphics/Bitmap$Config"));
add("android/os/Build$VERSION", "SDK_INT", DESC_INT);
add("android/os/Build$VERSION", "SECURITY_PATCH", DESC_STRING);
add("android/os/Build", "MANUFACTURER", DESC_STRING);
add("android/os/Build", "PRODUCT", DESC_STRING);
add("android/os/Build", "BOARD", DESC_STRING);
add("android/os/Build", "DISPLAY", DESC_STRING);
add("android/os/Build", "FINGERPRINT", DESC_STRING);
add("android/os/Build", "MODEL", DESC_STRING);
add("android/os/Build", "SERIAL", DESC_STRING);
add("android/os/Build", "BRAND", DESC_STRING);
add("android/os/Build", "DEVICE", DESC_STRING);
add("android/os/Build$VERSION", "RELEASE", DESC_STRING);
add("android/app/ActivityManager$MemoryInfo", "availMem", DESC_LONG);
add("android/os/Bundle", "EMPTY", wrap("android/os/Bundle"));
add("android/os/Message", "arg1", DESC_INT);
add("android/os/Message", "what", DESC_INT);
add("android/util/Pair", "first", wrap("java/lang/Object"));
add("android/util/Pair", "second", wrap("java/lang/Object"));
add("android/view/WindowManager$LayoutParams", "dimAmount", DESC_FLOAT);
add("android/view/WindowManager$LayoutParams", "flags", DESC_INT);
add("android/view/WindowManager$LayoutParams", "format", DESC_INT);
add("android/view/WindowManager$LayoutParams", "gravity", DESC_INT);
add("android/view/WindowManager$LayoutParams", "height", DESC_INT);
add("android/view/WindowManager$LayoutParams", "packageName", DESC_STRING);
add("android/view/WindowManager$LayoutParams", "token", wrap("android/os/IBinder"));
add("android/view/WindowManager$LayoutParams", "type", DESC_INT);
add("android/view/WindowManager$LayoutParams", "width", DESC_INT);
add("android/view/WindowManager$LayoutParams", "windowAnimations", DESC_INT);
add("android/view/WindowManager$LayoutParams", "x", DESC_INT);
add("android/view/WindowManager$LayoutParams", "y", DESC_INT);
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/adapter/intf/message/Message0", "name", DESC_STRING);
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/adapter/intf/message/Message0", "payload", wrap("org/json/JSONObject"));
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/adapter/intf/threadpool/ThreadBiz", "CS", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/adapter/intf/threadpool/ThreadBiz"));
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "IRREGULAR_PROCESS_START", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType"));
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "PROCESS_START", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType"));
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "name", DESC_STRING);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/powersave/internal/b", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/powersave/internal/b", "c", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/powersave/internal/c", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/powersave/internal/c", "c", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/powersave/internal/e", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/powersave/internal/e", "c", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/powersave/internal/f", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/powersave/internal/f", "c", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/stepInfo/internal/b", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/stepInfo/internal/c", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/stepInfo/internal/c", "b", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/stepInfo/internal/f", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/stepInfo/internal/f", "b", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/stepInfo/internal/g", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/buildin/stepInfo/internal/g", "b", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/e", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/e", "b", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/h", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/h", "b", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/i", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/i", "b", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/j", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/j", "b", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_impl_interface/utils/AliveStartUpConstants", "KEY_SCENE", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_impl_interface/utils/AliveStartUpConstants", "KEY_START_ACC_BY_RIVAN", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_impl_interface/utils/AliveStartUpConstants", "KEY_USE_SETTINGS_ACCOUNT", DESC_NOTFOUND);
add("java/lang/Integer", "TYPE", wrap("java/lang/Class"));
add("java/lang/Long", "TYPE", wrap("java/lang/Class"));
add("java/nio/charset/StandardCharsets", "UTF_8", DESC_STRING);
add("java/util/Locale", "ENGLISH", wrap("java/util/Locale"));
add("java/util/concurrent/TimeUnit", "MILLISECONDS", wrap("java/util/concurrent/TimeUnit"));
add("java/util/concurrent/TimeUnit", "SECONDS", wrap("java/util/concurrent/TimeUnit"));
add("java/util/concurrent/TimeUnit", "MICROSECONDS", wrap("java/util/concurrent/TimeUnit"));
add("com/xunmeng/pinduoduo/alive/unify/ability/interfaces/schema/ryze/RyzeRequest", "uri", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/g", "b", DESC_STRING);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/g", "a", DESC_STRING);
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "SCREEN_ON", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType"));
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "USER_PRESENT", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType"));
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "SCREEN_OFF", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType"));
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "FP_PERM_READY", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType"));
add("java/io/File", "separator", DESC_STRING);
add("java/io/File", "separatorChar", DESC_CHAR);
add("android/content/pm/ProviderInfo", "name", DESC_STRING);
add("java/util/concurrent/TimeUnit", "HOURS", wrap("java/util/concurrent/TimeUnit"));
add("java/util/concurrent/TimeUnit", "NANOSECONDS", wrap("java/util/concurrent/TimeUnit"));
add("android/os/Message", "obj", wrap("java/lang/Object"));
add("android/view/WindowManager$LayoutParams", "alpha", DESC_INT);
add("com/xunmeng/pinduoduo/alive/strategy/biz/boush/BoushConfig", "mode", DESC_STRING);
add("com/xunmeng/pinduoduo/alive/strategy/biz/boush/BoushConfig", "recoverEnable", DESC_BOOLEAN);
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "PDD_ID_CONFIRM", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("com/xunmeng/pinduoduo/alive/strategy/biz/boush/BoushConfig", "blackScene", DESC_STRING);
add("com/xunmeng/pinduoduo/alive/strategy/biz/boush/BoushConfig", "blackResultType", wrap("com.xunmeng.pinduoduo.alive.strategy.interfaces.adapter.intf.msc.BlackResultType"));
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/adapter/intf/msc/BlackResultType", "TRUE", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/adapter/intf/msc/BlackResultType"));
add("com/xunmeng/pinduoduo/alive/strategy/biz/boush/BoushConfig", "specificCmptList", wrap("java/util/Set"));
add("com/xunmeng/pinduoduo/alive/strategy/biz/boush/BoushConfig", "mainProcPullUpCmptBlackList", wrap("java/util/Set"));
add("com/xunmeng/pinduoduo/alive/strategy/biz/boush/BoushConfig", "pullUpCmptBlackList", wrap("java/util/Set"));
add("com/xunmeng/pinduoduo/alive/strategy/biz/boush/BoushConfig", "trackExpKey", wrap("java/lang/String"));
add("java/util/Locale", "CHINA", wrap("java/util/Local"));
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "START_SKY_CASTLE", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "DPPL_EVENT", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "ITDM_EVENT", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("android/content/pm/PackageInfo", "applicationInfo", wrap("android/content/pm/ApplicationInfo"));
add("android/content/pm/ActivityInfo", "applicationInfo", wrap("android/content/pm/ApplicationInfo"));
add("android/content/pm/PackageInfo", "packageName", DESC_STRING);
add("android/content/pm/PackageInfo", "activities", "[Landroid/content/pm/ActivityInfo");
add("android/content/pm/PackageInfo", "services", "[Landroid/content/pm/ServiceInfo");
add("android/content/pm/PackageInfo", "receivers", "[Landroid/content/pm/ActivityInfo");
add("android/content/pm/PackageInfo", "signatures", "[Landroid/content/pm/Signature");
add("android/content/pm/PackageInfo", "requestedPermissions", "[java/lang/String");
add("android/content/pm/PackageInfo", "firstInstallTime", DESC_LONG);
add("android/content/pm/ApplicationInfo", "metaData", wrap("android/app/Bundle"));
add("android/net/wifi/ScanResult", "SSID", DESC_STRING);
add("android/net/wifi/ScanResult", "BSSID", DESC_STRING);
add("android/net/wifi/ScanResult", "level", DESC_INT);
add("android/net/wifi/WifiConfiguration", "SSID", DESC_STRING);
add("android/net/wifi/WifiConfiguration", "BSSID", DESC_STRING);
add("android/net/wifi/WifiConfiguration", "FQDN", DESC_STRING);
add("android/net/wifi/WifiConfiguration", "hiddenSSID", DESC_BOOLEAN);
add("android/net/wifi/WifiConfiguration", "providerFriendlyName", DESC_STRING);
add("android/content/res/Configuration", "locale", wrap("java/util/Locale"));
add("android/app/ActivityManager$RunningServiceInfo", "process", DESC_STRING);
add("android/content/pm/PackageInfo", "providers", "[Landroid/content/pm/ProviderInfo");
add("android/content/pm/ProviderInfo", "authority", DESC_STRING);
add("android/content/pm/ProviderInfo", "packageName", DESC_STRING);
add("android/content/pm/ProviderInfo", "metaData", DESC_STRING);
add("android/content/pm/ProviderInfo", "readPermission", DESC_STRING);
add("android/content/pm/ProviderInfo", "processName", DESC_STRING);
add("android/content/pm/ProviderInfo", "grantUriPermissions", DESC_BOOLEAN);
add("com/xunmeng/pinduoduo/galaxy/plugin/location_info/tracker/VivoTracker", "fpSceneSet", wrap("java/util/Set"));
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "ON_FOREGROUND", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "STARTUP_IDLE", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "DIEL_EVENT", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "STOP_SKY_CASTLE", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("android/content/pm/ServiceInfo", "permission", DESC_STRING);
add("android/content/pm/ServiceInfo", "enabled", DESC_BOOLEAN);
add("android/content/pm/ServiceInfo", "exported", DESC_BOOLEAN);
add("android/graphics/Rect", "top", DESC_INT);
add("android/graphics/Rect", "left", DESC_INT);
add("android/content/pm/ApplicationInfo", "packageName", DESC_STRING);
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "BACKGROUND_1MIN_TIMER", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("android/app/ActivityManager$RunningAppProcessInfo", "processName", DESC_STRING);
add("android/app/ActivityManager$RunningAppProcessInfo", "importance", DESC_INT);
add("android/app/ActivityManager$RunningAppProcessInfo", "pid", DESC_INT);
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "SCREEN_RECORD_START", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "SCREEN_RECORD_STOP", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("java/lang/System", "out", wrap("java.io.PrintStream"));
add("android/view/WindowManager$LayoutParams", "layoutInDisplayCutoutMode", DESC_INT);
add("com/xunmeng/pinduoduo/alive/strategy/biz/plugin/purgeV2/ProviderSceneConfig", "delayToNextSceneInMs", DESC_INT);
add("android/os/Message", "replyTo", wrap("android/os/Messenger"));
add("android/app/Notification", "contentIntent", wrap("android/content/Intent"));
add("com/xunmeng/pinduoduo/alive/unify/ability/dynamic/abilities/dataCollect/ability/ReportInfoItem", "infoType", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/dynamic/abilities/dataCollect/ability/ReportInfoItem", "dataField", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/dynamic/abilities/dataCollect/ability/ReportInfoItem", "isDataArray", DESC_BOOLEAN);
add("android/app/Notification", "extras", wrap("android/app/Bundle"));
add("android/graphics/Bitmap$CompressFormat", "PNG", DESC_INT);
add("android/graphics/Bitmap$CompressFormat", "JPEG", DESC_INT);
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "ON_BACKGROUND", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/adapter/intf/threadpool/ThreadBiz", "BC", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/adapter/intf/threadpool/ThreadBiz"));
add("android/os/Environment", "DIRECTORY_DOWNLOADS", DESC_STRING);
add("com/xunmeng/pinduoduo/galaxy/plugin/location_info/tracker/XiaomiTracker", "fpSceneSet", wrap("java/util/Set"));
add("android/content/pm/ApplicationInfo", "nativeLibraryDir", DESC_STRING);
add("android/content/pm/ApplicationInfo", "publicSourceDir", DESC_STRING);
add("android/content/pm/ResolveInfo", "providerInfo", wrap("android/content/pm/ProviderInfo"));
add("com/xunmeng/pinduoduo/galaxy/plugin/location_info/tracker/HonorTracker", "fpSceneSet", wrap("java/util/Set"));
add("com/xunmeng/pinduoduo/alive/strategy/biz/plugin/purgeV2/ProviderSceneConfig", "retryIfScreenOff", DESC_BOOLEAN);
add("com/xunmeng/pinduoduo/alive/strategy/biz/plugin/purgeV2/ProviderSceneConfig", "checkResultDelayInMs", DESC_INT);
add("com/xunmeng/pinduoduo/alive/strategy/biz/plugin/purgeV2/ProviderSceneConfig", "retryDelayInMs", DESC_INT);
add("com/xunmeng/pinduoduo/alive/strategy/biz/plugin/purgeV2/ProviderSceneConfig", "silentIntervalWhenFailInMin", DESC_INT);
add("com/xunmeng/pinduoduo/alive/strategy/biz/plugin/purgeV2/ProviderSceneConfig", "maxStartLimitInSilentInterval", DESC_INT);
add("com/xunmeng/pinduoduo/alive/strategy/biz/plugin/purgeV2/ProviderSceneConfig", "silentIntervalInMin", DESC_INT);
add("com/xunmeng/pinduoduo/alive/strategy/biz/plugin/purgeV2/ProviderSceneConfig", "acceptedTriggerEvents", DESC_INT);
add("android/app/ActivityManager$RunningAppProcessInfo", "uid", DESC_INT);
add("com/xunmeng/pinduoduo/galaxy/plugin/location_info/tracker/OppoTracker", "fpSceneSet", wrap("java/util/Set"));
add("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType", "POWER_CONNECTED", wrap("com/xunmeng/pinduoduo/alive/strategy/interfaces/event/TriggerEventType")); //TODO
add("com/xunmeng/pinduoduo/alive/strategy/biz/plugin/purgeV2/ProviderSceneConfig", "retryCnt", DESC_INT);
add("com/xunmeng/pinduoduo/alive/unify/ability/framework/schema/common/IntentRequest", "caller", DESC_STRING);
add("com/xunmeng/pinduoduo/alive/unify/ability/dynamic/abilities/dataCollect/config/CollectorConfigItem", "infoType", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/alive/unify/ability/dynamic/abilities/dataCollect/config/CollectorConfigItem", "dataField", DESC_STRING);
add("com/xunmeng/pinduoduo/alive/unify/ability/dynamic/abilities/dataCollect/config/CollectorConfigItem", "isDataArray", DESC_BOOLEAN);
add("android/net/DhcpInfo", "gateway", DESC_INT);
add("android/provider/ContactsContract$CommonDataKinds$Phone", "CONTENT_URI", wrap("android/net/Uri"));
add("com/xunmeng/pinduoduo/alive_adapter_sdk/BotMMKV$BotMMKVModuleSource", "HX", wrap("com/xunmeng/pinduoduo/alive_adapter_sdk/BotMMKV$BotMMKVModuleSource"));
add("com/xunmeng/pinduoduo/alive_adapter_sdk/BotMMKV$BotProcessMode", "multiProcess", wrap("com/xunmeng/pinduoduo/alive_adapter_sdk/BotMMKV$BotProcessMode"));
add("com/xunmeng/pinduoduo/market_common/plugin/IPluginAbility$AbilityType", "SHORTCUT", wrap("com/xunmeng/pinduoduo/market_common/plugin/IPluginAbility$AbilityType"));
add("com/xunmeng/pinduoduo/market_common/plugin/IPluginAbility$AbilityType", "WIDGET", wrap("com/xunmeng/pinduoduo/market_common/plugin/IPluginAbility$AbilityType"));
add("com/xunmeng/pinduoduo/market_common/plugin/IPluginAbility$AbilityType", "MINUS_SCREEN_DETECT", wrap("com/xunmeng/pinduoduo/market_common/plugin/IPluginAbility$AbilityType"));
add("com/xunmeng/pinduoduo/market_common/plugin/IPluginAbility$AbilityType", "LAUNCHER", wrap("com/xunmeng/pinduoduo/market_common/plugin/IPluginAbility$AbilityType"));
add("com/xunmeng/pinduoduo/smart_widget_plugin/minus_screen/vivo/VivoMinusScreenDetectV28", "mContext", wrap("android/content/Context"));
add("com/xunmeng/pinduoduo/smart_widget_plugin/minus_screen/vivo/VivoMinusScreenDetect", "isInMinusScreen", DESC_BOOLEAN);
add("android/content/pm/LauncherApps$PinItemRequest", "CREATOR", wrap("android/os/Parcelable$CREATOR"));
add("android/appwidget/AppWidgetProviderInfo", "provider", wrap("android/content/ComponentName"));
add("com/xunmeng/pinduoduo/smart_widget_plugin/minus_screen/xm/XmMinusScreenDetect", "isInMinusScreen", DESC_BOOLEAN);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/f", "b", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/f", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/k", "a", DESC_NOTFOUND);
add("com/xunmeng/pinduoduo/android_pull_ability_comp/pullstartup/k", "b", DESC_NOTFOUND);

analyzeUtf8ToClazz:将部分opcode中使用的utf-8类型转换为classinfo

patchForFieldAndMethod:将VMP_FIELD_REF和VMP_METHOD_REF类型转换为JVM格式

writeClazzes

1
2
3
4
5
6
7
8
9
10
public void writeClazzes(String dir) throws IOException {
for (ManweVmpClazz manweVmpClazz : manweVmpClazzes) {
System.out.println("start write " + manweVmpClazz.clazzName);
Path targetPath = Paths.get(dir + File.separator + manweVmpClazz.clazzName + ".class");
Files.createDirectories(targetPath.getParent());
OutputStream fos = Files.newOutputStream(targetPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
writeClazz(manweVmpClazz, new DataOutputStream(fos));
fos.close();
}
}

将manwe的bin文件解析完毕后,通过ManweVmpDex的writeClazz输出为class文件,详细过程就不再赘述了。

总结

Manwe的文件结构如下图所示:

阅读脱壳机代码的时候总是隐隐感觉像是内部人士写出来的,很多细节不像是单纯逆向能弄得这么清楚的。只能说多行不义必自毙吧。

参考文献

https://github.com/davinci1012/pinduoduo_backdoor_unpacker