(系统之家peu盘启动盘制作工具)

今天周五了,不知道大家想看点什么,Tiger就更新一个CTF的多元线性方程吧,前阵子HW闹的好多话不敢说,下周正常恢复更新,如果各位亲爱的粉丝们有哪方面想看的文章或者问题,可以在下方留言告诉Tiger或者去咱官网查看www.edusahoo.com免费的课程。

今天更两篇,祝大家看的舒服~手机不卡机~

概述

本片分析文章通过一道看雪CTF题讲述作者的整个分析流程,学习WebAssemble,Z3库,IDC脚本,多元线性方程等内容

分析流程

安装应用后,出现一个输入框和一个按钮

安卓

jadx反编译的apk后先查看清单清单文件的注册组件,只有一个入口活动类,进入查看

<application android:theme = “@ style / AppTheme” android:label = “@ string / app_name” android:icon = “@ mipmap / ic_launcher” android:allowBackup = “true” android:supportsRtl = “true” android:usesCleartextTraffic = “true” android:roundIcon = “@ mipmap / ic_launcher_round” android:appComponentFactory = “android.support.v4.app.CoreComponentFactory” > <activity android:name = “com.example.assemgogogo.MainActivity” > <intent-filter> <action android:name = “android.intent.action.MAIN”/> <category android:name =“android.intent.category.LAUNCHER” /> </ intent-filter> </ activity>

初现迷雾

第一眼:看到Congratulations,我们的目标是打印出这里的字符串,也就是点击按钮后调用本地方法check_key返回值为1即可

再仔细看看:这里有个网页视图组件,这个组件表示有访问网络的操作,但是手机界面并没有看页面,我们去布局文件中看看,只需我们要看一个属性android:visibility表示控件是否可见,只有WebView中可见,按钮,输入框什么的都是隐藏的,并且这个网页流量占据的整个界面,所以我们看到的输入框和按钮都是网页展示的,所以我们需要关注的点就是sayHello的这个本地方法,它传入的网址是哪里的,这是我们下一步的要干的事

public String u = gogogoJNI 。sayHello (); 静态的 { 系统。loadLibrary (“gogogo” ); } protected void onCreate (Bundle bundle ) { super 。onCreate (捆绑); 的setContentView ((INT ) - [R 。布局。activity_main ); 这个。eText1 = (的EditText ) findViewById (? 。ID 。EDITTEXT ); 这个。txView1 = (TextView的) findViewById (? 。ID。textView ); ((web视图) findViewById (? 。ID 。text1View ))。使用loadURL (此。? ); ((web视图) findViewById (? 。ID 。text1View ))。getSettings ()。setJavaScriptEnabled (true ); 这个。按钮1 = (按钮) findViewById (? 。ID 。按钮); 这个。button1 。setOnClickListener (新 OnClickListener () { 公共 无效 的onClick (查看 视图) { 如果 (gogogoJNI 。check_key (MainActivity 。这个。eText1 。gettext的()。的toString ()) == 1 ) { MainActivity 。这个。txView1 。的setText (“恭喜!“ ); } else { MainActivity 。这个。txView1 。setText (“Not Correct!” ); } } };<EditText android:id = “@ + id / editText” android:visibility = “invisible” android:layout_width = “wrap_content” android:layout_height = “wrap_content” android:text = “Name” android:ems = “10” android: inputType = “textPersonName” android:layout_marginStart = “14dp” app:layout_constraintBottom_toBottomOf = “@ + id / button” app:layout_constraintStart_toEndOf = “@ + id / textView” /> <Button android:id = “@ + id / button” android :visibility = “隐形” android:layout_width = “wrap_content” android:layout_height =“wrap_content” android:layout_marginTop = “52dp” android:text = “check” android:layout_marginEnd = “38dp” app:layout_constraintEnd_toEndOf = “0” app:layout_constraintTop_toTopOf = “0” /> <TextView android:id = “@ + id / textView“ android:visibility = ”invisible“ android:layout_width = ”wrap_content“ android:layout_height = ”wrap_content“ android:layout_marginTop = ”68dp“ android:text = ”key“ android:layout_marginStart = ”22dp“ app:layout_constraintStart_toStartOf = “0” app:layout_constraintTop_toTopOf =“0” /> <WebView android:id = “@ + id / text1View” android:visibility = “visible” android:layout_width = “390dp” android:layout_height = “733dp” android:layout_marginStart = “4dp” app:layout_constraintStart_toStartOf = “0” app:layout_constraintTop_toTopOf = “0” />

探索网址

打开LIB文件夹,出现四个ABI架构对应的那么文件,基本现在手机的芯片都是支持的,这里ARM64在ida6.8不能使用F5大法,所以我们就分析armeabi-V7这个就行了

我们可以看看第一步我们排除的check_key方法,这里逻辑是输出的32位数都为1即可返回1,实际尝试是错误的,混淆视听

在导出表中找到SayHello的方法,要使用F5大法先右键将这个区域代码创建为函数。接着讲这个字节数组异或计算即可的到URL地址。下面写了一个简短的IDC脚本获取到URL地址为http://127.0.0.1:8000

分析到这里,虽然我们探索网址已经完成,但是却没有看见服务端处理的函数,这个才是我们访问URL的时候,处理我们访问请求的函数

#include <idc.idc>static main (){ auto addr = 0x2d28 ; 汽车 我; for (i = 0 ; i != 21 ; ++ i ) { Message (“%c” , Byte (addr + i )^ 0x66 ); } }

探索服务端处理函数

从Java的层分析的逻辑中并没有服务端的线索,而这样层也只有初始化的JNIonload,初始化节还没有探索,这是我们接下来的目标

第一步排除初始化节,所以加载后首先执行的节代码,这里可以看出没有这个节,所以排除,那么就直接分析JNIonload方法,JAVA中调用loadlibray的时候调用的方法

JNI_Onload分析:往进电话两层,最终调用下面这个函数

int (__cdecl *inti_proc(void))(int){ return inti_proc();}

这个函数一开始就对数据段中一块大小为34291的数据进行异或0x67解密,接着创建线程用socket链接将刚才解密的内容构造称HTTP的响应数据包,一旦有socket链接连接过来就发送这样的数据包回去。

逻辑分析清除,下面我们针对细节进行解决

电脑

针对需要解密的字节流,通过IDC脚本进行处理,解密后的数据是HTML页面,使用到了WebAssembly技术,网页汇编的灵魂就是将其他语言如C ^汇编成前端可以解释的语言,即用?语言写页面的一些逻辑。

#include <idc.idc>static main (){ auto addr = 0x4004 ; auto i = 34291 ; 而(i ) { - i ; 消息(“%c” , 字节(addr ++ )^ 0x67 ); }}

分析这里的逻辑得知,我们需要让输入内容为32位并且check_key()函数返回结果为1,即可完成这道题

<!DOCTYPE html> < html > < head > < meta http-equiv = “content-type” content = “text / html; charset = UTF-8” > < meta charset = “utf-8” > < style > body { 背景色: RGB (255 , 255 , 255 ); } </ style > </ head > < script >var 实例;WebAssembly 。编译(新的 Uint8Array (` 00 61 73 6D 01 00 00 00 01 1B 05 60 00 00 60 04 7F 7F 7F 7F 01 7F 60 02 7F 7F 01 7F 60 01 7F 01 ....... 66 6C 61 67 0A 12 73 65 74 5F 69 6E 70 75 74 5F 66 6C 61 67 5F 6C 65 6E 0B 09 63 68 65 63 6B 5F 6B 65 79 0C 03 78 78 78 ` 。电脑修剪()。分裂(/ [\ s \ r \ N] + /克)。图(STR => parseInt函数(STR , 16 )) ))。然后(module => { new WebAssembly。实例化(模块)。然后(results => { instance = results ; })。捕捉(控制台。误差);})函数 check_flag (){ VAR 值 = 文档。getElementById (“key_value” )。价值; 如果(值。长度 != 32 ) { 文档。getElementById (“tips” )。innerHTML = “不正确!” ; 回归; } 实例。出口。set_input_flag_len (值。长度); 为(VAR II = 0 ; 二< 值。长度; 二++ ){ 实例。出口。set_input_flag (value [ ii ] .charCodeAt (),ii ); } var ret = 实例。出口。check_key (); if (ret == 1 ){ document 。getElementById (“tips” )。innerHTML = “祝贺!” } else { document 。getElementById (“tips” )。innerHTML = “不正确!” } } </ 脚本> < 体> < DIV >键:< 输入 ID = “key_value” 类型= “文本” style = “width:60%” ; =“” value = “” > < input type = “submit” value = “check” onclick = “check_flag()” > </ div > < div > < label id = “tips “ > </ label > </ div &电脑gt;</ body > </ html >

下面我们进入网页汇编来探索内部实现逻辑

webassemble

我们在这部分探索的目标就是先用16进制内容构成对应的WASM二进制文件,然后将WASM二进制文件转成C,接着生成ELF文件,用IDA进行分析。

先生成data.bin二进制文件

import array , structhexstring = “ \ x00 \ x61 \ x73 \ x6D \ x01 \ x00 \ x00 \ x00 \ x01 \ x1B \ x05 \ x60 \ x00 \ x00 \ x60 \ x04 \ x7F \ x7F \ x7F \ x7F \ x01 \ x7F \ x60 \ x02 \ x7F \ x7F \ x01 \ x7F \ x60 \ x01 \ x7F \ x01 \ x7F \ x60 \ x00 \ x01 \ x7F ............. \ x6C \ x61 \ x67 \ x0A \ x12 \ X73 \ X65 \ X74 \ X5F \ X69 \ x6E \ X70 \ X75 \ X74 \ X5F \ X66 \ X6C \ X61 \ X67 \ X5F \x6C \ x65 \ x6E \ x0B \ x09 \ x63 \ x68 \ x65 \ x63 \ x6B \ x5F \ x6B \ x65 \ x79 \ x0C \ x03 \ x78 \ x78 \ x78 “f = open ('c:\\ Users \\ xxx \\ Desktop \\ data.bin' ,'wb' )f 。write (hexstring )f 。关闭()

接着用wasm2c.exe生成?文件

wasm2c 。exe 数据。bin -o test 。C

直接gcc wasm.c会报错,因为很多wasm的函数没有具体的实现。所以只编译就好了

gcc -c test.c -o test.o

用IDA打开的.o文件

首先JS中调用将输入的字符长度保存到内存中,接着将输入的字符也保存到内存0x400的处

接着就是主要的check_key函数,最终目标是XXX函数返回结果为1,即可完成逆向工作

这里前8个?函数对我们输入的32内容依次进行了处理,我们具体分析一下

一重加密

经过简单分析,这里其实是对输入内容进行了异或计算,然后将结果替换内存中原来的数据。下面图中的条件是肯定满足的,因为我们输入的内容在33到127之间,最小的33 * 4也等于132肯定不为-1时,这个语句恒执行其他的内容,至于其余几个都是相同的内容,即在这里对输入内容进行第一次加密

32元线性方程组

接着我们分析XXX函数,我们的目标也是满足XXX函数返回值为1

从内存中奖一重加密后的输入内容读取到变量中,可以看到顺序做过修改

接下来就是下图中看到的32元方程组,如果有兴趣和数学基础的同学可以用矩阵解法写个类似的小脚本,这里我用到的是Z3库解决

解密

一重解密

PIP安装Z3求解器

接着用蟒脚本写一个求解语句,先初始化32个变量,接着将IDA的内容拷贝过来,将符号修改一下即可

#* - *编码:utf-8 - * - 来自 z3 进口 *#生面32元变量v5 = Int ('m53' )v6 = Int ('m52' )v7 = Int ('m51' )v8 = Int ('m50' )v9 = Int ('m49' )v10 = Int ('m48' )v11 = Int ('m47' )v12 = Int ('m46' )v13 = Int ('m45' )V14 = 诠释('M44' )V15 = 诠释('M43' )V16 = 诠释('M42' )V17 = 诠释('M41' )V18 = 诠释('M40' )V19 = 诠释('M39' )V20 = Int ('m38' )v21 = Int ('m37' )v22 = Int ('m36')v23 = INT ('M35' )V24 = INT ('M34' )V25 = INT ('M33' )V26 = INT ('M32' ) V27 = INT ('M31' )V28 = INT ('M30' )V29 = INT ('m29' )v30 = Int ('m28' )v31 = Int ('m27')v32 = Int ('m26' )v33 = Int ('m24' )v34 = Int ('m25' )v35 = Int ('m55' )v36 = Int ('m54' )#实例化一个求解器对象s = 求解器()s 。添加(和(45 * v5 + 248 * v6 + 20 * v7 + 67 * v8 + 90 * v9 + 135 * v10 + 106 * v11 + 112 * v12 + 40 * v13 + 231 * v14 + 153 * v15 + 233 * v16 + 19 * v17 + 188 * V18 + 232 * V19 + 127 * V20 + 15 * V21 + 67 * V22 + 50 * V23 + 161 * V24 + 103 * V25 + 144 * V26 + 81 * V27 + 126 * V28 + 240 * V29 + 124 * v30 + 194 * v31 + 92 * v32 + 108 * v33 + 111 * v34 + 174 * v35 + 48 * v36 == 359512 ..... , 244 * v5 + 196 * v6 + 30 * v7 + 100 * v8 + 168 * v9 + 7 * v10 + 249 * v11 + 84 * v12 + 252 * v13 + 171 * V14 + 210 * V15 + 206 * V16 + 108 * V17 + 153 * V18 + 67 * V19 + 189 * V20 + 141 * V21 + 239 * V22 + 177 * V23 + 10 * V24 + 15 * V25 + 164 * v26 + 142 * v27 + 97 * v28 + 27 * v29 + 173 * v30 + 146 * v31 + 133 * v33 + 105 * v34 + 75 * (v32 + v35 ) + 197 * v36 == 393331 ))s 。添加(185 * v5 + 196 * v6 + 135 * v7 + 218 * (v24 + v9 ) + 241 * v8 + 210 * v10 + 127 * v11 + 221 * v12 + 47 * v13 + 179 * v14 + 61 * v15 + 59 * v16 + 197 * v17 + 204 * v18 + 198 * v19 + 75 * v20 + 146 * v21 + 156 * v22 + 235 * v23 + 63 * v25 + 220 * v26 + 3 * v27 + 167 * v28 + 230 * v29 + 69 * v30 + 186 * v31 + 57 * v32 + 147 * v33 + 221 * v34 + 79 * v35 + 53 * v36 == 430295 )#sat表示计算出结果if s 。check () == sat : t = [] print “compute result:” m = s 。model () t 。追加(str (m [ v33 ])) t 。追加(str (m [ v34 ])) t 。追加(str (m [ v32 ])) t 。追加(str(m [ v31 ])) t 。追加(str (m [ v30 ])) t 。追加(str (m [ v29 ])) t 。追加(str (m [ v28 ])) t 。追加(str (m [ v27 ])) t 。追加(str (m [ v26 ])) t 。追加(str (m [ v25 ])) t 。追加(str (m [ v24 ])) t 。追加(str (m [ v23 ])) t 。追加(str (m [ v22 ])) t 。追加(str (m [ v21 ])) t 。追加(str (m [v20 ])) t 。追加(str (m [ v19 ])) t 。追加(str (m [ v18 ])) t 。追加(str (m [ v17 ])) t 。追加(str (m [ v16 ])) t 。追加(str (m [ v15 ])) t 。追加(str (m [ v14 ])) t 。追加(str (m [ v13 ])) t 。追加(str (m [ v12 ])) t 。追加(str (m [ v11 ])) t 。追加(str (m [ v10 ])) t 。追加(str (m [ v9 ])) t。追加(str (m [ v8 ])) t 。追加(str (m [ v7 ])) t 。追加(str (m [ v6 ])) t 。追加(str (m [ v5 ])) t 。追加(str (m [ v36 ])) t 。追加(str (m [v35 ])) t = map (int , t ) t = map (chr , t ) print “” 。加入(t )否则: 打印 “失败”

二重解密

这里直接用的大佬的脚本,将上面解密的数据进行异或计算,即可返回最终我们需要输入的内容

int main (int argc , char ** argv ) { unsigned char c [ 33 ] = “S0m3time_l1tt1e_c0de_1s_us3ful33” ; 无符号 字符 在[ 33 ] = { 0 }; unsigned int t1 = 0 ,t2 = 0 ,t3 = 0 ,t4 = 0 ; printf ((const char * )c ); printf (“ \ n ” ); 在[ 0 ] = c [ 0 ] ^ 0x18 ; 在[ 1 ] = c [ 1 ] ^ 0x9 ; 在[ 2 ] = c [ 2 ] ^ 0x3 ; 在[ 3 ] = c [ 3 ] ^ 0x6b ; 在[ 4 ] = c [ 4 ] ^ 0x1 ; 在[ 5 ] = c [ 5 ] ^ 0x5A ; 在[ 6 ] = c [ 6 ] ^ 0x32 ; 在[ 7 ] = c [ 7 ] ^ 0x57 ; 在[ 8 ] = c [ 8 ] ^ 0x30 ; 在[ 9 ] = c [ 9 ] ^ 0x5d ; 在[ 10 ] = c [ 10 ] ^ 0x40 ; 在[ 11 ] = c [ 11 ] ^ 0x46 ; 在[ 12 ] = c [ 12 ] ^ 0x2b ; 在[ 13 ] = c [ 13 ] ^ 0x46 ; 在[ 14 ] = c [ 14 ] ^ 0x56 ; 在[ 15 ] = c [ 15 ] ^ 0x3d ; 在[ 16 ] = c [ 16 ] ^ 0x02 ; 在[ 17 ] = c [ 17 ] ^ 0x43 ; 在[ 18 ] = c [ 18 ] ^ 0x17 ; 在[ 19 ] = c [ 19 ]; 在[ 20 ] = c [ 20 ] ^ 0x32 ; 在[ 在[21 ] = c [ 21 ] ^ 0x53 ; 在[ 22 ] = c [ 22 ] ^ 0x1F ; 在[ 23 ] = c [ 23 ] ^ 0x26 ; 在[ 24 ] = c [ 24 ] ^ 0x2a ; 在[ 25 ] = c [ 25 ] ^ 0x01 ; 在[ 26 ] = c [ 26 ]; 在[ 27 ] = c [ 27 ] ^ 0x10 ; 在[ 28 ] = c [ 28 ] ^ 0x10 ; 在[ 在[29 ] = c [ 29 ] ^ 0x1E ; 在[ 30 ] = c [ 30 ] ^ 0x40 ; 在[ 31 ] = c [ 31 ]; printf ((const char * )in ); 返回 0 ; }小结

【1】多元线性方程式可以通过python的z3-solver库快速计算

反思

最开始做这道题我是卡在了最后一步,我用鼠尾草并未求出结果。

主要原因是:我甚至未能清除的理解这个算法的本质,当时并未意识到这是个多元方程求解的计算,只想着怎么求出这个结果,结果在网上找到一个相似题的解决方法,用鼠尾草计算,但在这里却并未算出

结论:解决问题时,不要求对所有细节了如执掌,但是题的主干脉络,根本思路是我们需要探索的

参考

【1】[原创]第五题:的丛林秘密https://bbs.pediy.com/thread-252191.htm

【2】Python中的Z3 API https://nen9ma0.github.io/2018/03/14/z3py/

【3】IDC脚本 - IDC脚本语言官方教程 https://bbs.pediy.com/thread-219016.htm

【4线性】方程组矩阵解法https://www.shuxuele.com/algebra/systems-linear-equations-matrices.html


电脑