前言
疫情当下,都已经耍了好几个月了,都不知道干啥了,好不容易等到我四川宣布开学时间了,结果四川高校一点动静都没有,无聊中,那就写一下解题记录吧,有些题先前已经在我个人博客上发过了,就不再重复写了,贴个链接就行了!!!
题目:app3
此题wp已经在个人博客写过,不在详细说明,详情请看链接:https://www.52pojie.cn/thread-1082706-1-1.html
题目:easy-apk
此题wp已经在个人博客写过,不在详细说明,详情请看链接:https://www.cnblogs.com/aWxvdmVseXc0/p/11955006.html
题目:easy-java
此题wp已经在个人博客写过,不在详细说明,详情请看链接:https://www.cnblogs.com/aWxvdmVseXc0/p/12207697.html
题目:easy-jni
此题wp已经在个人博客写过,不在详细说明,详情请看链接:https://www.cnblogs.com/aWxvdmVseXc0/p/12198459.html
题目:easy-so
1、下载好题目,拖入夜神中,打开如下所示:
2、查壳,发现无壳,用jeb反编译后,找到关键字验证失败
所在类,发现调用了so层的CheckString
函数进行了验证,传进去的参数为我们在输入框中输入的字符串,如下图所示:
3、用IDA打开so文件(要提取x86文件夹下面那个so文件,两个arm文件夹下面的so文件用ida打开有问题),找到该静态函数,直接F5大法,静态分析该函数可知:首先将传入的字符串前16位与后16位互换,然后两两一组互换位置,最后将得到的字符串与字符串f72c5a36569418a20907b55be5bf95ad
比较返回比较结果!!!
4、写了一个python小脚本跑出flag,如下所示:
脚本代码:
string = 'f72c5a36569418a20907b55be5bf95ad'
strlist = list(string)
k = 0
for i in range(16):
ch = strlist[1 + k]
strlist[1 + k] = strlist[0 + k]
strlist[0 + k] = ch
k = k + 2
k = 0
for i in range(16):
ch = strlist[0 + k]
strlist[0 + k] = strlist[16 + k]
strlist[16 + k] = ch
k = k + 1
print(''.join(strlist))
运行截图:
题目:app1
此题wp已经在个人博客写过,不在详细说明,详情请看链接:https://www.cnblogs.com/aWxvdmVseXc0/p/11902184.html
题目:Ph0en1x-100
1、拖进夜神中安装运行,主界面只有一个输入框和一个按钮,随便输入信息,点击按钮后,弹出信息Failed!
,如下图所示:
2、查壳后无壳直接使用JEB反编译,查看MainActivity.java文件,发现要弹出信息Success
逻辑如下:首先在so层注册了两个静态函数--encrypt(String)
、getFlag()
函数,然后在java层有个函数getSecret(String)
,将so层函数getFlag
返回值经过getSecret
函数加密后与我们在输入框中输入的字符串经过encrypt
函数后在经过getSecret
函数加密比较,如果一致,则返回Success
,由于比较的两个字符串最外层都经过getSecret
函数加密,所有我们不需要在管getSecret
函数,直接让内部两个字符串一直一致即可得到flag!!!
3、使用IDA打开so文件,静态分析一下encrypt
函数,发现逻辑很简单,就是将传进来的字符串的每个字符的ASCII码减一;对于getFlag
函数,由于该函数没有输入只有输出,直接用frida Hook该函数得到返回值即可,如下图所示:
Frida代码:
import frida
import sys
jscode = """
Java.perform(function(){
Interceptor.attach(Module.findExportByName("libphcm.so","Java_com_ph0en1x_android_1crackme_MainActivity_getFlag"),{
onEnter: function(args) {
},
onLeave: function(retval){
var String_java = Java.use('java.lang.String');
var args_4 = Java.cast(retval, String_java);
send("getFlag()==>"+args_4);
}
});
});
"""
def printMessage(message,data):
if message['type'] == 'send':
print('[*] {0}'.format(message['payload']))
else:
print(message)
process = frida.get_remote_device().attach('com.ph0en1x.android_crackme')
script = process.create_script(jscode)
script.on('message',printMessage)
script.load()
sys.stdin.read()
4、得到以上信息后,使用python脚本跑出falg即可,如下所示:
python脚本:
Flag = 'ek`fz@q2^x/t^fn0mF^6/^rb`qanqntfg^E`hq|'
flaglist = list(Flag)
stringlist = []
for ch in flaglist:
stringlist.append(chr(ord(ch) + 1))
print(''.join(stringlist))
题目:RememberOther
PS:此题为脑洞题,披了一个安卓的皮而已!!!
1、下载好题目后,查壳发现无壳,直接拖进夜神中,要求输入用户名和注册码,随便输入,弹出信息无效用户名或注册码
,如下图所示:
2、用JEB反编译后,查看onCreate
方法,发现将我们输入的用户名和注册码作为参数调用checkSN
函数,并且当该函数返回false
时不弹出信息无效用户名或注册码
,接着去看checkSN
函数,该函数当用户名和注册码为空时返回false
,返回false后弹出了一串md5值,再看checkSN
函数其他逻辑,发现将输入的用户名经过md5加密后返回16进制字符串,然后取该字符串的奇数位拼接成一个新的字符串,再然后与我们输入的注册码进行比较,返回true
,最后也没有发现这个跟flag没什么关系,想起之前还有一串md5值,进行解密,解密得出YOU_KNOW_
,输入,提示flag错误。。。看了一下其他大佬的wp,才发现在后面加上ANDROID
就行了。。。。。因为压缩包里面有个word文件,里面写了不懂安卓。。。。。。。
题目:app2
1、将下载好的题目拖进夜神中,发现要求登陆,随便点击登陆后,如下所示:
2、将apk用jeb反编译后,首先看一下MainActivity这个入口文件,发现没什么,就是将我们输入的用户名和密码传入SecondActivity页面中,然后跳转到该页面,再来看一下SecondActivity,发现调用了so层函数doRawData
,使其返回值和字符串VEIzd/V2UPYNdn/bxH3Xig==
进行比较,如下所示:
3、用IDA查看一下该函数,发现至少将传入的字符串进行了AES-128-ECB
加密,并且发现密钥thisisatestkey==
,将开始我们发现的字符串VEIzd/V2UPYNdn/bxH3Xig==
进行解密,得到的字符串输入后发现不是flag,但是在FileDataActivity文件中发现另一串字符串9YuQ2dk8CSaCe7DTAmaqAA==
,对其解密,得到flag:Cas3_0f_A_CAK3
题目:黑客精神
1、将下载好的题目拖进夜神模拟器中,发现要求注册,随便点击注册后,弹出一个弹框,提示已注册
。
2、用JEB反编译后,跟进MainActivity文件,发现点击按钮后就一个弹出弹框,点击弹框确定后,跳转到RegActivity界面去,在该界面点击注册后,调用了so层函数saveSN
。
3、用IDA分析so文件发现在java层注册的native函数都是动态注册的,此时用Ctrl+S
找到.data
段进入,发现对应的函数n1
、n2
、n3
。
4、先分析一些n1
函数,F5
大法后,可以很轻易看出该函数作用是创建了一个文件/sdcard/reg.dat
,然后读取该文件,与字符串EoPAoY62@ElRD
进行比较;再来看一下n2
函数,该函数对应这java层的saveSN
函数,首先前面做了一大堆令人看不懂的操作,然后将我们输入的注册码的每个字符与另一个字符进行异或操作(该字符我们不知道,所以无法得到最终异或结果);再来看一下n3
函数,该函数首先调用了n1
函数,若结果为真,则将一大串字符串赋值给v4
,双击去看一些该字符串,使用A
健将其转为ascii后,发现提示输入即是flag,格式为xman{……}!
。
5、按照提示,我们将开始发现的字符串EoPAoY62@ElRD
(只发现这一个字符串,不输入这个输入啥)作为字符串进行输入,然后将文件/sdcard/reg.dat
拷贝出来,打开文件一看,发现flag!!!
题目:easy-dex
1、下载好题目后,拖进夜神中,发现直接是黑屏的,在JEB中反编译后,发现在AndroidManifest.xml
文件application
和activity
标签中存在android:hasCode="false"
和android:name="android.app.NativeActivity"
,说明这是个纯C++编写的,并且不含java代码,也就是Native Activity
。
2、既然是Native Activity
,直接找到so文件,拖进IDA中,寻找android_main
方法(这是Native Activity
的入口方法,关于Native Activity
的一些基础知识,我会在将一些我觉得写得比较好的博客链接附在文末),发现存在一个'write'方法,结合包名'findmydex',大胆推测一波此处就是将dex写入某个文件中,那么直接开启动态调试,在关键地方下好断点后,终于运行到了write
函数处,结合传进去的参数,直接dump下来整个内存,用010打开一看,全都是'00000.....',。。。。。卒!!!!。。。。。。。
3、好吧,开玩笑的!!!动态调试dump下来的内存有问题,一看就不是dex文件,开始还以为调试的时候哪里出来问题,导致dump出来的有问题,然后接着动态了一整个下午,发现好像dump下来的加密后的dex,去看了一下大佬们的wp,发现都是dump下加密后的dex,然后直接解密,好吧,再来看android_main
函数,发现一开始就通过_aeabi_memcpy
函数将加密后的dex
文件加载进来了,我们可以轻松看到加密后的dex文件首地址为0x7004
(ida使用F5后,要使用那一块内存空间地址直接是以&unk_地址命名的,所以首地址可以轻松看出来是0x7004
),大小为0x3ca10
,那么直接在静态下执行dump脚本即可,至于解密,把想应的c语言转换为python即可,至于最后为啥要进行一次zip的解压操作,是因为在android_main
函数中解密完成后调用了uncompress
函数进行了解压缩(更偷懒的可以直接把F5后的c代码复制下来,替换一下就行了)。
IDA dump脚本:
import idaapi
addr = 0x7004
size = 0x3ca10
with open('dump','wb') as f:
f.write(get_bytes(addr,size))
print('[+] dump end')
python解密dex脚本(我电脑上运行环境为3.6):
import zlib
with open('dump','rb') as f:
data1 = f.read()
data = list(data1)
count = 0
while True:
if count <= 0x59:
count_tmp = (int)(count / 10)
if count % 10 == 9:
size = 0x3ca10
size_tmp = (int)(size / 10)
xor = (count_tmp + 1) * size_tmp
if (size_tmp * count_tmp) < xor:
index = size_tmp * count_tmp
while size_tmp:
data[index] = data[index] ^ count
index = index + 1
size_tmp = size_tmp - 1
if count == 89:
while xor < size:
data[xor] = data[xor] ^ 0x59
xor = xor + 1
else:
break
count = count + 1
filebytes = bytes(data)
with open('easy-dex.dex','wb') as f1:
f1.write(zlib.decompress(filebytes))
print('[+] decrypt end')
4、将解密后的dex文件拖进jeb中反编译,首先看一下MainActivity.java
文件的onCreate
函数,发现有一个按钮监听事件,触发后调用了a.java
里面的onClick
函数,那么去看一下onclick
函数,发现调用了MainActivity
里面的a
函数,并且传入了两个字符串参数,第一个字符串是输入框中的值,第二个参数是string.xml
资源文件中的字符串,然后与一个字节数组进行比较,结合题目反编译后的public.xml
和string.xml
文件,该字符串为I have a male fish and a female fish.
,并且资源ID为two_fish
,好吧,都已经是明示了,这里是TwoFish
加密(关于TwoFish加密我会将一下我觉得写得还行的博客链接贴在最后面),并且该字符串就是密钥,与之比较字节数组就是加密后的结果。那么首先将字节数组转为字符串再说吧,一看字符串里面还有负数,那就直接与一下0xff咯(关于为啥要与0xff,是因为数字在java中是以补码形式表示的,与0xff相当于将一个有符号数转为了无符号数,看来我确实没有写博客的天赋,感觉说得不明不白的,老规矩,就把我觉得写得可以的博客链接贴在文末),然后与完后,直接转为ascii拼接成字符串,发现有些根本无法显示出具体字符来。。。。。又卡了,突然发现里面有个/
符号,一下就想到了base64
,尝试一下base4解码,结果解出来又是啥都不是。。。。。好气哦这个题。。。。。那就在来一个base64加密吧,得到字符串iE3y2hEF1izgbVUfGKWQrUCtgFQFop7iEkbmRwWdwsZ1HdQGcPxRVAkWzV/eDC9N
(好吧,我承认我有赌的成分。。。。),随便找了个在线解密的网站,解密即得到flag。
python获取twofish加密结果脚本(运行环境同上):
import base64
i = [-120, 77, -14, -38, 17, 5, -42, 44, -32, 109, 85, 31, 24, -91, -112, -83, 64, -83, -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]
data = []
for k in i:
data.append(k&0xff)
print(base64.b64encode(bytes(data)))
题目:我是谁
55555555555,我太菜了,这道题还没有做出来,感觉对不起党,对不起人民。。。。。。。。。。。。。。。。。等做出来在补上!!!
一些总结
1、以往反编译后的第一步是看MainActivity
文件,很少看AnroidMainifes.xml
,结果在很多地方吃了大亏,比如easy-dex
这个题,android_main
这个函数还是撞进去的,后来看见那两个标签觉得没见过,才去百度了一下,才知道是Native Activity
,感觉这个文件看似不起眼,结果能少走一些不必要的地方!!!
2、思想上的牛角尖比技术上的牛角尖更难受,不然也不会去傻乎乎动态调试了一下午了!!!
一些博客链接
关于Native Activity的:
https://blog.csdn.net/qq_19683651/article/details/82623717
https://blog.csdn.net/qq_21071977/article/details/77878252
关于TwoFish加密的:
https://blog.csdn.net/l540538550/article/details/5642435