阳光惠生活逆向

阳光惠生活逆向

注入代码

疑似使用梆梆的加固,不能使用Xposed, Frida注入,即使使用frida-gadget也会被检测到。so文件也做了加固,无法直接lief添加依赖so文件(部分so可以添加成功,但APP启动的时候不会加载)。

后来想到可以通过替换so文件,将原来的so文件加到自己的so中,这样APP运行时就会先加载自己的so,同时不影响APP正常运行。

注入的框架依然是自己魔改的SandHook。

定位网络请求

首先hook了StringBuilder类的toString方法,但APP白屏崩溃,于是进行过滤,只打印”cmps.cebbank.com”相关内容,同时查看堆栈信息,发现使用了类似支付宝的RPC方案。

寻找signKey

计算签名的方式很简单: sign = md5(signKey + content)

由于之前逆向过支付宝的签名方法,偶然在内存中发现了signKey,于是我这次还是尝试通过搜索内存的方式获取signKey,但我使用的SandHook没有编译native hook的部分,只有自己写代码搜索内存。

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
static void find(uint8_t *p, unsigned long memsize)
{
for (int i = 0; i < memsize-50; ++i) {
if (p[i+32]=='&' && p[i+33]=='O' && p[i+34]=='p' && p[i+41]=='n' && p[i+42]=='-' && p[i+47]=='='){
LOGD("FOUND: %s", p+i);
}
}
}

static int scan_maps() {
char line[PATH_MAX];
unsigned long start, end;
char buf[PATH_MAX];
char path[PATH_MAX];
char tmp[100];
FILE *fp;
char maps[] = "/proc/self/maps";
fp = fopen(maps, "r");
if (fp == NULL) {
LOGE("cannot open %s", maps);
return -1;
}

while (fgets(line, PATH_MAX - 1, fp) != NULL) {
if (strstr(line, "libc_malloc") == NULL) continue;
sscanf(line, "%lx-%lx %s %s %s %s %s", &start, &end, buf, tmp, tmp, tmp, path);
uint8_t *buffer = (uint8_t *) start;
unsigned long memsize = end - start;
LOGD("start: %x, size:%d", buffer, memsize);
find(buffer, memsize);
}

LOGD("done...");
fclose(fp);
return 0;


}

void *myfunc(void * arg) {
while(1){
scan_maps();
sleep(5);
}
return NULL;
}

static int start() __attribute__((constructor));

int start() {
pthread_t pthid;
pthread_create(&pthid, NULL, myfunc, NULL);
return 0;
}

image-20221117220645912

网络请求加解密

加密方法在com.alipay.mobile.common.transport.http.selfencrypt.ClientRpcPack
实际还是调用了so中的加密。一番逆向后,发现是SM4。密钥生成依赖传入的16字节随机字符,通过hook使其固定,生成的密钥就不会再改变。

简要流程:

  1. 生成密钥
  2. 使用RSA加密密钥,本地保留一份密钥明文
  3. 加密数据(SM4-128-CBC)
  4. 发送加密密钥+加密数据

image-20221118013249406

image-20221118013213823

通过加密流程看似简单,但实际踩了不少坑。

因为是CBC加密,必须有IV,由下图可知,使用一组已知明文、密钥的加密结果,可以将IV设置为全0的,反推出IV。

但一直没成功,于是动态调试SO,发现IV确实是固定的,也是我推算出的值。原来是被Python坑了。。。

image-20221118011122303

image-20221118011044058

1
2
3
4
5
6
7
8
9
# python3
iv = '\xf4' +'\x0f'*15
print(binascii.hexlify(iv.encode()))
# b'c3b40f0f0f0f0f0f0f0f0f0f0f0f0f0f0f'


# python2
iv = '\xf4' +'\x0f'*15 print(binascii.hexlify(iv))
# f40f0f0f0f0f0f0f0f0f0f0f0f0f0f0f

最终使用Python实现的加解密:

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
def num_convert(n):
a = (n>>16)&0xff
b = (n>>8)&0xff
c = n&0xff
s = struct.pack('BBB', a,b,c)
return s

def encrypt(data):
if not isinstance(data, bytes):
data = data.encode()
compress_data = gzip.compress(data)
key = binascii.unhexlify('7ED8274FCD3C133A253A21E063EAFBD8')
iv = binascii.unhexlify('f40f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f')
crypt_sm4 = CryptSM4()
crypt_sm4.set_key(key, SM4_ENCRYPT)
encrypted_data = crypt_sm4.crypt_cbc(iv, compress_data)
length = len(encrypted_data)
len_data = num_convert(length)
result = binascii.unhexlify('03000021031e0a3408cf011170569c89d194cdb6579ec3fd2ff29582e95890385f659db5fd0d') + len_data + encrypted_data
return result

def decrypt(data):
key = binascii.unhexlify('7ED8274FCD3C133A253A21E063EAFBD8')
iv = binascii.unhexlify('f40f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f')
crypt_sm4 = CryptSM4()
crypt_sm4.set_key(key, SM4_DECRYPT)
compress_data = crypt_sm4.crypt_cbc(iv, data)
plain = gzip.decompress(compress_data)
return plain.decode()