记录一下在Bugku做的一些Moblie题。
1. signin(Java hook)
运行一下程序。
加载进jeb分析:
那如何获取到flag呢?可以用hook。hook一个方法需要知道:
- 方法的包名和类名:re.sdnisc2018.sdnisc_apk1.MainActivity
- 方法名:getFlag
- 方法的参数类型:无
另外还要知道程序的包名:re.sdnisc2018.sdnisc_apk1
Logcat返回getFlag的返回值:
随便找个可以逆序文字的网站,再拿去Base64解码,得到flag:
1 2 3 4 5
| #逆序后 ZmxhZ3tIZXIzX2k1X3kwdXJfZjFhZ18zOWZiY199
#Base64解码后 flag{Her3_i5_y0ur_f1ag_39fbc_}
|
2. mobile1(算法分析)
运行程序。
载入jeb分析Java伪代码:
1 2 3 4 5
| #Tenshine的MD5加密 b9c77224ff234f27ac6badf83b855c76
#取MD5的奇数位 flag{bc72f242a6af3857}
|
3. mobile2(XML文件)
同APK逆向-2,mobile2下载下来是一个ZIP文件,一样的。
4. Timer(算法分析&Smali文件修改)
拿去AK看了一下,没有允许动态调试。一般CTF的题如果没有动态调试,说明这道题不是考动态调试so文件的,而且这道题没有输入,断也不知道在哪里断下来,断下来干嘛。那如果so文件拿去IDA看,没有Log语句的话也挺难静态分析的。所以这道题很有可能是直接修改smali文件。
先jeb查看Java源码,因为重点不是so文件,所以可以暂时忽略它了:
因为最后打印flag是将k作为实参传入native方法的,而k是用is2方法求出来的,所以将is2方法运行一下,求出k的值。
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
| public class demo{ public static boolean is2(int arg4) { boolean v1 = true; if(arg4 > 3) { if(arg4 % 2 != 0 && arg4 % 3 != 0) { int v0 = 5; while(true) { if(v0 * v0 <= arg4) { if(arg4 % v0 != 0 && arg4 % (v0 + 2) != 0) { v0 += 6; continue; } return false; } else { return v1; } } } v1 = false; } else if(arg4 <= 1) { v1 = false; } return v1; } public static void main(String args[]){ int time=200000; int k=0; while(time>0){ if(is2(time)){ k+=100; }else{ k--; } time--; } System.out.println(k); } }
|
在smali文件修改传入native方法的k的值为1616384,还要将输出flag的条件语句修改为小于0。
保存,编译,安装,运行。
1 2
| alictf{Y0vAr3TimerMa3te7} flag{Y0vAr3TimerMa3te7}
|
5. First_Mobile(算法分析)
在模拟器上运行不了,可能是架构的问题,但这题不需要在页面上显示什么,所以运行不了也没事。
载入jeb分析:
调整一下encode类:
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
| class encode { private static byte[] b;
static { encode.b = new byte[]{23, 22, 26, 26, 25, 25, 25, 26, 27, 28, 30, 30, 29, 30, 32, 32}; }
public encode() { super(); }
public static void check(String arg7) { int v6 = 16; byte[] v1 = arg7.getBytes(); byte[] v3 = new byte[v6]; int i,j; byte[] v5 = new byte[v6]; for(j=0; j<v1.length; j++) { for(i = 0; i < v6; ++i) { v3[i] = ((byte)((v1[j] + encode.b[i]) % 61)); v3[i] = ((byte)(v3[i] * 2 - i)); if (v3[i] == v1[j]) { v5[i] = v3[i];
} } } String str = new String (v5); System.out.println(str); } } public class AES_128 {
public static void main(String[] args) { encode enc = new encode(); String arg7 = "ABCDEFGHJIKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890{}_"; enc.check(arg7); } }
|
所以flag为:
6. easy_100(AES加密)
虽然在模拟器不能运行,但没root的手机也可以从电脑上安装app。
在AK中看到没有so文件,也不用动态调试。jeb分析,因为有混淆,所以看起来还挺费力。
将图片用Winhex打开,144=90h,所以v=”this_is_the_key.”。
密文与数组比较。现在理一下思路:key经过小端存储作为密钥,输入的文本串经AES_ECB模式和密钥加密后得到需要比较的数组。所以我们现在想逆向回去找文本串,需要将key转换成密钥,再利用ECB解密数组,得到flag。编写脚本:
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
| package v5le0n9; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec;
public class demo { public static void main(String[] args) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { StringBuilder v1 = new StringBuilder(); int v0_1; String v0_2; String arg4 = "this_is_the_key."; for(v0_1 = 0; v0_1 < arg4.length(); v0_1 += 2) { v1.append(arg4.charAt(v0_1 + 1)); v1.append(arg4.charAt(v0_1)); } v0_2 = v1.toString(); byte key[] = v0_2.getBytes(); SecretKeySpec a; Cipher b; a = new SecretKeySpec(key, "AES"); b = Cipher.getInstance("AES/ECB/PKCS5Padding"); byte flag[] = {21, -93, -68, -94, 86, 117, -19, -68, -92, 33, 50, 118, 16, 13, 1, -15, -13, 3, 4, 103, -18, 81, 30, 68, 54, -93, 44, -23, 93, 98, 5, 59}; b.init(Cipher.DECRYPT_MODE,a);
String AES_decode = new String(b.doFinal(flag),"utf-8"); System.out.println(AES_decode); } }
|
7. HelloSmali2(送分)
下载下来一个smali文件和一个java文件,将java文件运行一下就有答案。所以smali文件并没有什么用?
1 2
| javac XMan.java//生成XMan.class文件 java XMan//执行class文件
|
1 2
| eM_5m4Li_i4_Ea5y XMAN{eM_5m4Li_i4_Ea5y}
|
8. LoopAndLoop(算法分析)
顺着它的逻辑编写代码,循环几次后发现规律,最后得到flag。
9. SafeBox(算法分析)
这有两个Activity,两个Activity的算法有点差别,将两个算法都算一遍就知道谁是正确flag了。
而且题目描述说明flag内容只包含[a-zA-Z0-9],所以可以轻易得出正确flag。
10. baby unity3d(unity & Native hook)
unity3d手游逆向
Android逆向笔记-Unity3D逆向一般思路(静态分析)
根据bugku baby unity3d分析思路,在Magisk中启动Zygisk,使用Zygisk-Il2CppDumper将生成的ZIP包刷入Magisk后重启,启动游戏,得到dump.cs
文件。
CS文件其实是C#源文件,可以使用任何编辑器打开。dump.cs
中有上万行代码,需要在这些代码中寻找关键函数并不容易。可以根据Assembly-CSharp.dll
的index进行定位。
将libil2cpp.so
载入IDA,定位到相关虚拟地址。将IDA中的函数名修改为dump.cs
中的函数名以便理解。
AESEncrypt()
的password和iv,op_Equality()
的dword_69B7F0都很难在IDA中得到,可以通过Native hook将这些参数打印出来,点击程序中的Check按钮就会调用到这两个函数。
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
| function hook_flag(){ var soAddr = Module.findBaseAddress("libil2cpp.so"); console.log("soAddr => " + soAddr);
var AESEncryptAddr = soAddr.add(0x518b54); console.log("AESEncryptAddr => " + AESEncryptAddr);
Interceptor.attach(AESEncryptAddr, { onEnter:function(args){ console.log("AESEncrypt addr => ", args[0]); console.log("text => ", hexdump(args[1])); console.log("password => ", hexdump(args[2])); console.log("iv => ", hexdump(args[3])); },onLeave:function(retval){ console.log("AESEncrypt retval => ",hexdump(retval)); return retval; } })
var op_EqualityAddr = soAddr.add(0x7d644); console.log("op_EqualityAddr => " + op_EqualityAddr);
Interceptor.attach(op_EqualityAddr, { onEnter:function(args){ console.log("op_Equality addr => ", args[0]); console.log("AESEncrypt retval => ", hexdump(args[1])); console.log("dword_69B7F0 => ", hexdump(args[2])); },onLeave:function(retval){ console.log("op_Equality retval => ",retval); return retval; } }) } setImmediate(hook_flag)
|
可以得到password为91c775fa0f6a1cba,iv为58f3a445939aeb79。dword_69B7F0为w0ZyUZAHhn16/MRWie63lK+PuVpZObu/NpQ/E/ucplc=,应该是将密文再进行了一次Base64编码。
1
| N1CTF{h4ppy_W1TH_1l2cpp}
|
如果不清楚AESEncrypt()
中还进行了什么奇怪的算法,使用在线解密工具解密不出时,可以主动调用libil2cpp.so
中的AESDecrypt()
。但我貌似没这能耐啊不会弄。
11. easyeasy-200(模拟器检测)
check()
方法中调用了Native方法checkEmulator()
,看名字就知道是模拟器检测,还调用了checkPasswd()
,这个才是核心函数。
将So文件载入IDA,查看.init_array
:
查看JNI_OnLoad()
:
相关检测可以看Tide安全团队——动态防护技术,Android模拟器检测及对抗方法,Android逆向之旅—运行时修改内存中的Dalvik指令来改变代码逻辑,Android常见风控检测位置。
最后看Native层函数:
.init_array
中的secret是pass逆序后的Base64编码,拿去解码逆序回来即可。由于Base64字符表中没有“.”,猜测是占位符。解出没有前后缀的flag,加上前后缀提交。
1 2 3
| iwantashellbecauseidonthaveitttt
flag{iwantashellbecauseidonthaveitttt}
|
12. exaycrack-100(算法分析 & RC4)
进入So文件查看parseText()
函数逻辑:
1 2 3
| It_s_a_easyCrack_for_beginners
flag{It_s_a_easyCrack_for_beginners}
|
13. 汤姆的苹果
AsyncTask解析
aVar这个异步任务有两个处理器(Handler),并在onClick()
函数中完成赋值。
对于输入的字符串,作为b.b.a.b()
的参数传入:
再回到MainActivity,执行aVar.execute("99");
。但在执行这条语句前,异步任务会先执行doInBackground()
方法,其中调用了b.b.a.b.a()
方法,使用到了经过变换后的输入。
继而再执行onPostExecute()
,传递消息内容为999,把整个处理器c传过去,由处理器f728b发送。
对每个字符进行匹配,如果全都匹配成功,由处理器c发送内容99,否则发送内容97。当发送的消息内容为99时,显示“Right”。
点击按钮后,将b.b.a.b
类的静态成员变量hook出来。
由于输入字符串长度为4,且f729a的可见字符长度也是4。可以根据cArr推测输入字符串长度为24。
使用3个反编译器,代码都不同,而且改来改去都不对,真是服了。
14. LittleRotatorGame
全部逻辑都在So层中。进行了OLLVM保护。