攻防世界刷题:Mobile, Easy部分。
easy-so
so层逆向,简单的字符串变换,先交换前后16个字符,然后每两个字符交换位置。
顺便体验了一下so动态调试。
https://zhuanlan.zhihu.com/p/58468014
https://blog.csdn.net/hbhgyu/article/details/81321923
app1
1 | this.btn.setOnClickListener(new View.OnClickListener() { |
app2
有点儿莫名其妙的一个题。
在com.tencent.testvuln.SecondActivity.onCreate
中,检查了username和password,具体是通过native的doRawData()
函数基于AES进行校验。
1 | // com.tencent.testvuln.a |
跟到native层的doRawData()
,发现直接给出的AES的加密模式和加密密钥。去http://tool.chacuo.net/cryptaes/解出来得到`username=tencent password=aimage`。
1 | jstring __fastcall doRawData(JNIEnv *env, jobject thiz, int jclass, int str) |
但是这个信息没什么用,之后的代码去开启了一个service监听指定路径下的文件变化,但是监听的回调函数也没进行什么可疑操作,不知道在哪儿去操作或者展示了flag。
扫一下其他class的代码,发现了在FileDataActivity
中进行了AES解密操作,解出来得到flag = Cas3_0f_A_CAK3
。
1 | public class FileDataActivity extends a { |
这个activity貌似没有在其他class的代码中被调用过,不知道在哪儿暗示了这个是flag,反正就挺莫名其妙的。
app3
下载得到一个.ab
,它是Android的备份文件,用工具打开得到一个apk和两个db文件。
这个apk在我的设备上运行不了,提示缺少一些so文件,在这个仓库里面可以找到多数的so文件,然后补上icu文件依然运行不了,遂作罢。
于是静态分析代码,关键在这个方法里面。主要是根据name=Stranger password=123456
生成了数据库的加密密钥,且该数据库通过sqlcipher
完成加解密操作。
跟着代码逻辑,可以用在线工具得到数据库密码为ae56f99
,然后使用工具sqlcipher-shell解密两个数据库文件,从中可以翻到flag的base64字符串。
也可以直接用sqlitebrowser打开,选择Sqlcipher3默认。
1 | private void a() { |
easy-apk
1 | #换表base64解密脚本,https://www.programminghunter.com/article/2612605879/ |
easyjava
检查代码发现是一个类似于转轮机的加密程序,把反编译代码复制下来,添加爆破的代码片段即可。
需要注意的是,我的jeb对一些细节处理不够好,比如下面这个转动密码表的rot1
函数。
在汇编代码中,先是把list中的头部元素取出,然后remove(0),接着把取出的头部元素加入的尾部。
但是在反编译的得到的java代码中,却先remove(0)移除了头部元素,然后再把头部元素(相当于第二个元素)复制到末尾,造成逻辑错误,需要交换顺序。
同样的情况还发生在com.a.easyjava.a.a()
中。
1 | import java.util.ArrayList; |
easyjni
1 | import base64 |
RememberOther
无趣的脑洞题:
apk里面藏了个YOU_KNOW_
,然后文档里提示本题只是强行沾边了Android,说明出题人不懂Android,而做题人是懂Android的,所以flag是YOU_KNOW_ANDROID
。
所以出题人看来真的不懂Android。
easy-dex
非常有营养的一道题,解题的思路由一系列的知识点构成,并考察了基础的Android逆向知识。
解题过程参考自neilwu@52pojie。
首先打开apk,发现没有任何代码。
检查manifest文件,发现属性hasCode=false
,这里需要的知识点为NativeActivity开发,即使用纯C来完成App的开发。
有了这个知识,用ida打开so文件,定位到android_main
函数,里面就是关于这个App的主要逻辑代码。
应用要求使用者在十秒之内晃动手机100次,然后解密释放出一个dex文件到指定目录,加载之后来到flag校验的步骤。
具体的流程还是需要我们自己去一点点分析还原。
文件路径
dex释放过程中涉及到的两个路径名分别保存在filename
和name
中,并被抑或加密保护,还原的时候还要每4个字节调整顺序。dex文件解密
dex文件也通过抑或加密保护,以字节流的方式写在.data中,起始位置是 0x7004,长度是 0x3ca10。
整个dex文件流一共分为10段来加解密,解密代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import os
import zlib
fr = open("xxx/odex", "rb")
dd = list(fr.read())
fr.close()
L = 0x3ca10
segLen = L // 10
for cnt in range(9,99,10):
rnd = cnt // 10
for ind in range(segLen * rnd, segLen*rnd + segLen):
dd[ind] ^= cnt
if(cnt == 89):
for ind in range(segLen*rnd+segLen,L):
dd[ind] ^= 89
ddd=b''.join(map(lambda x:int.to_bytes(x,1,'little'), dd))
dddd = zlib.decompress(ddd)
fw = open("xxx/class.dex", "wb")
fw.write(dddd)
fw.close()
dex执行
程序记录到100次摇晃之后,开始释放dex文件,加载并运行。
在loadDex()
和runDex()
函数中会有从native反射java层执行方法的相关代码。TwoFish
dex的代码包含了一个flag校验程序,为encode(flag, key)==cipher
的模式,加密代码很长很烧脑。浏览一下App的字符串信息,有如下发现:
这里需要新的知识:TwoFish加密算法,与AES的功能相似(因为也是当初AES海选的参赛选手之一)。
我们并不需要自己去实现一遍,找个在线工具就好。
不过还需要确定key和cipher。
分析代码,可知cipher=new byte[]{-120, 77, -14, -38, 17, 5, -42, 44, (byte)224, 109, 85, 31, 24, -91, (byte)144, -83, 0x40, -83, (byte)128, 84, 5, -94, -98, -30, 18, 70, -26, 71, 5, -99, -62, -58, 117, 29, -44, 6, 112, -4, 81, 84, 9, 22, -51, 95, -34, 12, 47, 77}
。
key是0x7F060025
对应的String,简单的确定方法如下所示,可知key=("I have a male fish and a female fish.").getBytes()
。
黑客精神
简单扫一下,看起来是注册码校验模式的App,关键的几个操作、检查注册码的函数埋在native层。
从JNI_OnLoad()函数里面可以找到与Java层的initSN() saveSN() work()
方法绑定的native层函数。
程序运行时,先要求填入注册码,经过抑或加密之后保存到/sdcard/reg.dat
。
在work()
函数中,先调用initSN()
检查是否注册,如果已经注册,就修改MainActivity.m=1
。initSN()
就是检查/sdcard/reg.dat
中的数据是不是EoPAoY62@ElRD
,如果是的话就设置MainActivity.m=1
。initSN
结束之后,就去检查MainActivity.m
判断是否注册,并返回相应的提示信息。
1 | int __fastcall work(int a1) |
1 | int __fastcall initSN(JNIEnv *env) |
返回的字符串提示输入的注册码就是flag,提交的时候包裹上xman{....}
就行了。
注册码加密为xor(SignCode, key)=cipher
模式,其中cipher=EoPAoY62@ElRD
。
经过分析,也可以得到key=W3_arE_whO_we_ARE
(或者说key=w_a
),进一步可以得到flag。
1 | int __fastcall saveSN(JNIEnv *env, int thiz, int ptr_sn) |
你是谁
首先,构造一个心形图案,约束条件如下:
1 | public boolean check() { |
其实把所有圈都点了也能通过,不过这个题只静态分析就行。
关注xyz.konso.testsrtp.background$4.onResult()
这个回调函数:
这里接入了科大讯飞的语音SDK,传入的results会被解析为文本信息然后传入getsna()
中。
1 | public void onResult(RecognizerResult results, boolean isLast) { |
在getsna()
中,逻辑比较简单,输入的flag参数去utf-8解码出来是我是傻逼
。
1 | public void getsna(String flag) { |
但是提交的flag形式是啥还是不知道。makeText
提示sorted flag=20667 25105 26159 36924
,那么original flag=25105 26159 20667 36924
,且需要保留空格。
最后这一步我是真没想到,看来我真是傻逼了。: (