-
-
[原创]攻防世界Mobile通关记录_easyapk_dex_java_jni_so_wp
-
发表于: 2022-10-18 17:21 6170
-
前言
业余时间写的,纯兴趣更新!
easy-apk
题目信息
下载安装并打开,随便输入字符串点击check按钮,弹框验证失败。
分析
将其拖入jeb中查看MainActvity
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 | package com.testjava.jack.pingan1; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View$OnClickListener; import android.view.View; import android.widget.Toast; public class MainActivity extends AppCompatActivity { public MainActivity() { super (); } protected void onCreate(Bundle arg3) { super .onCreate(arg3); this.setContentView( 0x7F04001B ); this.findViewById( 0x7F0B0076 ).setOnClickListener(new View$OnClickListener() { public void onClick(View arg8) { if (new Base64New().Base64Encode(MainActivity.this.findViewById( 0x7F0B0075 ).getText().toString().getBytes()).equals( "5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=" )) { Toast.makeText(MainActivity.this, "验证通过!" , 1 ).show(); } else { Toast.makeText(MainActivity.this, "验证失败!" , 1 ).show(); } } }); } } |
调用了Base64New()类中的Base64Encode(arg)方法,其中参数为我们的输入
1 | MainActivity.this.findViewById( 0x7F0B0075 ).getText().toString().getBytes() |
如果返回值和"5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs="相等的话则验证通过。
我们看下Base64New()类
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 | package com.testjava.jack.pingan1; public class Base64New { private static final char[] Base64ByteToStr = null; private static final int RANGE = 0xFF ; private static byte[] StrToBase64Byte; static { Base64New.Base64ByteToStr = new char[]{ 'v' , 'w' , 'x' , 'r' , 's' , 't' , 'u' , 'o' , 'p' , 'q' , '3' , '4' , '5' , '6' , '7' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' , 'y' , 'z' , '0' , '1' , '2' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'K' , 'L' , 'M' , 'N' , 'O' , 'Z' , 'a' , 'b' , 'c' , 'd' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , '8' , '9' , '+' , '/' }; Base64New.StrToBase64Byte = new byte[ 0x80 ]; } public Base64New() { super (); } public String Base64Encode(byte[] input ) { int v7 = 3 ; StringBuilder ret = new StringBuilder(); int i; for (i = 0 ; i < = input .length - 1 ; i + = 3 ) { byte[] v0 = new byte[ 4 ]; byte v4 = 0 ; int offset; for (offset = 0 ; offset < = 2 ; + + offset) { if (i + offset < = input .length - 1 ) { v0[offset] = ((byte)(( input [i + offset] & 0xFF ) >>> offset * 2 + 2 | v4)); v4 = ((byte)((( input [i + offset] & 0xFF ) << ( 2 - offset) * 2 + 2 & 0xFF ) >>> 2 )); } else { v0[offset] = v4; v4 = 0x40 ; } } v0[v7] = v4; for (offset = 0 ; offset < = v7; + + offset) { if (v0[offset] < = 0x3F ) { ret.append(Base64New.Base64ByteToStr[v0[offset]]); } else { ret.append( '=' ); } } } return ret.toString(); } } |
发现这个base64将加密的表给替换成了
1 | { 'v' , 'w' , 'x' , 'r' , 's' , 't' , 'u' , 'o' , 'p' , 'q' , '3' , '4' , '5' , '6' , '7' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' , 'y' , 'z' , '0' , '1' , '2' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'K' , 'L' , 'M' , 'N' , 'O' , 'Z' , 'a' , 'b' , 'c' , 'd' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , '8' , '9' , '+' , '/' } |
解题
python脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import base64 import string import binascii base64_ok = "5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=" outtab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" table = [ 'v' , 'w' , 'x' , 'r' , 's' , 't' , 'u' , 'o' , 'p' , 'q' , '3' , '4' , '5' , '6' , '7' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' , 'y' , 'z' , '0' , '1' , '2' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'K' , 'L' , 'M' , 'N' , 'O' , 'Z' , 'a' , 'b' , 'c' , 'd' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , '8' , '9' , '+' , '/' ] intab = "" for i in range ( len (table)): intab + = table[i] print (intab) teaout = (base64.b64decode(base64_ok.translate(base64_ok.maketrans(intab,outtab)))) print (teaout) #b'05397c42f9b6da593a3644162d36eb01' |
验证通过,flag为flag{05397c42f9b6da593a3644162d36eb01}
easy-dex
题目信息
下载附件,并安装
打开后只有一个空白界面颜色不断变化
分析
拖入jeb中查看反编译却只有一个类
apktool对它进行解包
1 2 3 4 5 6 7 8 9 10 11 | >apktool d easy - dex.apk I: Using Apktool 2.6 . 1 on easy - dex.apk I: Loading resource table... I: Decoding AndroidManifest.xml with resources... I: Loading resource table from file : C:\Users\yang\AppData\Local\apktool\framework\ 1.apk I: Regular manifest package... I: Decoding file - resources... I: Decoding values * / * XMLs... I: Copying assets and libs... I: Copying unknown files... I: Copying original files... |
但里面却没有dex代文件,奇奇怪怪。
查看logcat,发现一句可以日志,发现有使用去加载so库,
同时发现可以log输出
1 | Can you shake your phone 100 times in 10 seconds? |
难道要在10秒内摇晃100次手机就会显示出flag了吗。
打开后黑屏一会时间然后打印出 "Oh yeah~ You Got it~ 99 times to go~"
虚拟机无法显示,即使真机手速也跟不上。
所以这里将so库拖入ida中,搜索该字符串通过交叉引用去定位到android_main代码。
解题
看android_main代码,因为这一块从打印"Can you shake your phone 100 times in 10 seconds?"顺序执行到打印""Oh yeah~ You Got it~ 99 times to go~"",中间的代码就不用细看了。
往下面走将加密的dex数据的size等分为了10份。(不是等分,最后一份比前九份大6字节)
每当摇晃到第9 19 29...89时便对其中一份数据进行异或的解密处理。
其中
第i(0开始)中的数据与i*10+9进行异或 异或的结果存放到了v3
最后一份的数据与0x59进行异或 异或的结果存放到了v3中
继续往下看
当摇晃次数为100的时候再去计算花费时间的,如果时间大于10秒,就把&unk_7004中的数据给复制到v3中,即将加密的dex数据给复原。而如果小于10秒就对解密后的v3进行解压处理。
另外看一下这里的filename和name是怎么来的,在android_main函数刚开始的时候有一段代码
我们将它模拟运行,去得到下filename和name是什么
首先是filename,得到"/data/data/com.a.sample.findmydex/files/classes.dex"
1 2 3 4 5 6 7 8 9 10 11 12 | filename = [ 0xc6 , 0x8d , 0x88 , 0x9d , 0x88 , 0xc6 , 0x8d , 0x88 , 0x9d , 0x88 , 0xc6 , 0x8a , 0x86 , 0x84 , 0xc7 , 0x88 , 0xc7 , 0x9a , 0x88 , 0x84 , 0x99 , 0x85 , 0x8c , 0xc7 , 0x8f , 0x80 , 0x87 , 0x8d , 0x84 , 0x90 , 0x8d , 0x8c , 0x91 , 0xc6 , 0x8f , 0x80 , 0x85 , 0x8c , 0x9a , 0xc6 , 0x8a , 0x85 , 0x88 , 0x9a , 0x9a , 0x8c , 0x9a , 0xc7 , 0x8d , 0x8c , 0x91 , 0xe9 ] filename[ 0 ] = 0x2f #print(filename) filenameout = [] filenameout.append(filename[ 0 ]) for i in range ( 1 , len (filename)): filenameout.append(filename[i]^ 0xe9 ) print (filenameout) print ( len (filenameout)) for i in range ( len (filenameout)): print ( chr (filenameout[i]),end = "") #/data/data/com.a.sample.findmydex/files/classes.dex |
然后是name得到"/data/data/com.a.sample.findmydex/files/odex/"
1 2 3 4 5 6 7 8 9 10 11 12 | name = [ 0xc6 , 0x8d , 0x88 , 0x9d , 0x88 , 0xc6 , 0x8d , 0x88 , 0x9d , 0x88 , 0xc6 , 0x8a , 0x86 , 0x84 , 0xc7 , 0x88 , 0xc7 , 0x9a , 0x88 , 0x84 , 0x99 , 0x85 , 0x8c , 0xc7 , 0x8f , 0x80 , 0x87 , 0x8d , 0x84 , 0x90 , 0x8d , 0x8c , 0x91 , 0xc6 , 0x8f , 0x80 , 0x85 , 0x8c , 0x9a , 0xc6 , 0x86 , 0x8d , 0x8c , 0x91 , 0xc6 , 0xe9 ] name[ 0 ] = 0x2f #print(name) nameout = [] nameout.append(filename[ 0 ]) for i in range ( 1 , len (name)): nameout.append(name[i]^ 0xe9 ) print (nameout) print ( len (nameout)) for i in range ( len (nameout)): print ( chr (nameout[i]),end = "") #/data/data/com.a.sample.findmydex/files/odex/ |
所以第一步先得到解密后的dex文件
在ida将它给dump出来
1 2 3 4 5 6 7 | import idaapi start_address = 0x7004 data_length = 0x3ca10 data = idaapi.get_bytes(start_address, data_length) fp = open ( 'dump123' , 'wb' ) fp.write(data) fp.close() |
然后对其进行解密
脚本如下,写的有些潦草.
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 | #coding:utf8 import zlib with open ( "dump123" , "rb" ) as f1: #读取dump出的加密字符串 dump_data = list (f1.read()) #print(dump_data) dump_size = 0x3ca10 #处理每一个字节 print ( int (dump_size / 10 ) * 1 ) #24833 print ( int (dump_size / 10 ) * 2 ) #49666 print ( int (dump_size / 10 ) * 3 ) #74499 print ( int (dump_size / 10 ) * 4 ) #99332 print ( int (dump_size / 10 ) * 5 ) #124165 print ( int (dump_size / 10 ) * 6 ) #148998 print ( int (dump_size / 10 ) * 7 ) #173831 print ( int (dump_size / 10 ) * 8 ) #198664 print ( int (dump_size / 10 ) * 9 ) #223497 print ( int (dump_size / 10 ) * 10 ) #248330 for i in range (dump_size): if ( 0 < = i< int (dump_size / 10 ) * 1 ): dump_data[i]^ = 9 elif ( int (dump_size / 10 ) * 1 < = i< int (dump_size / 10 ) * 2 ): dump_data[i]^ = 19 elif ( int (dump_size / 10 ) * 2 < = i< int (dump_size / 10 ) * 3 ): dump_data[i]^ = 29 elif ( int (dump_size / 10 ) * 3 < = i< int (dump_size / 10 ) * 4 ): dump_data[i]^ = 39 elif ( int (dump_size / 10 ) * 4 < = i< int (dump_size / 10 ) * 5 ): dump_data[i]^ = 49 elif ( int (dump_size / 10 ) * 5 < = i< int (dump_size / 10 ) * 6 ): dump_data[i]^ = 59 elif ( int (dump_size / 10 ) * 6 < = i< int (dump_size / 10 ) * 7 ): dump_data[i]^ = 69 elif ( int (dump_size / 10 ) * 7 < = i< int (dump_size / 10 ) * 8 ): dump_data[i]^ = 79 elif ( int (dump_size / 10 ) * 8 < = i< int (dump_size / 10 ) * 9 ): dump_data[i]^ = 89 # elif (int(dump_size/10)*9<=i<int(dump_size/10)*10): # dump_data[i]^=99 else : dump_data[i]^ = 0x59 #print(dump_data) dump_decode = bytes(dump_data) #print(dump_decode) with open ( "dump_decode" , "wb" ) as f2: f2.write(zlib.decompress(dump_decode)) |
解密后的字节并不是文件格式不能导出后通过zip解压,需要进行zlib.decompress解压才得到dex的文件。把它拖入到jadx中,终于看到了安卓代码啊。
调用了类a,看下类a
第22行为关键位置
MainActivity.b函数的返回值如何和MainActivity.m相等的话就证明我们的输入的正确的。
MainActivity.m为
1 2 3 | m = [ - 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 ] |
Byte.Min_VALUE为-128
MainActivity.b函数的第一个参数是编辑框中获取的内容即我们的输入
MainActivity.b函数的第二个参数是MainActivity.getString(2131099683)字符串。
然后我们可将apk拖入jadx在resources.arsc中进行搜索2131099683
发现它为"two_fish",即id
然后再去strings.xml中查找two_fish字符串的内容为"I have a male fish and a female fish."
网上谷歌下发现是个Twofish对称加密算法。那就很显而易见了,我们的输入作为明文,加密算法是Twofish对称加密算法,它的key是"I have a male fish and a female fish.",密文是MainActivity.m对应的字符串。找个在线网站解密就好。
在线Twofish加密解密、Twofish在线加密解密、Twofish encryption and decryption--查错网 (chacuo.net)
因为看了下two_fish的密文输出为base64,所以先将MainActivity.m对应的字符串给改成base64格式。
exp如下
1 2 3 4 5 6 7 8 9 10 | #coding:utf8 import base64 m = [ - 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 ] en_data = [] for i in m: en_data.append(i& 0xFF ) print (base64.b64encode(bytes(en_data))) #b'iE3y2hEF1izgbVUfGKWQrUCtgFQFop7iEkbmRwWdwsZ1HdQGcPxRVAkWzV/eDC9N' |
然后在线解密即可,成功拿到flag.
1 | qwb{TH3y_Io<e_EACh_OTh3r_FOrEUER} |
这题如果不知道这个Twofish是个算法的话,估计但考静态分析是不够的的。有个思路把dex给本来的apk解包后的目录给合成一个apk,然后通过动调的方式去分析,嗯,突然觉得jeb动调也不错,回头更一下。
easyjava
题目信息
下载安装,随便输入字符串每点击check按钮,弹出"You are wrong! Bye~",然后程序退出。
将其拖入jeb中反编译
分析
在if中,调用了MainActivity类中的a方法,参数为我们的输入。如果返回值为true,就证明我们的输入是正确的。
看下a方法,发现仅调用了b函数
1 2 3 | static Boolean a(String arg1) { return MainActivity.b(arg1); } |
再看下b函数,首先判断输入字符串首尾是否为"flag{"及"}",重要部分在else中
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 | private static Boolean b(String input_str) { Boolean ret; int v0 = 0 ; if (!input_str.startsWith( "flag{" )) { ret = Boolean.valueOf(false); } else if (!input_str.endsWith( "}" )) { ret = Boolean.valueOf(false); } else { String input = input_str.substring( 5 , input_str.length() - 1 ); / / 取输入字符串flag{}大括号中间的部分 b v4 = new b(Integer.valueOf( 2 )); / / 实例化类b 为v4对象 a v5 = new a(Integer.valueOf( 3 )); / / 实例化类a 为v5对象 StringBuilder v3 = new StringBuilder(); / / 空字符串 v3 int i = 0 ; while (v0 < input .length()) { v3.append(MainActivity.a( input .charAt(v0) + "", v4, v5)); / / 调用MainActivity.a方法(带参的) / / 参数 1 为输入的每个字符 / / 参数 2 为对象v4 / / 参数 3 为对象v5 / / 返回值 添加到v3字符串中 Integer v6 = Integer.valueOf(v4.b().intValue() / 25 ); if (v6.intValue() > i && v6.intValue() > = 1 ) { + + i; } + + v0; } ret = Boolean.valueOf(v3.toString().equals( "wigwrkaugala" )); / / v3需要和 "wigwrkaugala" 相等,返回才为true } return ret; } |
看下这两行
1 2 | b v4 = new b(Integer.valueOf( 2 )); / / 实例化类b 为v4对象 a v5 = new a(Integer.valueOf( 3 )); / / 实例化类a 为v5对象 |
看b的构造方法,相当于传入的参数作为偏移下标,然后将c整型数组给进行倒置赋值给a数组列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class b { public static ArrayList a; static String b; Integer[] c; static Integer d; static { b.a = new ArrayList(); b.b = "abcdefghijklmnopqrstuvwxyz" ; b.d = Integer.valueOf( 0 ); } public b(Integer arg9) { / / 即调用了这个构造方法,参数传入的是整形 2 super (); this.c = new Integer[]{Integer.valueOf( 8 ), Integer.valueOf( 25 ), Integer.valueOf( 17 ), Integer.valueOf( 23 ), Integer.valueOf( 7 ), Integer.valueOf( 22 ), Integer.valueOf( 1 ), Integer.valueOf( 16 ), Integer.valueOf( 6 ), Integer.valueOf( 9 ), Integer.valueOf( 21 ), Integer.valueOf( 0 ), Integer.valueOf( 15 ), Integer.valueOf( 5 ), Integer.valueOf( 10 ), Integer.valueOf( 18 ), Integer.valueOf( 2 ), Integer.valueOf( 24 ), Integer.valueOf( 4 ), Integer.valueOf( 11 ), Integer.valueOf( 3 ), Integer.valueOf( 14 ), Integer.valueOf( 19 ), Integer.valueOf( 12 ), Integer.valueOf( 20 ), Integer.valueOf( 13 )}; / / 对整形数组c进行赋值 int v0; for (v0 = arg9.intValue(); v0 < this.c.length; + + v0) { b.a.add(this.c[v0]); / / 将整形数组c的第 2 (传入的参数)位到最后一位添加到给a数组列表 } for (v0 = 0 ; v0 < arg9.intValue(); + + v0) { b.a.add(this.c[v0]); / / 然后再将整形数组c的第 0 位到第 2 (传入的参数)位添加到a数组列表 } } |
即实例化后的对象v4中的变量内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public static ArrayList a; static String b; Integer[] c; static Integer d; #v4.c c = [ 8 , 25 , 17 , 23 , 7 , 22 , 1 , 16 , 6 , 9 , 21 , 0 , 15 , 5 , 10 , 18 , 2 , 24 , 4 , 11 , 3 , 14 , 19 , 12 , 20 , 13 ] print ( len (c)) #v4.a a = [] for i in range ( 2 , len (c)): a.append(c[i]) for i in range ( 0 , 2 ): a.append(c[i]) print (a) #[17, 23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25] |
看类a的构造方法,和类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 | public class a { public static ArrayList a; static String b; Integer[] c; static Integer d; static { a.a = new ArrayList(); a.b = "abcdefghijklmnopqrstuvwxyz" ; a.d = Integer.valueOf( 0 ); } public a(Integer arg8) { / / 3 super (); this.c = new Integer[]{Integer.valueOf( 7 ), Integer.valueOf( 14 ), Integer.valueOf( 16 ), Integer.valueOf( 21 ), Integer.valueOf( 4 ), Integer.valueOf( 24 ), Integer.valueOf( 25 ), Integer.valueOf( 20 ), Integer.valueOf( 5 ), Integer.valueOf( 15 ), Integer.valueOf( 9 ), Integer.valueOf( 17 ), Integer.valueOf( 6 ), Integer.valueOf( 13 ), Integer.valueOf( 3 ), Integer.valueOf( 18 ), Integer.valueOf( 12 ), Integer.valueOf( 10 ), Integer.valueOf( 19 ), Integer.valueOf( 0 ), Integer.valueOf( 22 ), Integer.valueOf( 2 ), Integer.valueOf( 11 ), Integer.valueOf( 23 ), Integer.valueOf( 1 ), Integer.valueOf( 8 )}; int v0; for (v0 = arg8.intValue(); v0 < this.c.length; + + v0) { a.a.add(this.c[v0]); } for (v0 = 0 ; v0 < arg8.intValue(); + + v0) { a.a.add(this.c[v0]); } } |
即实例化后的对象v5(类a)中的变量内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public static ArrayList a; static String b; Integer[] c; static Integer d; #v5.c c = [ 7 , 14 , 16 , 21 , 4 , 24 , 25 , 20 , 5 , 15 , 9 , 17 , 6 , 13 , 3 , 18 , 12 , 10 , 19 , 0 , 22 , 2 , 11 , 23 , 1 , 8 ] print ( len (c)) #v5.a a = [] for i in range ( 3 , len (c)): a.append(c[i]) for i in range ( 0 , 3 ): a.append(c[i]) print (a) #[21, 4, 24, 25, 20, 5, 15, 9, 17, 6, 13, 3, 18, 12, 10, 19, 0, 22, 2, 11, 23, 1, 8, 7, 14, 16] |
然后我们回过去看MainActivity类的b方法中的else中有调用MainActivity.a方法,所以看下MainActivity.a方法
1 2 3 4 | private static char a(String arg1, b arg2, a arg3) { / / 参数 1 为输入的每个字符 / / 参数 2 为对象v4(类b) / / 参数 3 为对象v5(类a) return arg3.a(arg2.a(arg1)); } |
然后调用了v5(类a)对象中的a方法,参数为v4(类b)对象中的a方法,
v4(类b)对象中的a方法的参数为我们的输入的每一个字符。
我们先看v4(类b)对象中的a方法
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 | b.a = [ 17 , 23 , 7 , 22 , 1 , 16 , 6 , 9 , 21 , 0 , 15 , 5 , 10 , 18 , 2 , 24 , 4 , 11 , 3 , 14 , 19 , 12 , 20 , 13 , 8 , 25 ] b.b = "abcdefghijklmnopqrstuvwxyz" ; public Integer a(String arg5) { / / 参数为输入的每个字符 int v0 = 0 ; Integer v1 = Integer.valueOf( 0 ); if (b.b.contains(arg5.toLowerCase())) { / / 判断b字符串是否包含 将参数转成小写 Integer v2 = Integer.valueOf(b.b.indexOf(arg5)); / / v2为 参数在b字符串中首次出现的位置,即下标(b字符串中无重复字符) while (v0 < b.a.size() - 1 ) { if (b.a.get(v0) = = v2) { v1 = Integer.valueOf(v0); / / 如果a数组列表中的第某个下标对应的内容和v2相同 v1为的值就取第某个下标 } + + v0; } } else { if (arg5.contains( " " )) { v1 = Integer.valueOf( - 10 ); goto label_24; } v1 = Integer.valueOf( - 1 ); } label_24: b.a(); / / 返回之前调用了a方法 return v1; / / 返回此下标 } |
再看下a方法是什么
1 2 3 4 5 6 7 8 9 10 11 12 | b.a = [ 17 , 23 , 7 , 22 , 1 , 16 , 6 , 9 , 21 , 0 , 15 , 5 , 10 , 18 , 2 , 24 , 4 , 11 , 3 , 14 , 19 , 12 , 20 , 13 , 8 , 25 ] b.b = "abcdefghijklmnopqrstuvwxyz" ; b.d = 0 ; public static void a() { int v0 = b.a.get( 0 ).intValue(); / / v0是 17 b.a.remove( 0 ); / / b.a = [ 23 , 7 , 22 , 1 , 16 , 6 , 9 , 21 , 0 , 15 , 5 , 10 , 18 , 2 , 24 , 4 , 11 , 3 , 14 , 19 , 12 , 20 , 13 , 8 , 25 ] b.a.add(Integer.valueOf(v0)); / / b.a = [ 23 , 7 , 22 , 1 , 16 , 6 , 9 , 21 , 0 , 15 , 5 , 10 , 18 , 2 , 24 , 4 , 11 , 3 , 14 , 19 , 12 , 20 , 13 , 8 , 25 , 17 ] b.b = b.b + " " + b.b.charAt(0); //b.b = " abcdefghijklmnopqrstuvwxyza" b.b = b.b.substring( 1 , 27 ); / / b.b = "bcdefghijklmnopqrstuvwxyza" b.d = Integer.valueOf(b.d.intValue() + 1 ); / / 1 } |
相等于每处理一次,v4(类b)对象中的变量都会发生一次变化。
接着调用了v5(类a)中的a方法
返回的v1作为参数。
然后看下v5(类a)中的a方法
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 | a.a = [ 21 , 4 , 24 , 25 , 20 , 5 , 15 , 9 , 17 , 6 , 13 , 3 , 18 , 12 , 10 , 19 , 0 , 22 , 2 , 11 , 23 , 1 , 8 , 7 , 14 , 16 ] a.b = "abcdefghijklmnopqrstuvwxyz" ; static { a.a = new ArrayList(); a.b = "abcdefghijklmnopqrstuvwxyz" ; a.d = Integer.valueOf( 0 ); } public char a(Integer arg5) { char v0_1; int v0 = 0 ; Integer v1 = Integer.valueOf( 0 ); if (arg5.intValue() = = - 10 ) { a.a(); v0_1 = " " .charAt( 0 ); } else { while (v0 < a.a.size() - 1 ) { if (a.a.get(v0) = = arg5) { / / 判断a中的a数组列表第几位和传进来的参数(输入的每个字符)相等 v1 = Integer.valueOf(v0); / / 如果相等就获得下表v1 } + + v0; } a.a(); v0_1 = a.b.charAt(v1.intValue()); / / 返回值为a类b字符串的下表对应的字符 } return v0_1; } |
在返回之前调用了a.a方法
看下a.a
1 2 3 4 5 6 7 8 9 10 11 12 | a.b = "abcdefghijklmnopqrstuvwxyz" ; a.d = 0 ; public static void a() { a.d = Integer.valueOf(a.d.intValue() + 1 ); / / 每次加 1 if (a.d.intValue() = = 25 ) { / / 增加到 25 才会对a.a数组列表进行一个倒置 int v0 = a.a.get( 0 ).intValue(); a.a.remove( 0 ); a.a.add(Integer.valueOf(v0)); a.d = Integer.valueOf( 0 ); } } |
当输入的字符串的每一个字符都经过上面的运算后的结果如果和"wigwrkaugala"就可以。
逆向去分析下
"wigwrkaugala"的长度为12
所以每次调用v5(a类).a方法后,其中只会增加v5(a类).d的值,但不会倒置v5(a类).b
所以从wigwrkaugala开始逆向分析。
第一个字符"w"为a类b字符串的下表对应的字符,而a类b字符串是固定的,所以可反推出"w"在a类b字符串中的下标。有了下标就可以知道v5(类a)中的a方法的参数是什么了。得到的v5(类a)中的a方法的参数其实就是v4(类b)中的a方法的返回值,这个返回值是v4(类b)中的a数组列表中的下标 (根据b.a.get(v0) == v2),有了下标就可以得到v2的值,再根据v2 = Integer.valueOf(b.b.indexOf(arg5),得知v2其实是输入的字符在v4(类b)中的b字符串的下标,而v4(类b)中的b字符又是知道的,所以即可得到输入的单个字符。
解题
exp如下
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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | #v4.c c = [ 8 , 25 , 17 , 23 , 7 , 22 , 1 , 16 , 6 , 9 , 21 , 0 , 15 , 5 , 10 , 18 , 2 , 24 , 4 , 11 , 3 , 14 , 19 , 12 , 20 , 13 ] print ( len (c)) #v4.a a = [] for i in range ( 2 , len (c)): a.append(c[i]) for i in range ( 0 , 2 ): a.append(c[i]) print (a) #[17, 23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25] #v5.c c = [ 7 , 14 , 16 , 21 , 4 , 24 , 25 , 20 , 5 , 15 , 9 , 17 , 6 , 13 , 3 , 18 , 12 , 10 , 19 , 0 , 22 , 2 , 11 , 23 , 1 , 8 ] print ( len (c)) #v5.a a = [] for i in range ( 3 , len (c)): a.append(c[i]) for i in range ( 0 , 3 ): a.append(c[i]) print (a) #[21, 4, 24, 25, 20, 5, 15, 9, 17, 6, 13, 3, 18, 12, 10, 19, 0, 22, 2, 11, 23, 1, 8, 7, 14, 16] #exp daan = "wigwrkaugala" a_b = "abcdefghijklmnopqrstuvwxyz" #获取daan在a_b的下标 index = [] for i in range ( len (daan)): temp = a_b.find(daan[i]) #print(temp) index.append(temp) print ( "下标为:" ,end = "") print (index) #传进来的参数 a_a = [ 21 , 4 , 24 , 25 , 20 , 5 , 15 , 9 , 17 , 6 , 13 , 3 , 18 , 12 , 10 , 19 , 0 , 22 , 2 , 11 , 23 , 1 , 8 , 7 , 14 , 16 ] can = [] for i in range ( len (index)): can.append(a_a[index[i]]) print ( "参数为:" ,end = "") print (can) #这里的参数can其实就是v4(类b)对象中的a方法的返回值。 #根据它再去推v2 v2为输入字符在b字符串中首次出现的位置,即下标(b字符串中无重复字符) flag = "" can = [ 8 , 17 , 15 , 8 , 22 , 13 , 21 , 23 , 15 , 21 , 3 , 21 ] b_a = [ 17 , 23 , 7 , 22 , 1 , 16 , 6 , 9 , 21 , 0 , 15 , 5 , 10 , 18 , 2 , 24 , 4 , 11 , 3 , 14 , 19 , 12 , 20 , 13 , 8 , 25 ] b_b = "abcdefghijklmnopqrstuvwxyz" #获取下标 offset = [] for i in range ( len (can)): offset.append(b_a[can[i]]) print (offset) #获取flag flag + = b_b[offset[ 0 ]] print (flag) #懒死了,有更好的解题脚本,不想动了。复制代码再来次。把b_a b_b can的值略改下得了。 can = [ 17 , 15 , 8 , 22 , 13 , 21 , 23 , 15 , 21 , 3 , 21 ] #每次删一个 b_a = [ 23 , 7 , 22 , 1 , 16 , 6 , 9 , 21 , 0 , 15 , 5 , 10 , 18 , 2 , 24 , 4 , 11 , 3 , 14 , 19 , 12 , 20 , 13 , 8 , 25 , 17 ] #首移到尾 b_b = "bcdefghijklmnopqrstuvwxyza" #首移到尾 #获取下标 offset = [] for i in range ( len (can)): offset.append(b_a[can[i]]) print (offset) #获取flag flag + = b_b[offset[ 0 ]] print (flag) can = [ 15 , 8 , 22 , 13 , 21 , 23 , 15 , 21 , 3 , 21 ] #每次删一个 b_a = [ 7 , 22 , 1 , 16 , 6 , 9 , 21 , 0 , 15 , 5 , 10 , 18 , 2 , 24 , 4 , 11 , 3 , 14 , 19 , 12 , 20 , 13 , 8 , 25 , 17 , 23 ] #首移到尾 b_b = "cdefghijklmnopqrstuvwxyzab" #首移到尾 #获取下标 offset = [] for i in range ( len (can)): offset.append(b_a[can[i]]) print (offset) #获取flag flag + = b_b[offset[ 0 ]] print (flag) can = [ 8 , 22 , 13 , 21 , 23 , 15 , 21 , 3 , 21 ] #每次删一个 b_a = [ 22 , 1 , 16 , 6 , 9 , 21 , 0 , 15 , 5 , 10 , 18 , 2 , 24 , 4 , 11 , 3 , 14 , 19 , 12 , 20 , 13 , 8 , 25 , 17 , 23 , 7 ] #首移到尾 b_b = "defghijklmnopqrstuvwxyzabc" #首移到尾 #获取下标 offset = [] for i in range ( len (can)): offset.append(b_a[can[i]]) print (offset) #获取flag flag + = b_b[offset[ 0 ]] print (flag) can = [ 22 , 13 , 21 , 23 , 15 , 21 , 3 , 21 ] #每次删一个 b_a = [ 1 , 16 , 6 , 9 , 21 , 0 , 15 , 5 , 10 , 18 , 2 , 24 , 4 , 11 , 3 , 14 , 19 , 12 , 20 , 13 , 8 , 25 , 17 , 23 , 7 , 22 ] #首移到尾 b_b = "efghijklmnopqrstuvwxyzabcd" #首移到尾 #获取下标 offset = [] for i in range ( len (can)): offset.append(b_a[can[i]]) print (offset) #获取flag flag + = b_b[offset[ 0 ]] print (flag) #venividivkcr flag{venividivkcr} |
这题有一说一写的太乱了,如果有人要复现建议的话参考下其它大佬的。
这里随便贴几篇吧。
(32条消息) 攻防世界easyJava(re Moble)_CTF小白的博客-CSDN博客
(32条消息) 攻防世界-easyjava-WriteupSkYe231的博客-CSDN博客
攻防世界-Mobile-easyjava-最详细分析 - 灰信网(软件开发博客聚合) (freesion.com)
easyjni
题目信息
下载安装运行,看起来和上题差不多
分析
将其拖入jeb中查看java代码
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 | package com.a.easyjni; import android.content.Context; import android.os.Bundle; import android.support.v7.app.c; import android.view.View$OnClickListener; import android.view.View; import android.widget.Toast; public class MainActivity extends c { / / 加载native的so库 static { System.loadLibrary( "native" ); } public MainActivity() { super (); } static boolean a(MainActivity arg1, String arg2) { return arg1.a(arg2); } private boolean a(String arg3) { boolean v0_1; try { v0_1 = this.ncheck(new a().a(arg3.getBytes())); } catch(Exception v0) { v0_1 = false; } return v0_1; } private native boolean ncheck(String arg1) { } protected void onCreate(Bundle arg3) { super .onCreate(arg3); this.setContentView( 0x7F04001B ); this.findViewById( 0x7F0B0076 ).setOnClickListener(new View$OnClickListener(((Context)this)) { public void onClick(View arg4) { if (MainActivity.a(this.b, this.a.findViewById( 0x7F0B0075 ).getText().toString())) { Toast.makeText(this.a, "You are right!" , 1 ).show(); } else { Toast.makeText(this.a, "You are wrong! Bye~" , 1 ).show(); } } }); } } |
当点击check时调用MainActivity.a方法,其中参数1为this.b,参数2为输入字符串,看下MainActivity.a方法
1 2 3 4 5 6 7 8 9 10 11 | private boolean a(String arg3) { boolean v0_1; try { v0_1 = this.ncheck(new a().a(arg3.getBytes())); } catch(Exception v0) { v0_1 = false; } return v0_1; } |
发现实例化了类a对象,并调用了a.a方法,参数为输入的字符串。,返回的结果继续作为this.ncheck的方法。
看下a.a方法
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 | package com.a.easyjni; public class a { private static final char[] a; static { a.a = new char[]{ 'i' , '5' , 'j' , 'L' , 'W' , '7' , 'S' , '0' , 'G' , 'X' , '6' , 'u' , 'f' , '1' , 'c' , 'v' , '3' , 'n' , 'y' , '4' , 'q' , '8' , 'e' , 's' , '2' , 'Q' , '+' , 'b' , 'd' , 'k' , 'Y' , 'g' , 'K' , 'O' , 'I' , 'T' , '/' , 't' , 'A' , 'x' , 'U' , 'r' , 'F' , 'l' , 'V' , 'P' , 'z' , 'h' , 'm' , 'o' , 'w' , '9' , 'B' , 'H' , 'C' , 'M' , 'D' , 'p' , 'E' , 'a' , 'J' , 'R' , 'Z' , 'N' }; } public a() { super (); } public String a(byte[] arg10) { int v8 = 3 ; StringBuilder v4 = new StringBuilder(); int v0; for (v0 = 0 ; v0 < = arg10.length - 1 ; v0 + = 3 ) { byte[] v5 = new byte[ 4 ]; int v3 = 0 ; byte v2 = 0 ; while (v3 < = 2 ) { if (v0 + v3 < = arg10.length - 1 ) { v5[v3] = ((byte)(v2 | (arg10[v0 + v3] & 0xFF ) >>> v3 * 2 + 2 )); v2 = ((byte)(((arg10[v0 + v3] & 0xFF ) << ( 2 - v3) * 2 + 2 & 0xFF ) >>> 2 )); } else { v5[v3] = v2; v2 = 0x40 ; } + + v3; } v5[v8] = v2; int v2_1; for (v2_1 = 0 ; v2_1 < = v8; + + v2_1) { if (v5[v2_1] < = 0x3F ) { v4.append(a.a[v5[v2_1]]); } else { v4.append( '=' ); } } } return v4.toString(); } } |
很容易看出base64编码,但是table表是有发生变化的。加密后的结果作为返回值。
然后看this.ncheck代码
1 2 | private native boolean ncheck(String arg1) { } |
发现是它的代码实现在so库中,首先使用apktool对它进行解包,获得so库,然后再so库导入ida中进行分析
找到Java_com_a_easyjni_MainActivity_ncheck函数
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 | signed int __fastcall Java_com_a_easyjni_MainActivity_ncheck(_JNIEnv * env, int a2, int a3) { int v3; / / r8 _JNIEnv * v4; / / r5 int v5; / / r8 const char * base64_str; / / r6 int i; / / r0 char * v8; / / r2 char v9; / / r1 int j; / / r0 bool v11; / / nf unsigned __int8 v12; / / vf int v13; / / r1 signed int result; / / r0 char s1[ 32 ]; / / [sp + 3h ] [bp - 35h ] char temp; / / [sp + 23h ] [bp - 15h ] int v17; / / [sp + 28h ] [bp - 10h ] v17 = v3; v4 = env; v5 = a3; base64_str = (const char * )(( int (__fastcall * )(_JNIEnv * , int , _DWORD))env - >functions - >GetStringUTFChars)(env, a3, 0 ); if ( strlen(base64_str) = = 32 ) { i = 0 ; do { v8 = &s1[i]; s1[i] = base64_str[i + 16 ]; / / s1从下标 16 开始取 取 16 位 v9 = base64_str[i + + ]; v8[ 16 ] = v9; / / v8从下标 0 开始取 取 16 位 } while ( i ! = 16 ); / / 16 轮 ((void (__fastcall * )(_JNIEnv * , int , const char * ))v4 - >functions - >ReleaseStringUTFChars)(v4, v5, base64_str); j = 0 ; do { / / 正数减正数不会溢出 这里v12恒为 0 v12 = __OFSUB__(j, 30 ); / / 当j大于或等于 30 v11为也为 0 v11 = j - 30 < 0 ; temp = s1[j]; / / 从输入的后 16 位进行取值 s1[j] = s1[j + 1 ]; / / 位置互换 s1[j + 1 ] = temp; j + = 2 ; / / 每轮 + 2 } while ( v11 ^ v12 ); / / 即当j> = 30 时,v11^v12才不满足条件,结束循环 v13 = memcmp(s1, "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7" , 0x20u ); / / 最后和目标字符串作比较 result = 0 ; if ( !v13 ) result = 1 ; } else { ((void (__fastcall * )(_JNIEnv * , int , const char * ))v4 - >functions - >ReleaseStringUTFChars)(v4, v5, base64_str); result = 0 ; } return result; } |
其实这里的算法很简单,首先将将传进来的base64编码后的字符串进行前16个字节数据和后面16字节数据进行颠倒存放到s1数组中。
然后再将s1数组的前32位的每2位进行互换位置,
最后的结果和 "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"需要是一致的。
所以逆过去即可。
解题
exp
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 | #coding:utf8 jieguo = "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7" str1 = list (jieguo) #print(str1)#['M', 'b', 'T', '3', 's', 'Q', 'g', 'X', '0', '3', '9', 'i', '3', 'g', '=', '=', 'A', 'Q', 'O', 'o', 'M', 'Q', 'F', 'P', 's', 'k', 'B', '1', 'B', 's', 'c', '7'] str2 = "" for i in range ( 0 , 32 , 2 ): str1[i],str1[i + 1 ] = str1[i + 1 ],str1[i] #print(str1)#['b', 'M', '3', 'T', 'Q', 's', 'X', 'g', '3', '0', 'i', '9', 'g', '3', '=', '=', 'Q', 'A', 'o', 'O', 'Q', 'M', 'P', 'F', 'k', 's', '1', 'B', 's', 'B', 'c', '7'] str1[: 16 ], str1[ 16 :] = str1[ 16 :], str1[: 16 ] print (str1) #['Q', 'A', 'o', 'O', 'Q', 'M', 'P', 'F', 'k', 's', '1', 'B', 's', 'B', 'c', '7', 'b', 'M', '3', 'T', 'Q', 's', 'X', 'g', '3', '0', 'i', '9', 'g', '3', '=', '='] str2 = "" for i in range ( len (str1)): str2 + = str1[i] print (str2) import base64 import string import binascii base64_ok = "QAoOQMPFks1BsB7cbM3TQsXg30i9g3==" outtab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" table = [ 'i' , '5' , 'j' , 'L' , 'W' , '7' , 'S' , '0' , 'G' , 'X' , '6' , 'u' , 'f' , '1' , 'c' , 'v' , '3' , 'n' , 'y' , '4' , 'q' , '8' , 'e' , 's' , '2' , 'Q' , '+' , 'b' , 'd' , 'k' , 'Y' , 'g' , 'K' , 'O' , 'I' , 'T' , '/' , 't' , 'A' , 'x' , 'U' , 'r' , 'F' , 'l' , 'V' , 'P' , 'z' , 'h' , 'm' , 'o' , 'w' , '9' , 'B' , 'H' , 'C' , 'M' , 'D' , 'p' , 'E' , 'a' , 'J' , 'R' , 'Z' , 'N' ] intab = "" for i in range ( len (table)): intab + = table[i] print (intab) #flag{just_ANot#er_@p3} teaout = (base64.b64decode(base64_ok.translate(base64_ok.maketrans(intab,outtab)))) print (teaout) #b'flag{just_ANot#er_@p3}' |
XCTF-easyjni - S1mba - 博客园 (cnblogs.com)
(32条消息) IDA OFSUB 测试_hambaga的博客-CSDN博客
IDA头文件摘录_Em0s_Er1t的博客-程序员宅基地_ida 头文件 - 程序员宅基地 (cxyzjd.com)
easy-so
题目信息
下载安装运行,随便输入,点击check,弹框验证失败。
分析
将其拖入jeb中,查看MainActivity类,当我们点击check按钮时,会调用类cyberpeace中的CheckString方法了,参数位我们的输入。
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 | package com.testjava.jack.pingan2; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View$OnClickListener; import android.view.View; import android.widget.Toast; public class MainActivity extends AppCompatActivity { public MainActivity() { super (); } protected void onCreate(Bundle arg3) { super .onCreate(arg3); this.setContentView( 0x7F09001B ); this.findViewById( 0x7F070022 ).setOnClickListener(new View$OnClickListener() { public void onClick(View arg6) { if (cyberpeace.CheckString(MainActivity.this.findViewById( 0x7F070031 ).getText().toString()) = = 1 ) { Toast.makeText(MainActivity.this, "验证通过!" , 1 ).show(); } else { Toast.makeText(MainActivity.this, "验证失败!" , 1 ).show(); } } }); } } |
额,逻辑好简单。看下类cyberpeace中的CheckString是怎么实现的
1 2 3 4 5 6 7 8 9 10 11 12 | package com.testjava.jack.pingan2; public class cyberpeace { static { System.loadLibrary( "cyberpeace" ); } public cyberpeace() { super (); } public static native int CheckString(String arg0) { } } |
发现这个方法是在cyberpeace.so中实现的。
解包得到so库,将其拖入ida中,查看Java_com_testjava_jack_pingan2_cyberpeace_CheckString函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | signed int __fastcall Java_com_testjava_jack_pingan2_cyberpeace_CheckString(_JNIEnv * env) { signed int v1; / / r8 const char * input ; / / r9 size_t len ; / / r6 const char * input_str; / / r5 signed int v5; / / r1 v1 = 0 ; input = (const char * )(( int ( * )(void))env - >functions - >GetStringUTFChars)(); len = strlen( input ); input_str = (const char * )malloc( len + 1 ); v5 = 0 ; if ( len ! = - 1 ) v5 = 1 ; _aeabi_memclr(&input_str[ len ], v5); _aeabi_memcpy(input_str, input , len ); j_TestDec(( int )input_str); if ( !strcmp(input_str, "f72c5a36569418a20907b55be5bf95ad" ) ) v1 = 1 ; return v1; } |
将输入的字符串经过了j_TestDec函数处理,返回的结果需要和"f72c5a36569418a20907b55be5bf95ad"相等。
然后分析下j_TestDec函数
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 | size_t __fastcall TestDec(const char * input ) { char * v1; / / r4 size_t v2; / / r5 int v3; / / r1 char v4; / / r0 size_t result; / / r0 int j; / / r5 int v7; / / r0 char temp; / / r1 unsigned int v9; / / r1 v1 = (char * ) input ; if ( strlen( input ) > = 2 ) { v2 = 0 ; do { v3 = ( int )&v1[v2]; v4 = v1[v2]; v1[v2] = v1[v2 + 16 ]; + + v2; * (_BYTE * )(v3 + 16 ) = v4; } while ( v2 < strlen(v1) >> 1 ); / / 将前后 16 字节数据进行换位置 结果存放到v1中 } result = (unsigned __int8) * v1; if ( * v1 ) { * v1 = v1[ 1 ]; v1[ 1 ] = result; result = strlen(v1); if ( result > = 3 ) { j = 0 ; do { v7 = ( int )&v1[j]; temp = v1[j + 2 ]; * (_BYTE * )(v7 + 2 ) = v1[j + 3 ]; * (_BYTE * )(v7 + 3 ) = temp; result = strlen(v1); v9 = j + 4 ; j + = 2 ; } while ( v9 < result ); / / 将v1中的每 2 个字符都换下位置 } } return result; } |
解题
这题和easyjni一样,还少了一个base64编码。
将 "f72c5a36569418a20907b55be5bf95ad"先进行每2位换位,然后再前后的16位的字符进行互换就是flag
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #coding:utf8 jieguo = "f72c5a36569418a20907b55be5bf95ad" str1 = list (jieguo) print (str1) str2 = "" for i in range ( 0 , 32 , 2 ): str1[i],str1[i + 1 ] = str1[i + 1 ],str1[i] print (str1) str1[: 16 ], str1[ 16 :] = str1[ 16 :], str1[: 16 ] print (str1) str2 = "" for i in range ( len (str1)): str2 + = str1[i] print (str2) #90705bb55efb59da7fc2a5636549812a |
即flag{90705bb55efb59da7fc2a5636549812a}