一、人脸识别简介与应用场景
二、人脸识别框架分析
三、人脸识别的攻击面
四、攻击流程分析
五、总结
人脸识别,是基于人的脸部特征信息进行身份识别的一种生物识别技术。用摄像机或摄像头采集含有人脸的图像或视频流,并自动在图像中检测和跟踪人脸,进而对检测到的人脸进行脸部识别的一系列相关技术,通常也叫做人像识别、面部识别。
早在20世纪50年代,认知科学家就已着手对人脸识别展开研究。
20世纪60年代,人脸识别工程化应用研究正式开启。当时的方法主要利用了人脸的几何结构,通过分析人脸器官特征点及其之间的拓扑关系进行辨识。这种方法简单直观,但是一旦人脸姿态、表情发生变化,则精度严重下降。
1991年,著名的“特征脸”方法第一次将主成分分析和统计特征技术引入人脸识别,在实用效果上取得了长足的进步。这一思路也在后续研究中得到进一步发扬光大。
21世纪的前十年,随着机器学习理论的发展,学者们相继探索出了基于遗传算法、支持向量机(Support Vector Machine, SVM)、boosting、流形学习以及核方法等进行人脸识别。
2013年,MSRA的研究者首度尝试了10万规模的大训练数据,并基于高维LBP特征和Joint Bayesian方法在LFW上获得了95.17%的精度。这一结果表明:大训练数据集对于有效提升非受限环境下的人脸识别很重要。然而,以上所有这些经典方法,都难以处理大规模数据集的训练场景。
2014年前后,随着大数据和深度学习的发展,神经网络重受瞩目,并在图像分类、手写体识别、语音识别等应用中获得了远超经典方法的结果。香港中文大学的Sun Yi等人提出将卷积神经网络应用到人脸识别上,采用20万训练数据,在LFW上第一次得到超过人类水平的识别精度,这是人脸识别发展历史上的一座里程碑。
自此之后,研究者们不断改进网络结构,同时扩大训练样本规模,将LFW上的识别精度推到99.5%以上。不断在训练数据扩充、新模型设计及度量学习等方面投入更多的精力,如今大规模人脸识别己走入实用。
人脸验证产品我们应该都很熟悉了,不少人都在用人脸进行解锁手机或者刷脸支付、人脸注册登录等,不过这只是其中的一小部分,还有应用于金融、泛安防、零售等行业场景,满足身份核验、人脸考勤、闸机通行等业务需求,概括来说,人脸识别实现了一件事,确定实际人脸与目标人脸的相似度,粗略可分为:人脸1:1比对、人脸1:N检测,按照类型分类大致如图1-1所示:
图1-1
该产品主要功能包含人脸检测与属性分析、人脸对比、人脸搜索、活体检测等能力。灵活应用于金融、泛安防、零售等行业场景,满足身份核验、人脸考勤、闸机通行等业务需求。如图1-2所示:
图1-2
一般来说人脸识别分为三步,采集数据、人脸检测、结果验证,流程大致如图2-1所示:
图2-1
人脸识别是身份认证的第一步,因为首先我要确认这个人是真人,而不是视频、照片、面具等欺诈盗用行为,所以身份认证/安防的核心技术在于活体检测、人脸比对、人脸搜索;主要用于:线上远程认证场景(金融开户、刷脸注册、刷脸登录等)、线下无人值守场景(智慧交通、人脸门禁、刷脸取款、刷脸支付等)。
人脸识别主要分为人脸检测(face detecTIon)、特征提取(feature extracTIon)和人脸识别(face recogniTIon)三个过程。这期间随着硬件的升级发展,分为不同有类型,如图3-1所示:
图3-1
3D人脸模型比2D人脸模型有更强的描述能力,能更好的表达出真实人脸,所以基于3D数据的人脸识别不管识别准确率还是活体检测准确率都有很大的提高。
业务场景不同,攻击方式也不同,难易度也不同,本次攻击的目标是用于金融APP登录注册时用到的人脸识别。
目前大部分人脸识别产品防止照片攻击基本都有活体检测(点头、张嘴等动作)机制,但是这种用户体验不好,在些基础上又出现一种静默活体检测的方式,这种方式相对来说用户体验更佳。
目前常见的攻击方式如图3-2、3-3、3-4所示:
图3-2
图3-3
图3-4
第一次尝试是在手机中打开一张静态照片来模拟攻击,AI能识别出图片中的人脸,但是被检测出不是真人,“人脸验证失败”,结果如图4-1所示:
图 4-1
第二次尝,通过第一次的尝试后我想应该是AI有检测人脸是否有动态的特征我再次将一个视频放在手机中播放来欺骗AI,但是最终还是没有成功。
经过两次尝试后,还是不能过掉AI活体检测,我最终决定分析APP识别人脸的整个过程和逻辑,是否能反向推导出识别模型,然后再构造出一张对抗性图像或视频数据来达到攻击模型的目的。攻击场景如图4-2所示
图 4-2
对于离线模型其实还有一种方式是直接修改程序逻辑达到攻击的目的。
打开摄像头,画出人脸识别框
if(v4_5 != null) { Camera.CameraInfo v6_2 = new Camera.CameraInfo(); Camera.getCameraInfo(v4_5.x, v6_2); WindowManager v7 = v4_5.getWindowManager(); g.a(v7, "windowManager"); Display v7_1 = v7.getDefaultDisplay(); g.a(v7_1, "windowManager.defaultDisplay"); int v7_2 = v7_1.getRotation(); int v1 = 0; if(v7_2 != 0) { if(v7_2 == 1) { v1 = 90; } else if(v7_2 == 2) { v1 = 180; } else if(v7_2 == 3) { v1 = 270; } } int v6_3 = v6_2.facing == 1 ? 360 - (v6_2.orientation + v1) % 360 : v6_2.orientation - v1 + 360 % 360; Camera v4_6 = v4_5.w; if(v4_6 != null) { v4_6.setDisplayOrientation(v6_3); return; } g.a(); throw null; }
模型加载
public final int loadModel(AssetManager arg18) { if(arg18 != null) { JSONArray v2 = new JSONArray(new BufferedReader(new InputStreamReader(arg18.open("live/config.json"))).readLine()); ArrayList v1 = new ArrayList(); int v3 = v2.length(); int v4; for(v4 = 0; v4 < v3; ++v4) { JSONObject v5 = v2.getJSONObject(v4); g.a(v5, "jsonArray.getJSONObject(i)"); ModelConfig v6 = new ModelConfig(0f, 0f, 0f, 0, 0, null, false, 0x7F, null); String v7 = v5.optString("name"); g.a(v7, "config.optString(\"name\")"); v6.setName(v7); v6.setWidth(v5.optInt("width")); v6.setHeight(v5.optInt("height")); v6.setScale(((float)v5.optDouble("scale"))); v6.setShift_x(((float)v5.optDouble("shift_x"))); v6.setShift_y(((float)v5.optDouble("shift_y"))); v6.setOrg_resize(v5.optBoolean("org_resize")); v1.add(v6); } if(v1.isEmpty()) { Log.e("Live", "parse model config failed"); return -1; } return this.nativeLoadModel(arg18, v1); } g.a("assetManager"); throw null; }
模型初始化
从模型加载可以看到最终会调用nativeLoadModel来初始化模型 .text:0000BB30 ; _DWORD __fastcall FaceDetector::LoadModel(FaceDetector *__hidden this, AAssetManager *mgr) .text:0000BB30 EXPORT _ZN12FaceDetector9LoadModelEP13AAssetManager .text:0000BB30 _ZN12FaceDetector9LoadModelEP13AAssetManager .text:0000BB30 ; __unwind { .text:0000BB30 B0 B5 PUSH {R4,R5,R7,LR} .text:0000BB32 02 AF ADD R7, SP, #8 .text:0000BB34 05 46 MOV R5, R0 .text:0000BB36 5C 30 ADDS R0, #0x5C ; '\' .text:0000BB38 60 F9 8F 0A VLD1.32 {D16-D17}, [R0] .text:0000BB3C 0C 46 MOV R4, R1 .text:0000BB3E 68 20 MOVS R0, #0x68 ; 'h' .text:0000BB40 29 46 MOV R1, R5 .text:0000BB42 17 4A LDR R2, =(aDetectionDetec - 0xBB50) .text:0000BB44 41 F9 80 0A VST1.32 {D16-D17}, [R1],R0 .text:0000BB48 05 F1 0C 00 ADD.W R0, R5, #0xC .text:0000BB4C 7A 44 ADD R2, PC ; "detection/detection.param" .text:0000BB4E 61 F9 8F 0A VLD1.32 {D16-D17}, [R1] .text:0000BB52 21 46 MOV R1, R4 ; mgr .text:0000BB54 40 F9 8F 0A VST1.32 {D16-D17}, [R0] .text:0000BB58 28 46 MOV R0, R5 ; int .text:0000BB5A 0C F0 AD FF BL sub_18AB8 .text:0000BB5E 50 B1 CBZ R0, loc_BB76 .text:0000BB60 10 49 LDR R1, =(aEngine - 0xBB6C) ; "Engine" .text:0000BB62 03 46 MOV R3, R0 .text:0000BB64 10 4A LDR R2, =(aFacedetectorLo - 0xBB6E) .text:0000BB66 06 20 MOVS R0, #6 ; prio .text:0000BB68 79 44 ADD R1, PC ; "Engine" .text:0000BB6A 7A 44 ADD R2, PC ; "FaceDetector load param failed. %d" .text:0000BB6C FE F7 EE E8 BLX __android_log_print .text:0000BB70 4F F0 FF 30 MOV.W R0, #0xFFFFFFFF .text:0000BB74 B0 BD POP {R4,R5,R7,PC} .text:0000BB76 .text:0000BB76 loc_BB76 .text:0000BB76 0D 4A LDR R2, =(aDetectionDetec_0 - 0xBB80) ; "detection/detection.bin" .text:0000BB78 28 46 MOV R0, R5 ; int .text:0000BB7A 21 46 MOV R1, R4 ; mgr .text:0000BB7C 7A 44 ADD R2, PC ; "detection/detection.bin" .text:0000BB7E 0D F0 63 F8 BL sub_18C48 .text:0000BB82 50 B1 CBZ R0, loc_BB9A .text:0000BB84 0A 49 LDR R1, =(aEngine - 0xBB90) ; "Engine" .text:0000BB86 03 46 MOV R3, R0 .text:0000BB88 0A 4A LDR R2, =(aFacedetectorLo_0 - 0xBB92) .text:0000BB8A 06 20 MOVS R0, #6 ; prio .text:0000BB8C 79 44 ADD R1, PC ; "Engine" .text:0000BB8E 7A 44 ADD R2, PC .text:0000BB90 FE F7 DC E8 BLX __android_log_print .text:0000BB94 6F F0 01 00 MOV R0, #0xFFFFFFFE .text:0000BB98 B0 BD POP {R4,R5,R7,PC} .text:0000BB9A 00 20 MOVS R0, #0 .text:0000BB9C B0 BD POP {R4,R5,R7,PC} //从二进制文件中载入模型并解析 .text:00016A64 CA 48 LDR R0, =(__sF_ptr - 0x16A6A) .text:00016A66 78 44 ADD R0, PC ; __sF_ptr .text:00016A68 01 68 LDR R1, [R0] ; __sF .text:00016A6A CA 48 LDR R0, =(aInvalidLayerCo - 0x16A76) ; "invalid layer_count or blob_count\n" .text:00016A6C 01 F1 A8 03 ADD.W R3, R1, #0xA8 .text:00016A70 22 21 MOVS R1, #0x22 ; '"' .text:00016A72 78 44 ADD R0, PC ; "invalid layer_count or blob_count\n" .text:00016A74 22 E0 B loc_16ABC .text:00016A76 .text:00016A76 loc_16A76 ; CODE XREF: sub_169E4+36↑j .text:00016A76 C8 48 LDR R0, =(__sF_ptr - 0x16A7C) .text:00016A78 78 44 ADD R0, PC ; __sF_ptr .text:00016A7A 01 68 LDR R1, [R0] ; __sF .text:00016A7C C7 48 LDR R0, =(aParseMagicFail - 0x16A88) ; "parse magic failed\n" .text:00016A7E 01 F1 A8 03 ADD.W R3, R1, #0xA8 .text:00016A82 13 21 MOVS R1, #0x13 .text:00016A84 78 44 ADD R0, PC ; "parse magic failed\n" .text:00016A86 19 E0 B loc_16ABC .text:00016A88 .text:00016A88 C5 48 LDR R0, =(__sF_ptr - 0x16A8E) .text:00016A8A 78 44 ADD R0, PC ; __sF_ptr .text:00016A8C 01 68 LDR R1, [R0] ; __sF .text:00016A8E C5 48 LDR R0, =(aParamIsTooOldP - 0x16A9A) ; "param is too old, please regenerate\n" .text:00016A90 01 F1 A8 03 ADD.W R3, R1, #0xA8 .text:00016A94 24 21 MOVS R1, #0x24 ; '$' .text:00016A96 78 44 ADD R0, PC ; "param is too old, please regenerate\n" .text:00016A98 10 E0 B loc_16ABC .text:00016A9A .text:00016A9A loc_16A9A ; CODE XREF: sub_169E4+5E↑j .text:00016A9A C3 48 LDR R0, =(__sF_ptr - 0x16AA0) .text:00016A9C 78 44 ADD R0, PC ; __sF_ptr .text:00016A9E 01 68 LDR R1, [R0] ; __sF .text:00016AA0 C2 48 LDR R0, =(aParseLayerCoun - 0x16AAC) ; "parse layer_count failed\n" .text:00016AA2 01 F1 A8 03 ADD.W R3, R1, #0xA8 .text:00016AA6 19 21 MOVS R1, #0x19 .text:00016AA8 78 44 ADD R0, PC ; "parse layer_count failed\n" .text:00016AAA 07 E0 B loc_16ABC
以上是初始化过程,主要逻辑是读取模型解析出网络的layer层数及blob数,遍历所有的layer,解析每个layer层的类型(layer_type)、名称(layer_name)、输入数(bottom_count)和输出数(top_count),设置layer参数:layer的类型、名字、输入和输出。
获取人脸数据
@Override // f.l.j.a.a public final Object c_onPreviewFrame(Object arg13) { b v3; r.d(arg13); MainActivity v13 = this.j.e; v13.G = true; c v1 = v13.u; if(v1 != null) { byte[] Facedata = this.k; int v5 = v13.y; int v6 = v13.z; int v7 = v13.A; if(Facedata != null) { List v13_1 = v1.a.detect(Facedata, v5, v6, v7);// 识别人脸方法 if((v13_1.isEmpty() ^ 1) == 0) { v3 = new b(); } else { long v10 = System.currentTimeMillis(); FaceBox v13_2 = (FaceBox)v13_1.get(0); v13_2.setConfidence(v1.b.a(Facedata, v5, v6, v7, v13_2)); v3 = new b(v13_2, System.currentTimeMillis() - v10, true); } MainActivity v13_3 = this.j.e; v3.h = v13_3.v; Rect v6_1 = new Rect(((int)(((float)v3.b) * v13_3.D)), ((int)(((float)v3.c) * v13_3.E)), ((int)(((float)v3.d) * v13_3.D)), ((int)(((float)v3.e) * v13_3.E))); a v13_4 = MainActivity.a(this.j.e); v3.b = v6_1.left; v3.a(4); v3.c = v6_1.top; v3.a(9); v3.d = v6_1.right; v3.a(6); v3.e = v6_1.bottom; v3.a(1); v13_4.a(v3); Log.d("MainActivity", "threshold:" + v3.h + ", confidence: " + v3.f); MainActivity.a(this.j.e).p.postInvalidate(); this.j.e.G = false; return j.a; } g.a("yuv"); throw null; } //调用到Native方法nativeDetectYuv public final List detect(byte[] arg3, int arg4, int arg5, int arg6) { if(arg3 != null) { if(arg4 * arg5 * 3 / 2 == arg3.length) { return this.nativeDetectYuv(arg3, arg4, arg5, arg6); } throw new IllegalArgumentException("Invalid yuv data"); } g.a("yuv"); throw null; }
数据处理
手机摄像头获取的数据是yuv420sp格式,AI模型要求图像输入格式BGR,所以需要做格式转换。 .text:0000AC60 F0 B5 PUSH {R4-R7,LR} .text:0000AC62 03 AF ADD R7, SP, #0xC .text:0000AC64 2D E9 00 0F PUSH.W {R8-R11} .text:0000AC68 99 B0 SUB SP, SP, #0x64 .text:0000AC6A 0D 46 MOV R5, R1 .text:0000AC6C 5B 49 LDR R1, =(__stack_chk_guard_ptr - 0xAC78) .text:0000AC6E 00 26 MOVS R6, #0 .text:0000AC70 0D F1 28 0B ADD.W R11, SP, #0x80+var_58 .text:0000AC74 79 44 ADD R1, PC ; __stack_chk_guard_ptr .text:0000AC76 99 46 MOV R9, R3 .text:0000AC78 14 46 MOV R4, R2 .text:0000AC7A 00 23 MOVS R3, #0 ; int .text:0000AC7C D1 F8 00 A0 LDR.W R10, [R1] ; __stack_chk_guard .text:0000AC80 DA F8 00 10 LDR.W R1, [R10] .text:0000AC84 18 91 STR R1, [SP,#0x80+var_20] .text:0000AC86 CD E9 00 06 STRD.W R0, R6, [SP,#0x80+var_80] ; void * .text:0000AC8A 02 EB D2 70 ADD.W R0, R2, R2,LSR#31 .text:0000AC8E 02 EB 60 01 ADD.W R1, R2, R0,ASR#1 ; int .text:0000AC92 58 46 MOV R0, R11 ; this .text:0000AC94 2A 46 MOV R2, R5 ; int .text:0000AC96 FF F7 4E E8 BLX j__ZN2cv3MatC2EiiiPvj ; cv::Mat::Mat(int,int,int,void *,uint) .text:0000AC9A D7 F8 08 80 LDR.W R8, [R7,#arg_0] .text:0000AC9E 40 46 MOV R0, R8 ; this .text:0000ACA0 21 46 MOV R1, R4 ; int .text:0000ACA2 2A 46 MOV R2, R5 ; int .text:0000ACA4 10 23 MOVS R3, #0x10 ; int .text:0000ACA6 FF F7 7C E8 BLX j__ZN2cv3Mat6createEiii ; cv::Mat::create(int,int,int) .text:0000ACAA 4B 4D LDR R5, =0x1010000 .text:0000ACAC CD E9 08 66 STRD.W R6, R6, [SP,#0x80+var_60] .text:0000ACB0 05 F1 80 74 ADD.W R4, R5, #0x1000000 .text:0000ACB4 CD E9 06 5B STRD.W R5, R11, [SP,#0x80+var_68] .text:0000ACB8 CD E9 04 66 STRD.W R6, R6, [SP,#0x80+var_70] .text:0000ACBC CD E9 02 48 STRD.W R4, R8, [SP,#0x80+var_78] .text:0000ACC0 06 A8 ADD R0, SP, #0x80+var_68 .text:0000ACC2 02 A9 ADD R1, SP, #0x80+var_78 .text:0000ACC4 5D 22 MOVS R2, #0x5D ; ']' .text:0000ACC6 00 23 MOVS R3, #0 .text:0000ACC8 FF F7 3A E8 BLX _ZN2cv8cvtColorERKNS_11_InputArrayERKNS_12_OutputArrayEii ; cv::cvtColor(cv::_InputArray const&,cv::_OutputArray const&,int,int) .text:0000ACCC A9 F1 02 00 SUB.W R0, R9, #2 ; switch 7 cases .text:0000ACD0 06 28 CMP R0, #6 .text:0000ACD2 64 D8 BHI def_ACD4 ; jumptable 0000ACD4 default case .text:0000ACD4 DF E8 00 F0 TBB.W [PC,R0] ; switch jump
创建一个模型对象,设置输入;提取中间节点的运算结果
.text:0000BBB8 F0 B5 PUSH {R4-R7,LR} .text:0000BBBA 03 AF ADD R7, SP, #0xC .text:0000BBBC 2D E9 00 0B PUSH.W {R8,R9,R11} .text:0000BBC0 2D ED 0A 8B VPUSH {D8-D12} .text:0000BBC4 AA B0 SUB SP, SP, #0xA8 .text:0000BBC6 05 46 MOV R5, R0 .text:0000BBC8 96 48 LDR R0, =(__stack_chk_guard_ptr - 0xBBD2) .text:0000BBCA 14 46 MOV R4, R2 .text:0000BBCC 0E 46 MOV R6, R1 .text:0000BBCE 78 44 ADD R0, PC ; __stack_chk_guard_ptr .text:0000BBD0 D0 F8 00 80 LDR.W R8, [R0] ; __stack_chk_guard .text:0000BBD4 D8 F8 00 00 LDR.W R0, [R8] .text:0000BBD8 29 90 STR R0, [SP,#0xE8+var_44] .text:0000BBDA 91 ED 02 0A VLDR S0, [R1,#8] .text:0000BBDE 91 ED 03 1A VLDR S2, [R1,#0xC] .text:0000BBE2 B8 EE C0 8A VCVT.F32.S32 S16, S0 .text:0000BBE6 B8 EE C1 9A VCVT.F32.S32 S18, S2 .text:0000BBEA 95 ED 10 0A VLDR S0, [R5,#0x40] .text:0000BBEE B8 EE C0 CA VCVT.F32.S32 S24, S0 .text:0000BBF2 89 EE 08 BA VDIV.F32 S22, S18, S16 .text:0000BBF6 B1 EE CB AA VSQRT.F32 S20, S22 .text:0000BBFA B0 EE 4A 0A VMOV.F32 S0, S20 .text:0000BBFE B4 EE CA AA VCMPE.F32 S20, S20 .text:0000BC02 F1 EE 10 FA VMRS APSR_nzcv, FPSCR .text:0000BC06 05 D7 BVC loc_BC14 .text:0000BC08 1B EE 10 0A VMOV R0, S22 ; x .text:0000BC0C FE F7 B8 E9 BLX sqrtf .text:0000BC10 00 EE 10 0A VMOV S0, R0 .text:0000BC14 .text:0000BC14 20 EE 0C 0A VMUL.F32 S0, S0, S24 .text:0000BC18 95 ED 10 1A VLDR S2, [R5,#0x40] .text:0000BC1C B4 EE CA AA VCMPE.F32 S20, S20 .text:0000BC20 B8 EE C1 CA VCVT.F32.S32 S24, S2 .text:0000BC24 F1 EE 10 FA VMRS APSR_nzcv, FPSCR .text:0000BC28 BD EE C0 0A VCVT.S32.F32 S0, S0 .text:0000BC2C 10 EE 10 9A VMOV R9, S0 .text:0000BC30 05 D7 BVC loc_BC3E .text:0000BC32 1B EE 10 0A VMOV R0, S22 ; x .text:0000BC36 FE F7 A4 E9 BLX sqrtf .text:0000BC3A 0A EE 10 0A VMOV S20, R0 .text:0000BC3E .text:0000BC3E 8C EE 0A 0A VDIV.F32 S0, S24, S20 .text:0000BC42 D6 E9 02 03 LDRD.W R0, R3, [R6,#8] .text:0000BC46 31 69 LDR R1, [R6,#0x10] .text:0000BC48 1F AE ADD R6, SP, #0xE8+var_6C .text:0000BC4A 00 22 MOVS R2, #0 .text:0000BC4C BD EE C0 0A VCVT.S32.F32 S0, S0 .text:0000BC50 03 92 STR R2, [SP,#0xE8+var_DC] .text:0000BC52 CD E9 00 09 STRD.W R0, R9, [SP,#0xE8+var_E8] .text:0000BC56 30 46 MOV R0, R6 .text:0000BC58 02 22 MOVS R2, #2 .text:0000BC5A 8D ED 02 0A VSTR S0, [SP,#0xE8+var_E0] .text:0000BC5E 05 F0 C5 F9 BL sub_10FEC ; from_pixels_resize .text:0000BC62 05 F1 7C 01 ADD.W R1, R5, #0x7C ; '|' .text:0000BC66 30 46 MOV R0, R6 .text:0000BC68 00 22 MOVS R2, #0 .text:0000BC6A 01 F0 B1 FC BL sub_D5D0 ; substract_mean_normalize .text:0000BC6E 14 A8 ADD R0, SP, #0xE8+var_98 .text:0000BC70 29 46 MOV R1, R5 .text:0000BC72 0D F0 29 F8 BL sub_18CC8 ; create_extractor .text:0000BC76 D5 F8 88 10 LDR.W R1, [R5,#0x88] .text:0000BC7A 14 A8 ADD R0, SP, #0xE8+var_98 .text:0000BC7C 0E F0 2C FB BL sub_1A2D8 ; set_num_threads .text:0000BC80 28 46 MOV R0, R5 .text:0000BC82 10 F8 44 1F LDRB.W R1, [R0,#0x44]! .text:0000BC86 C9 07 LSLS R1, R1, #0x1F .text:0000BC88 0C BF ITE EQ .text:0000BC8A 41 1C ADDEQ R1, R0, #1 .text:0000BC8C E9 6C LDRNE R1, [R5,#0x4C] .text:0000BC8E 14 A8 ADD R0, SP, #0xE8+var_98 .text:0000BC90 1F AA ADD R2, SP, #0xE8+var_6C .text:0000BC92 0E F0 27 FB BL sub_1A2E4 ; input .text:0000BC96 0A A8 ADD R0, SP, #0xE8+var_C0 .text:0000BC98 C0 EF 50 00 VMOV.I32 Q8, #0 .text:0000BC9C 00 F1 10 01 ADD.W R1, R0, #0x10 .text:0000BCA0 41 F9 CF 0A VST1.64 {D16-D17}, [R1] .text:0000BCA4 24 21 MOVS R1, #0x24 ; '$' .text:0000BCA6 40 F9 C1 0A VST1.64 {D16-D17}, [R0],R1 .text:0000BCAA 00 21 MOVS R1, #0 .text:0000BCAC 01 60 STR R1, [R0] .text:0000BCAE 28 46 MOV R0, R5 .text:0000BCB0 12 91 STR R1, [SP,#0xE8+var_A0] .text:0000BCB2 10 F8 50 1F LDRB.W R1, [R0,#0x50]! .text:0000BCB6 C9 07 LSLS R1, R1, #0x1F .text:0000BCB8 0C BF ITE EQ .text:0000BCBA 41 1C ADDEQ R1, R0, #1 .text:0000BCBC A9 6D LDRNE R1, [R5,#0x58] .text:0000BCBE 14 A8 ADD R0, SP, #0xE8+var_98 .text:0000BCC0 0A AA ADD R2, SP, #0xE8+var_C0 .text:0000BCC2 0E F0 82 FB BL sub_1A3CA ; extract .text:0000BCC6 B7 EE 00 AA VMOV.F32 S20, #1.0 .text:0000BCCA 0D F1 10 09 ADD.W R9, SP, #0xE8+var_D8 .text:0000BCCE B6 EE 00 BA VMOV.F32 S22, #0.5 .text:0000BCD2 20 68 LDR R0, [R4] .text:0000BCD4 BF EE 00 CA VMOV.F32 S24, #-1.0 .text:0000BCD8 00 26 MOVS R6, #0 .text:0000BCDA 60 60 STR R0, [R4,#4]
通过调试分析找到两个关键点,Patch这两个点就能过掉检测:
人脸检测:
.text:CC0CC6C8 BF EE 00 CA VMOV.F32 S24, #-1.0 .text:CC0CC6CC 00 26 MOVS R6, #0 .text:CC0CC6CE 60 60 STR R0, [R4,#4] .text:CC0CC6D0 5C E0 B loc_CC0CC78C .text:CC0CC6D2 // box.confidence = confidence;//人脸分数 .text:CC0CC6D2 .text:CC0CC6D2 10 9A LDR R2, [SP,#0xE8+var_A8] .text:CC0CC6D4 0C 99 LDR R1, [SP,#0xE8+var_B8] .text:CC0CC6D6 0A 98 LDR R0, [SP,#0xE8+var_C0] .text:CC0CC6D8 72 43 MULS R2, R6 .text:CC0CC6DA ; 66: if ( v11[1] >= *(float *)(a1 + 120) ) .text:CC0CC6DA 95 ED 1E 1A VLDR S2, [R5,#0x78] .text:CC0CC6DE 02 FB 01 00 MLA.W R0, R2, R1, R0 .text:CC0CC6E2 90 ED 01 0A VLDR S0, [R0,#4] .text:CC0CC6E6 B4 EE C1 0A VCMPE.F32 S0, S2 .text:CC0CC6EA F1 EE 10 FA VMRS APSR_nzcv, FPSCR .text:CC0CC6EE 4C D4 BMI loc_CC0CC78A ; 判断是否为人脸,Patch点1
Patch检测是否为人脸,将其Patch永远为真
.text:0000C72C 9B ED 1E 0A VLDR S0, [R11,#0x78] .text:0000C730 B4 EE C0 DA VCMPE.F32 S26, S0 .text:0000C734 F1 EE 10 FA VMRS APSR_nzcv, FPSCR .text:0000C738 4C DC BGT loc_C7D4 ; 大于
活体检测:
.text:CC20D9CA DA F8 30 00 LDR.W R0, [R10,#0x30] .text:CC20D9CE 83 42 CMP R3, R0 .text:CC20D9D0 FF F6 32 AF BLT.W loc_CC20D838 .text:CC20D9D4 00 EE 10 0A VMOV S0, R0 ; 活体分数Patch点2 .text:CC20D9D8 B8 EE C0 0A VCVT.F32.S32 S0, S0 .text:CC20D9DC 09 98 LDR R0, [SP,#0x178+var_154] ; 存放活体分数空间 .text:CC20D9DE 88 EE 00 0A VDIV.F32 S0, S16, S0 ; 活体分数 .text:CC20D9E2 80 ED 00 0A VSTR S0, [R0] .text:CC20D9E6 4D 98 LDR R0, [SP,#0x178+var_44] .text:CC20D9E8 06 99 LDR R1, [SP,#0x178+var_160] .text:CC20D9EA 09 68 LDR R1, [R1] .text:CC20D9EC 08 1A SUBS R0, R1, R0
Patch检测是否为活体,将其Patch永远为真的值
.text:0000DA40 09 99 LDR R1, [SP,#0x178+var_154] .text:0000DA42 19 48 LDR R0, =0x3F7B3380 ; patch值 .text:0000DA44 08 60 STR R0, [R1] ; 存放空间 .text:0000DA46 4D 99 LDR R1, [SP,#0x178+var_44] .text:0000DA48 06 9A LDR R2, [SP,#0x178+var_160] .text:0000DA4A 12 68 LDR R2, [R2] .text:0000DA4C 51 1A SUBS R1, R2, R1 .text:0000DA4E 01 BF ITTTT EQ .text:0000DA50 4E B0 ADDEQ SP, SP, #0x138 .text:0000DA52 BD EC 08 8B VPOPEQ {D8-D11} .text:0000DA56 01 B0 ADDEQ SP, SP, #4 .text:0000DA58 BD E8 00 0F POPEQ.W {R8-R11}
patch完后我们再用手机中的同一张静态照来试试是否能验证成功?效果如图4-3所示:
图4-3
攻击成功,完美通过。
上面通过Patch的方式来过掉检测看起来是很好,但是、但是、但是,当相同的AI模型放在服务器端时,Patch这种方式就失效了,通过前面对模型进行逆向分析,获取模型内部的一些特征数据,所以需要根据分析出来的模型特征构造出一张对抗性图像或视频数据送给服务器端达到攻击模型的目的。
上面分析活体检测时得到的活体特征,再根据YUV420数据格式将其构造到数据体中,得到一张人眼无法观看只有机器能识别的图像数据,如图4-4、4-5所示:
图4-4
图4-5
通过hook摄像头读取方法可以直接将构造好的数据传送给服务器端模型,效果如图4-6所示:
图4-6
这种方式是不改变目标AI学习系统的情况下,通过构造特定输入样本以完成欺骗目标系统的攻击。我用一张A4纸打印了一张非真人脸图做测试,在没有真人脸的情况下成功通过验证。这其中的根本原因,在于模型没有学到完美的判别规则。虽然图片识别系统一直试图在计算机上模仿人类肉眼视觉功能,但由于人类肉眼视觉机理过于复杂,两个系统在判别物体时依赖的判断规则存在一定差异。因此人类肉眼的判别规则和模型实际学到的判别规则之间的差距,就给了攻击者逃脱模型检测提供可趁之机。
本文主要是最近自己学习人脸识别的一点总结,与小伙伴们分享,有不对的地方请指正。
主要分为攻击模型计算逻辑与攻击模型本身两种方式,不同的业务场景攻击方案与难易成度不同。
模型计算逻辑攻击:
这种攻击方式只适用于离线模型,分析模型计算逻辑进行关键点Patch达到强制绕过验证的目的。
攻击模型:
攻击模型和其他攻击不同,攻击模型主要发生在构造对抗性数据的时候,之后该对抗性数据就如正常数据一样输入机器学习模型并得到欺骗的识别结果。在构造对抗性数据的过程中,攻击者并不知道AI所使用的算法和参数,但攻击者仍能与AI系统有所交互,比如可以通过传入任意输入观察输出,判断输出。或者攻击者可以通过逆向分析推导掌握AI模型参数信息。
欢迎关注公众号: