-
-
攻防世界Mobile通关记录_app1app2app3_wp
-
发表于: 2022-10-13 11:49 6269
-
app1
题目信息
首先改app的后缀为 zip。然后解压缩:
我们通过安卓逆向工具 Android逆向助手 v2.2
将 classes.dex转成 jar
然后 就 自动到这个页面了。
即反编译后的页面。
分析
MainActivty是我们开始要分析的入口。
然后我们去分析 找到 解题的关键函数
所以
str1 是我们输入的字符串
str2 是 "X<cP[?PHNB<P?aj"
i=15
我们看关键函数。
如果 str1[j] 等于 str2[j]^15 的话 就能 成功开启大闯关门了
解题
写python 脚本:
1 2 3 4 5 6 7 8 | i = 15 str1 = "" str2 = "X<cP[?PHNB<P?aj" for j in range ( len (str2)): str1 + = chr ( ord (str2[j])^i) print str1 #W3l_T0_GAM3_0ne |
运行脚本拿到正确答案即W3l_T0_GAM3_0ne
app2
题目信息
下载附件将其运行
点击登陆跳转到下面的界面
分析
将apk拖入jeb中分析,首先看MainActivity
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 | package com.tencent.testvuln; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences$Editor; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View$OnClickListener; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import com.tencent.testvuln.c.SignatureTool; @SuppressLint (value = { "ShowToast" }) public class MainActivity extends Activity implements View$OnClickListener { private Button lojin; private Handler b; private EditText mEd_Username; private EditText Ed_Password; public MainActivity() { super (); this.b = null; } public void onClick(View arg6) { switch(arg6.getId()) { case 2131165187 : { if (this.mEd_Username.getText().length() ! = 0 && this.Ed_Password.getText().length() ! = 0 ) { String username = this.mEd_Username.getText().toString(); String password = this.Ed_Password.getText().toString(); Log.e( "test" , username + " test2 = " + password); Intent v2 = new Intent(((Context)this), SecondActivity. class ); / / 将输入的username和password传递给了SecondActivity界面 v2.putExtra( "ili" , username); v2.putExtra( "lil" , password); this.startActivity(v2); return ; } Toast.makeText(((Context)this), "不能为空" , 1 ).show(); break ; } } } protected void onCreate(Bundle arg5) { super .onCreate(arg5); this.setContentView( 0x7F030000 ); this.lojin = this.findViewById( 0x7F070003 ); this.lojin.setOnClickListener(((View$OnClickListener)this)); this.mEd_Username = this.findViewById( 0x7F070001 ); this.Ed_Password = this.findViewById( 0x7F070002 ); SharedPreferences$Editor v0 = this.getSharedPreferences( "test" , 0 ).edit(); v0.putLong( "ili" , System.currentTimeMillis()); v0.commit(); Log.d( "hashcode" , SignatureTool.getSignature(((Context)this)) + ""); } public boolean onCreateOptionsMenu(Menu arg3) { this.getMenuInflater().inflate( 0x7F060000 , arg3); return 1 ; } public boolean onOptionsItemSelected(MenuItem arg3) { boolean v0 = arg3.getItemId() = = 0x7F070004 ? true : super .onOptionsItemSelected(arg3); return v0; } } |
首先判断用户和密码的两个编辑框是否为空,如果都不为空则跳转到SecondActivity,并将用户及密码给传递过去。
然后去看下SecondActivity
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 | package com.tencent.testvuln; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences$Editor; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; import com.tencent.testvuln.c.Encryto; public class SecondActivity extends a { class com.tencent.testvuln.SecondActivity$ 1 extends BroadcastReceiver { com.tencent.testvuln.SecondActivity$ 1 (SecondActivity arg1) { this.a = arg1; super (); } public void onReceive(Context arg3, Intent arg4) { Toast.makeText(arg3, "myReceiver receive" , 0 ).show(); arg3.getPackageName().equals(arg4.getAction()); } } private BroadcastReceiver c; public SecondActivity() { super (); this.c = new com.tencent.testvuln.SecondActivity$ 1 (this); } protected void onCreate(Bundle arg6) { super .onCreate(arg6); this.setContentView( 0x7F030001 ); Intent v0 = this.getIntent(); / / 获取到传入的intent String username = v0.getStringExtra( "ili" ); String password = v0.getStringExtra( "lil" ); if (Encryto.doRawData(this, username + password).equals( "VEIzd/V2UPYNdn/bxH3Xig==" )) { / / 调用类Encryto中的doRawData方法,参数为usename + password,如果返回值和VEIzd / V2UPYNdn / bxH3Xig = = 相等则进入 if v0.setAction( "android.test.action.MoniterInstallService" ); v0.setClass(((Context)this), MoniterInstallService. class ); v0.putExtra( "company" , "tencent" ); v0.putExtra( "name" , "hacker" ); v0.putExtra( "age" , 18 ); this.startActivity(v0); this.startService(v0); } SharedPreferences$Editor v0_1 = this.getSharedPreferences( "test" , 0 ).edit(); v0_1.putString( "ilil" , username); v0_1.putString( "lili" , password); v0_1.commit(); } public boolean onCreateOptionsMenu(Menu arg3) { this.getMenuInflater().inflate( 0x7F060000 , arg3); return 1 ; } public boolean onOptionsItemSelected(MenuItem arg3) { boolean v0 = arg3.getItemId() = = 0x7F070004 ? true : super .onOptionsItemSelected(arg3); return v0; } } |
首先它接收了传递过来的用户和密码。调用类Encryto中的doRawData方法,参数为usename+password,如果返回值和VEIzd/V2UPYNdn/bxH3Xig== 相等则进入if中。待会再看if中的代码。我们首先定位到类Encryto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package com.tencent.testvuln.c; public class Encryto { static { System.loadLibrary( "JNIEncrypt" ); / / 加载so库 } public Encryto() { super (); } public native String HelloLoad() { } public static native int checkSignature( Object arg0) { } public static native String decode( Object arg0, String arg1) { } public static native String doRawData( Object arg0, String arg1) { / / doRawData方法 } public static native String encode( Object arg0, String arg1) { } } |
所以我们将so拖入ida,发现它将user+pass的字符串给进行了AES加密,其中key为"thisisatestkey==",AES加密方式为ECB,数据块为128位,加密后的结果作为返回值。
所以要输入的用户名和密码就是将"VEIzd/V2UPYNdn/bxH3Xig=="给进行对应的aes解密后的值,
在线解密即可
在线AES加密解密、AES在线加密解密、AES encryption and decryption--查错网 (chacuo.net)
所以username+password就是"aimagetencent"
这里其实比较奇怪。在逻辑上只要用户名和密码连在一起是aimagetencent就是正确的,然后进入if中。
1 2 3 4 5 6 7 8 9 10 | if (Encryto.doRawData(this, username + password).equals( "VEIzd/V2UPYNdn/bxH3Xig==" )) { / / 调用类Encryto中的doRawData方法,参数为usename + password,如果返回值和VEIzd / V2UPYNdn / bxH3Xig = = 相等则进入 if v0.setAction( "android.test.action.MoniterInstallService" ); v0.setClass(((Context)this), MoniterInstallService. class ); v0.putExtra( "company" , "tencent" ); v0.putExtra( "name" , "hacker" ); v0.putExtra( "age" , 18 ); this.startActivity(v0); this.startService(v0); } |
但感觉逻辑上怎么都和类FileDataActivity连串不起来。但看别人的分析这题的flag就是在类FileDataActivity中。
解题
这题中还有一个类FileDataActivity,在创建这个Activity时,将TextView c给设置为Encryto.decode(this, "9YuQ2dk8CSaCe7DTAmaqAA==")返回值
然后在ida中看下decode方法,即将9YuQ2dk8CSaCe7DTAmaqAA==给进行aes解密,key同样为 thisisatestkey==。
在线解密得到
这里就是答案
1 | Cas3_0f_A_CAK3 |
这里其实可以对aes加解密的代码实现。
这里有2个动态分析的文章后续再看看。
(32条消息) Mobile App2 攻防世界 实战_zapata_ok的博客-CSDN博客
app3
题目信息
下载附件发现是个app3.ab
.ab后缀文件为android应用的备份文件,需要用安卓备份解析abe.jar工具将数据给提取出来。
1 2 | abe.ja项目地址 https: / / github.com / nelenkov / android - backup - extractor / releases |
备份文件有两种,一种是未加密的,一种是加密的
未加密的ab文件有着0x18字节的文件头,并包含有none
加密的ab文件的文件头比较复杂了,暂时知道有这个东西就可以了。
分析
将这个app3.ab放入16进制中,发现它没有进行加密。
所以使用安卓备份解析abe.jar工具对它进行数据提取
1 2 3 | >java - jar abe.jar unpack app3.ab app3.tar 0 % 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 % 9097216 bytes written to app3.tar. |
然后将得到的app3.tar压缩包进行解压
1 2 3 4 5 6 7 | └─app3 └─apps └─com.example.yaphetshan.tencentwelcome ├─a - base.apk └─db - Demo.db - _manifest - Encryto.db |
发现base.apk
将其放入到jeb中查看MainActivity入口
首先看下onCreate方法
创建个SharedPreferences$Editor对象,并向里面存入了3个值
1 2 3 | v0.putString( "Is_Encroty" , "1" ); v0.putString( "Encryto" , "SqlCipher" ); v0.putString( "ver_sion" , "3_4_0" ); |
然后调用了this.a()方法
首先分析下MainActivity入口类中的a函数实现了什么
首先加载Demo.db数据库
创建数据结构ContentValues
将name赋值为"Stranger" 将password赋值为"123456"。得到数据库有这两个字段名
然后v1是com.example.yaphetshan.tencentwelcome.a类的(构造方法)实例化对象。
看下com.example.yaphetshan.tencentwelcome.a类吧
然后v2被赋值为了v1对象中的a(arg4,arg5)方法的返回值。
即"Stranger" 的前4个字符加上"123456"前四个字符。
即v2此时为"Stra1234"
然后v1.b(v2, v0.getAsString("password"))就是
v1.b("Stra1234", "123456"))对应于com.example.yaphetshan.tencentwelcome.a类中的String b(String arg2, String arg3)方法。
1 2 3 4 | public String b(String arg2, String arg3) { new b(); return b.a(arg2); } |
它将类b进行实例化,并调用了类b中的a方法,参数为"Stra1234"。
去看下b类是怎么实现,将参数进行md5加密后又将其进行base16加密
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 | public class b { public b() { super (); } public static final String a(String arg9) { String v0_2; int i = 0 ; char[] v2 = new char[]{ '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' }; try { byte[] arg = arg9.getBytes(); MessageDigest v3 = MessageDigest.getInstance( "MD5" ); v3.update(arg); byte[] md5_out = v3.digest(); int len = md5_out.length; char[] v5 = new char[ len * 2 ]; int j = 0 ; while (i < len ) { int v6 = md5_out[i]; int v7 = j + 1 ; v5[j] = v2[v6 >>> 4 & 15 ]; j = v7 + 1 ; v5[v7] = v2[v6 & 15 ]; + + i; } v0_2 = new String(v5); } catch(Exception v0_1) { v0_2 = null; } |
然后返回的结果前面拼接上v2,作为参数,调用v1对象的a(含一个参数的)方法
1 | this.b.getWritableDatabase(v1.a(v2 + ret_str).substring( 0 , 7 )); |
而v1对象的a(含一个参数的)方法又进行调用了b类中的b方法,参数为前面返回的结果前面拼接上v2,然后面在拼接上"yaphetshan"
1 2 3 4 | public String a(String arg3) { new b(); return b.b(arg3 + this.a); } |
所以现在去看下b类中的b方法是怎么实现的。
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 | public static final String b(String arg9) { String v0_2; int i = 0 ; char[] v2 = new char[]{ '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' }; try { byte[] arg = arg9.getBytes(); MessageDigest v3 = MessageDigest.getInstance( "SHA-1" ); v3.update(arg); byte[] sha1_out = v3.digest(); int v4 = sha1_out.length; char[] v5 = new char[v4 * 2 ]; int j = 0 ; while (i < v4) { int v6 = sha1_out[i]; int v7 = j + 1 ; v5[j] = v2[v6 >>> 4 & 15 ]; j = v7 + 1 ; v5[v7] = v2[v6 & 15 ]; + + i; } v0_2 = new String(v5); } catch(Exception v0_1) { v0_2 = null; } return v0_2; } |
发现是将参数先用sha1进行加密然后再用base16加密的。然后将其返回
1 2 | this.a = this.b.getWritableDatabase(ret_str.substring( 0 , 7 )); this.a.insert( "TencentMicrMsg" , null, v0); |
并取前7个字符,作为数据库存储的密码。
解题
程序顺序执行中会会到这步的,如果通关动调打断点的方式是可以成功查出这个值的。
如果不动调的话,就可以写脚本对这些函数进行模拟运行将它给输出出来。想了下,直接写java吧。因为可以直接复制jeb反编译的代码。得到pass为ae56f99。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package com.example.app3_20221012; public class Main { public static void main(String[] args) { String arg = "Stra1234" ; String step1 = b.a(arg); System.out.println(step1); String step2 = arg + step1 + "yaphetshan" ; System.out.println(step2); String step3 = b.b(step2); System.out.println(step3); String pass = step3.substring( 0 , 7 ); System.out.println( pass ); } } |
这里其实也可用python实现,暂时不管。另外关于动调的方式回头再写一篇,这里就不写了。哦对,使用在smali代码中插log也是可以将其输出的。
拿到密码后继续向下看
1 2 | this.a = this.b.getWritableDatabase(v1.a(v2 + v1.b(v2, v0.getAsString( "password" ))).substring( 0 , 7 )); this.a.insert( "TencentMicrMsg" , null, v0); / / 向数据表TencentMicrMsg表插入username和password |
我们看下a类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package com.example.yaphetshan.tencentwelcome; import android.content.Context; import net.sqlcipher.database.SQLiteDatabase$CursorFactory; import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteOpenHelper; public class a extends SQLiteOpenHelper { private int a; public a(Context arg2, String arg3, CursorFactory arg4, int arg5) { super (arg2, arg3, arg4, arg5); this.a = 0 ; } public void onCreate(SQLiteDatabase arg2) { arg2.execSQL( "create table TencentMicrMsg(name text,password integer,F_l_a_g text)" ); } public void onUpgrade(SQLiteDatabase arg1, int arg2, int arg3) { } } |
所以flag应该在TencentMicrMsg表中,为第三个字段。
这里我们解压app3.tar后得到两个db文件,分别去查看下即可获得flag。
看网上都是使用DB Browser for SQLCipher打开然后输入密码就可以的。
我也用这个吧
下载地址
Downloads - DB Browser for SQLite (sqlitebrowser.org)
成功看到flag
VGN0ZntIM2xsMF9Eb19ZMHVfTG92M19UZW5jM250IX0=
对其进行base64解密即可。
Tctf{H3ll0_Do_Y0u_Lov3_Tenc3nt!}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!