Reverse EasyCpp [2019/10/25更新] 首先读入16个数字,然后生成一个斐波那契数组,长度也为16 然后对输入数组进行操作,从第二位开始,都加上第一位的值(transform
) 动态调试可知accumulate
的匿名函数对运算完的数组做反转操作 然后与斐波那契数组比较,相同则输出flag 也就是说输入的第一个数字是不会变的,所以就是987 后面的数字直接脚本跑一下就可以得到结果了
Crypto 哈夫曼之谜 100pt 二叉树是真的还没学,还好找到了大佬的代码南邮数据结构实验二—二叉树的基本操作及哈夫曼编码译码系统的实现
解题思路 得到一个文本,内容如下
1 2 3 4 5 6 7 8 9 10 11 11000111000001010010010101100110110101111101110101011110111111100001000110010110101111001101110001000110 a:4 d:9 g:1 f:5 l:1 0:7 5:9 {:1 }:1
由题意知, 使用了哈夫曼编码。所以上面的是二进制编码,下面的是词频 使用大佬的程序生成哈夫曼树,再解码得到flag。 因为5和d词频一样,如果flag不对,两者替换一下就行了 (原来这题只要提交flag{}里面的字符,难怪我提交2次都错了 = =)
Misc crackme 300pt 分析 这是一道安卓逆向题,反编译后修改apk主入口为CokeyActivity,进入输入用户名和密码的界面。 (默认进入的界面是GameActivity,该游戏基于js编写,代码加密,对这块不熟,所以直接分析另一个Activity) 分析代码可知用户名需为Conan
,密码则需要将明文处理一番后与950519fec04js3mjtyioqbwxoshxvbprsjir1miy536kHp6GbGiTfL6GckiKfcUG0PzjFgijHmikAr-DUm39096000030240
相等,生成密文的函数代码为(JEB2提取):
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 public static String a (String arg18, int arg19) { String v0 = arg18.replace(" " , "" ); int v2 = 10 ; if (v0.length() >= v2) { if (v0.length() > 99 ) { } else { String v1 = "IVMDy5x1A0ali-O|fbnKEHjTUQz86oqdNewCvPGX4utJ3SZ9cLR2gmpkFrh7WsYB|da6202b5FA5fF510702092e729A3afN8" ; Random v3 = new Random(); StringBuilder v4 = new StringBuilder(); String v5 = "950519" ; int v6 = v0.length(); StringBuilder v8 = new StringBuilder(); v8.append(System.currentTimeMillis()); v8.append("" ); int v10 = 2 ; String v8_1 = v8.toString().substring(v10, 4 ); StringBuilder v9 = new StringBuilder(); v9.append(System.currentTimeMillis()); v9.append("" ); String v9_1 = v9.toString().substring(6 , 8 ); String v13_1 = (((int )(Math.random() * 5 ))) + "" ; int v14 = 0 ; String v15 = "" ; int v7; for (v7 = 0 ; v7 < v6; ++v7) { int v12 = v0.charAt(v7); if (47 < v12 && v12 < 58 ) { v12 = (v12 - 48 + Integer.parseInt(v8_1) + v7 * v7 % arg19) % v2 + 48 ; } if (64 < v12 && v12 < 91 ) { v12 = (v12 - 65 + Integer.parseInt(v9_1) + v7 * v7 % arg19) % 26 + 65 ; } if (96 < v12 && v12 < 123 ) { v12 = (v12 - 97 + Integer.parseInt(v13_1) + v7 * v7 % arg19) % 26 + 97 ; } v15 = v15 + (((char )v12)); } int v0_1 = v15.length(); v2 = v3.nextInt(v0_1 - 1 ); String v3_1 = v15.substring(0 , v2); v0 = v15.substring(v2, v0_1); v0 = v5 + v0 + v8_1; for (v3_1 = new BigInteger(1 , v3_1.getBytes("UTF-8" )).toString(v10); v3_1.length() % 8 != 0 ; v3_1 = "0" + v3_1) { } v5_1 = new StringBuilder(); v5_1.append(v6 + arg19); v5_1.append(String.format("%06d" , Integer.valueOf(v2))); StringBuilder v2_1 = new StringBuilder(); v2_1.append(v6); v2_1.append("" ); v5_1.append(v2_1.toString().length()); String v2_2 = v5_1.toString(); int v5_2; for (v5_2 = 0 ; v3_1.length() % 24 != 0 ; ++v5_2) { v3_1 = v3_1 + "0" ; } while (v14 <= v3_1.length() - 6 ) { v6 = v14 + 6 ; int v8_2 = Integer.parseInt(v3_1.substring(v14, v6), v10); if (v8_2 != 0 || v14 < v3_1.length() - v5_2) { v4.append(v1.charAt(v8_2)); } else { v4.append("*" ); } v14 = v6; } v1 = v4.toString(); return v0 + v1 + v9_1 + v13_1 + v2_2 + v1.length(); } } return "" ; }
分析代码逻辑 可以看到其中有取时间戳,随机数生成,所以我们需要把这些固定下来。
分析Java代码可知密文结构如下:
1 2 950519 fec04js3mjtyioqbwxoshxvbprsjir1miy 53 6kHp6GbGiTfL6GckiKfcUG0PzjFgijHmikAr-DUm 39 0 96 000030 2 40 固定前缀 sec1 时间戳取2-4位 sec2 时间戳取6-8位 5以内随机数 明文长度+32 明文位数-1内取随机数再补0至6位 固定数值(明文长度的长度) sec2长度
sec1: 30-64位替换结果 sec2: 0-30位替换后再处理结果 分析完密文结构可知,明文长度为64位(96-32) sec2生成具体流程为: 将密文进行逐字替换(所处位置不同,替换结果也不同)。 然后取前30位,逐位转化为二进制,除第一项外,其余项目前面补0至八位,再末尾补0至24的倍数 每六位作为二进制数,转化为十进制,读取指定字符串的该位置字符,再拼接成字符串 所以我们需要逆向求解得到替换的结果(中间密文),再爆破解得原始明文
抽取替换代码为函数 为爆破做准备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static char getRet (int x, int index ) { if (47 < x && x < 58 ) { x = (x - 48 + 53 + index * index % 32 ) % 10 + 48 ; } if (64 < x && x < 91 ) { x = (x - 65 + 39 + index * index % 32 ) % 26 + 65 ; } if (96 < x && x < 123 ) { x = (x - 97 + 0 + index * index % 32 ) % 26 + 97 ; } return (((char )x)); }
解题方法 首先我们需要求到前30位,写个python脚本就可
1 2 3 4 5 6 7 8 9 f = "6kHp6GbGiTfL6GckiKfcUG0PzjFgijHmikAr-DUm" ; v1 = "IVMDy5x1A0ali-O|fbnKEHjTUQz86oqdNewCvPGX4utJ3SZ9cLR2gmpkFrh7WsYB|da6202b5FA5fF510702092e729A3afN8" ; s="" for i in range (len (f)): tmp = f[i] loc = v1.index(tmp) s += str (bin (loc)).replace("0b" ,"" ).rjust(6 ,'0' ) print (s)
得到011100110111010101110110011100100110010001100110001100010111010000110001011100100110110000110111001100010011010000110000011000100110001001100101011010010110111000110100001100010110010101110101001100110111001000111001001101000011011000110101
然后把它转成十进制 (去掉了开头的0)
1 2 3 4 5 6 7 ret = "" s = "11100110111010101110110011100100110010001100110001100010111010000110001011100100110110000110111001100010011010000110000011000100110001001100101011010010110111000110100001100010110010101110101001100110111001000111001001101000011011000110101" for i in range (30 ): ttt = s[8 *i:8 *(i+1 )-1 ] ret += chr (int (ttt,2 )) print (ret)
得到suvrdf1t1rl7140bbein41eu3r9465
这就是中间密文得前30位,而后34位则是fec04js3mjtyioqbwxoshxvbprsjir1miy
,拼接起来爆破即可(Java)
1 2 3 4 5 6 7 8 9 10 String interStr = "suvrdf1t1rl7140bbein41eu3r9465fec04js3mjtyioqbwxoshxvbprsjir1miy" ; StringBuilder sb = new StringBuilder(); for (int i = 0 ;i<interStr.length();i++) { char ta = interStr.charAt(i); for (int j = 48 ;j<123 ;j++) { if (getRet(j,i) == ta) sb.append((char )(j)); } } Log.d("Xhy" ,sb.toString());
得到string4c8ah9223abdee53ad0a2673bdc67ac5isthepasswordofclasses2dex
所以这只是第一步,assets\vendor\multidex
下有个拓展名为dex的zip文件,使用4c8ah9223abdee53ad0a2673bdc67ac5
解压。 里面有个Cokey.smali
文件,调用了native方法qwert
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 .field private qwert_param1:Ljava/lang/String; .field private qwert_param2:Ljava/lang/String; .method static constructor <clinit>()V .locals 1 .line 14 const-string v0, "cokey" invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V .line 15 return-void .end method .method public constructor <init>()V .locals 1 .line 8 invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V .line 10 const-string v0, "from CokeyActivity" iput-object v0, p0, Lxyz/sysorem/cokey/Cokey;->qwert_param1:Ljava/lang/String; .line 11 const-string v0, "from GameActivity" iput-object v0, p0, Lxyz/sysorem/cokey/Cokey;->qwert_param2:Ljava/lang/String; return-void .end method .method public onCreate(Landroid/os/Bundle;Landroid/os/PersistableBundle;)V .locals 0 .param p1, "savedInstanceState" .annotation build Landroid/support/annotation/Nullable; .end annotation .end param .param p2, "persistentState" .annotation build Landroid/support/annotation/Nullable; .end annotation .end param .line 20 invoke-super {p0, p1, p2}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;Landroid/os/PersistableBundle;)V .line 21 return-void .end method .method public native qwert(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; .end method
所以新建一个工程,包名跟原程序一样,修改MainActivity为CokeyActivity,代码如下:
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 package xyz.sysorem.cokey;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.widget.Toast;public class CokeyActivity extends AppCompatActivity { static String parm1="from CokeyActivity" ; static String parm2="from GameActivity" ; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); String s = qwert(parm1,parm2); Toast.makeText(this ,s,Toast.LENGTH_SHORT).show(); } static { System.loadLibrary("cokey" ); } public native String qwert (String s1,String s2) ; }
运行试试,返回真実はいつもひとつ !
(真相只有一个),果然没那么简单。 ida打开libcokey.so
,在JNI_OnLoad
注册了qwert方法,指向ready,打开该方法,有获取签名和从证书中读取公钥的操作。并存在对时间戳和随机数的调用,所以需要通过修改跳转来修改程序执行流程。 然后根据一系列操作生成aes_key进行AES解密操作(eee函数),再调用aaa, bbb函数进行转换,目测是转化成16进制字符串 所以关键就在于,如何得到正确的密文和aeskey来进行后续的解密操作 注意到ready函数中有对mystery进行赋值的操作,所以推测mystery即为密文 时间所限,剩下的以后再看吧(逃