bugku LoopAndLoop(阿里CTF)
资料来源
来自bugku的LoopAndLoop(阿里CTF)。
工具
- JADX 用于解包和分析APK
- IDA 7.5 SP3 逆向分析so
简单看一下
拿到压缩包解压,看到文件时APK格式,直接拖到夜神模拟器看看。
简单的测试了下如下

反编译,提取算法
将APK拖到JADX,找到MainActivity,代码如下
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 | public class MainActivity extends AppCompatActivity { public native int chec(int i, int i2); public native String stringFromJNI2(int i); /* JADX INFO: Access modifiers changed from: protected */ @Override // android.support.v7.app.AppCompatActivity, android.support.v4.app.FragmentActivity, android.support.v4.app.BaseFragmentActivityDonut, android.app.Activity public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = (Button) findViewById(R.id.button); final TextView tv1 = (TextView) findViewById(R.id.textView2); final TextView tv2 = (TextView) findViewById(R.id.textView3); final EditText ed = (EditText) findViewById(R.id.editText); button.setOnClickListener(new View.OnClickListener() { // from class: net.bluelotus.tomorrow.easyandroid.MainActivity.1 @Override // android.view.View.OnClickListener public void onClick(View v) { String in_str = ed.getText().toString(); try { int in_int = Integer.parseInt(in_str); if (MainActivity.this.check(in_int, 99) == 1835996258) { tv1.setText("The flag is:"); tv2.setText("alictf{" + MainActivity.this.stringFromJNI2(in_int) + "}"); return; } tv1.setText("Not Right!"); } catch (NumberFormatException e) { tv1.setText("Not a Valid Integer number"); } } }); } @Override // android.app.Activity public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override // android.app.Activity public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } public String messageMe(String text) { return "LoopOk" + text; } public int check(int input, int s) { return chec(input, s); } public int check1(int input, int s) { int t = input; for (int i = 1; i < 100; i++) { t += i; } return chec(t, s); } public int check2(int input, int s) { int t = input; if (s % 2 == 0) { for (int i = 1; i < 1000; i++) { t += i; } return chec(t, s); } for (int i2 = 1; i2 < 1000; i2++) { t -= i2; } return chec(t, s); } public int check3(int input, int s) { int t = input; for (int i = 1; i < 10000; i++) { t += i; } return chec(t, s); } static { System.loadLibrary("lhm"); }} |
安卓层面主要的逻辑
1 2 3 4 5 6 7 | int in_int = Integer.parseInt(in_str);if (MainActivity.this.check(in_int, 99) == 1835996258) { tv1.setText("The flag is:"); tv2.setText("alictf{" + MainActivity.this.stringFromJNI2(in_int) + "}"); return;}tv1.setText("Not Right!"); |
以上代码逻辑
- 将输入的字符串转int
- 将int传入check函数,得到的int必须等于1835996258
双击check函数,一层层引用可以找到最终调用的是
1 | public native int chec(int i, int i2); |
提取so文件,提取算法
JADX保存文件到本地,直接将so文件拖入32位IDA7.5版本,其支持arm F5的版本也行。
查看Exports 页面,下面时部分导出函数
1 2 3 4 | Name Address OrdinalJava_net_bluelotus_tomorrow_easyandroid_MainActivity_chec 00000E8C Java_net_bluelotus_tomorrow_easyandroid_MainActivity_stringFromJNI2 00000F18 .... |
双击函数Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec,直接跳转到汇编代码页面,F5就可得到逻辑代码。简单的修改了下第一个参数的数据类型,代码逻辑看着更清晰了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | int __fastcall Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec(JNIEnv *jenv, int a2, int t, int s){ jclass v5; // r7 int result; // r0 int v10[9]; // [sp+1Ch] [bp-24h] BYREF v5 = (*jenv)->FindClass(jenv, "net/bluelotus/tomorrow/easyandroid/MainActivity"); v10[0] = _JNIEnv::GetMethodID(jenv, v5, "check1", "(II)I"); v10[1] = _JNIEnv::GetMethodID(jenv, v5, "check2", "(II)I"); v10[2] = _JNIEnv::GetMethodID(jenv, v5, "check3", "(II)I"); if ( s - 1 <= 0 ) result = t; else result = _JNIEnv::CallIntMethod(jenv, a2, v10[2 * s % 3], t, s - 1);// // 99 * 2 % 3 = 0 // 98 * 2 % 3 = 1 // 97 * 2 % 3 = 2 // 96 * 2 % 3 = 0 return result;} |
可以看到so文件的代码又去调用了UI层的逻辑。
还原代码
结合MainActivity中的代码和so中的代码,整理chec函数逻辑如下
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 | int chec(int t, int s);int check1(int input, int s){ int t = input; for (int i = 1; i < 100; i++) { t += i; } return chec(t, s);}int check2(int input, int s){ int t = input; if (s % 2 == 0) { for (int i = 1; i < 1000; i++) { t += i; } return chec(t, s); } for (int i2 = 1; i2 < 1000; i2++) { t -= i2; } return chec(t, s);}int check3(int input, int s){ int t = input; for (int i = 1; i < 10000; i++) { t += i; } return chec(t, s);}typedef int (*pfn_check)(int, int);pfn_check check_fns[3] = { check1, check2, check3};int chec(int t, int s){ if (s - 1 <= 0) { /* code */ return t; } else { return check_fns[2 * s % 3](t, s - 1); }} |
发现规律
既然是个int值,从0开始穷举就可以了。但是这绝对不是唯一办法。
先看看前0-99的输出吧!
1 2 3 4 5 | for ( i = 0; i < 100; i++) { /* code */ printf("[%08d] chec(%d,99) = %d \n",i,i,chec(i,99)); } |
结果
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 | [00000000] chec(0,99) = 1599503850[00000001] chec(1,99) = 1599503851[00000002] chec(2,99) = 1599503852[00000003] chec(3,99) = 1599503853[00000004] chec(4,99) = 1599503854[00000005] chec(5,99) = 1599503855[00000006] chec(6,99) = 1599503856[00000007] chec(7,99) = 1599503857[00000008] chec(8,99) = 1599503858[00000009] chec(9,99) = 1599503859[00000010] chec(10,99) = 1599503860[00000011] chec(11,99) = 1599503861[00000012] chec(12,99) = 1599503862[00000013] chec(13,99) = 1599503863[00000014] chec(14,99) = 1599503864[00000015] chec(15,99) = 1599503865[00000016] chec(16,99) = 1599503866[00000017] chec(17,99) = 1599503867[00000018] chec(18,99) = 1599503868[00000019] chec(19,99) = 1599503869[00000020] chec(20,99) = 1599503870[00000021] chec(21,99) = 1599503871[00000022] chec(22,99) = 1599503872[00000023] chec(23,99) = 1599503873[00000024] chec(24,99) = 1599503874..... |
2分法
可以看出是结果是线性的。首先我们会想到2分法。
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 | unsigned int low, high, value, cur, t,v,i;value = 1835996258;low = 0;high = 0xffffffff;cur = low + (high - low)/2;printf("value = %08x \n",value);i = 0;while (1){ v = chec(cur, 99) ; printf("[%08d] cur = %08x v = %08x low = %08x high = %08x \n", i, cur,v,low,high); i++; if (v > value) { high = cur; cur = low + (high - low) / 2; continue; } if (v < value) { low = cur; cur = low + (high - low) / 2; continue; } //printf("chec(low,99) = %d \n", chec(low, 99)); //printf("chec(high,99) = %d \n", chec(high, 99)); printf("value = %d \n", value); printf("chec(%d,99) = %d \n", cur,chec(cur,99)); flag(cur); break;} |
2分法计算如下
1 2 3 4 5 6 7 8 9 10 11 | [00000023] cur = 0e1896ff v = 6d6f14e9 low = 0e1895ff high = 0e1897ff[00000024] cur = 0e18967f v = 6d6f1469 low = 0e1895ff high = 0e1896ff[00000025] cur = 0e18963f v = 6d6f1429 low = 0e1895ff high = 0e18967f[00000026] cur = 0e18965f v = 6d6f1449 low = 0e18963f high = 0e18967f[00000027] cur = 0e18966f v = 6d6f1459 low = 0e18965f high = 0e18967f[00000028] cur = 0e189677 v = 6d6f1461 low = 0e18966f high = 0e18967f[00000029] cur = 0e18967b v = 6d6f1465 low = 0e189677 high = 0e18967f[00000030] cur = 0e189679 v = 6d6f1463 low = 0e189677 high = 0e18967b[00000031] cur = 0e189678 v = 6d6f1462 low = 0e189677 high = 0e189679value = 1835996258chec(236492408,99) = 1835996258 |
找规律
通过仔细观察也可以看出i增长1,计算结果也增长1。这样可以直接算出想要的i值。
1 2 | i = 1835996258 - chec(0,99);printf("%d \n",i); |
计算结果。
1 | 236492408 |
计算flag
找到so中函数stringFromJNI2,方法同上
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 | jstring __fastcall Java_net_bluelotus_tomorrow_easyandroid_MainActivity_stringFromJNI2(JNIEnv *a1, int a2, int a3){ int v3; // r1 JNIEnv v4; // r2 char v6; // [sp+8h] [bp-50h] char v7; // [sp+Ch] [bp-4Ch] char v8; // [sp+10h] [bp-48h] int v9; // [sp+24h] [bp-34h] char str[12]; // [sp+28h] [bp-30h] BYREF v8 = a3 / 1000 % 10; v9 = a3 / 10000 % 10; v6 = a3 % 10; str[0] = v8 + v6 * v9; v7 = a3 / 1000000 % 10; v3 = a3 / 100 % 10; str[1] = v9 * v7 + 10 * v3 + 3; str[2] = 10 * (v8 + v9); str[3] = v9 * v7; str[4] = 19 * (a3 / 100000 % 10) + 2; str[5] = v6 * v7 + 1; str[6] = v6 * v7; str[7] = 12 * v3; str[9] = 12 * v3 + 3; str[8] = str[2] + v8; v4 = *a1; str[10] = 2 * (v9 * v7 + 10 * v3 - 37); str[11] = 0; return v4->NewStringUTF(a1, str);} |
剔除无用参数得到c语言逻辑
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 | void flag(int a3){ int v3; // r1 char v6; // [sp+8h] [bp-50h] char v7; // [sp+Ch] [bp-4Ch] char v8; // [sp+10h] [bp-48h] int v9; // [sp+24h] [bp-34h] char str[12]; // [sp+28h] [bp-30h] BYREF v8 = a3 / 1000 % 10; v9 = a3 / 10000 % 10; v6 = a3 % 10; str[0] = v8 + v6 * v9; v7 = a3 / 1000000 % 10; v3 = a3 / 100 % 10; str[1] = v9 * v7 + 10 * v3 + 3; str[2] = 10 * (v8 + v9); str[3] = v9 * v7; str[4] = 19 * (a3 / 100000 % 10) + 2; str[5] = v6 * v7 + 1; str[6] = v6 * v7; str[7] = 12 * v3; str[9] = 12 * v3 + 3; str[8] = str[2] + v8; str[10] = 2 * (v9 * v7 + 10 * v3 - 37); str[11] = 0; printf("flag:alictf{%s}", str);} |
最后得到flag如下
1 2 | 236492408flag:alictf{Jan6N100p3r} |
