题目链接: LoopAndLoop(阿里CTF) - Bugku CTF
反编译
Java
// Target
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");
}
// Function chec is a jni function
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
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
t += i;
}
return chec(t, s);
}
for (int i2 = 1; i2
t -= i2;
}
return chec(t, s);
}
public int check3(int input, int s) {
int t = input;
for (int i = 1; i
t += i;
}
return chec(t, s);
}
C
Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec
(int *param_1,_jmethodID *param_2,undefined4 param_3,int param_4)
{
char *pcVar1;
int extraout_r1;
undefined4 local_24[4];
pcVar1 = (char *)(**(code **)(*param_1 + 0x18))
(param_1,"net/bluelotus/tomorrow/easyandroid/MainActivity");
local_24[0] = _JNIEnv::GetMethodID((_jclass *)param_1,pcVar1,"check1");
local_24[1] = _JNIEnv::GetMethodID((_jclass *)param_1,pcVar1,"check2");
local_24[2] = _JNIEnv::GetMethodID((_jclass *)param_1,pcVar1,"check3");
if (0
__aeabi_idivmod(param_4
param_3 = _JNIEnv::CallIntMethod
((_jobject *)param_1,param_2,local_24[extraout_r1],param_3,param_4 + -1);
}
return param_3;
}
Jni for ARM
官方文档中函数GetMethodID
和CallIntMethod
的签名如下:
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
//
NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
至于那个pcVar1
, 根据字符串的字面量猜测, 可能是获取MainClass
对象的函数, 在文档中查找相关函数, 可以发现函数FindClass
大概与之对应:
jclass FindClass(JNIEnv *env, const char *name);
按照上述函数签名, 对反汇编的C代码稍加修饰. 可以得到如下的伪代码:
Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec
(int *jenv,_jmethodID *thiz,undefined4 param_3,int param_4)
{
char *clazz;
int extraout_r1;
undefined4 local_24 [4];
clazz = (char *)(**(code **)(*jenv + 0x18))
(jenv,"net/bluelotus/tomorrow/easyandroid/MainActivity");
local_24[0] = _JNIEnv::GetMethodID((_jclass *)jenv,clazz,"check1");
local_24[1] = _JNIEnv::GetMethodID((_jclass *)jenv,clazz,"check2");
local_24[2] = _JNIEnv::GetMethodID((_jclass *)jenv,clazz,"check3");
if (0 < param_4 + -1) {
__aeabi_idivmod(param_4 << 1,3);
param_3 = _JNIEnv::CallIntMethod
((_jobject *)jenv,thiz,local_24[extraout_r1],param_3,param_4 + -1);
}
return param_3;
}
似乎Ghidra在没有安装插件的情况下, 对ARM32的汇编支持不是很好, 重命名符号会把汇编中寄存器的名称给改掉. 也不知道是哪里做错了.
剩下的工作是分析__aeabi_idivmod
和变量extraout_r1
的赋值逻辑.
ARM汇编码简记
ldr r6,[sp,#local_30 ] ;load_register: r6 = memory[sp + local_30]
str r0,[r5,#local_1c ] ;store_register: memory[r5 + local_1c] = r0
subs r6,#0x1 ; r6 = r6 - 1
cmp r6,#0x0 ; compare r6 with 0
ble LAB_00010efe ; if (r6 less than 0)
ldr r3,[sp,#local_30 ] ;load_register: xxxxxxx
movs r1,#0x3 ; r1 = 3
lsls r0,r3,#0x1 ; r0 = r3 << 1
bl __aeabi_idivmod ;call: __aeabi_idivmod
lsls r1,r1,#0x2 ;r1 is the remainder(余数)
ldr r2,[r1,r5] ;load_register: r2 = memory[r1 + r5](r5保存着methods id数组)
adds r0,r4,#0x0 ; r0 = r4 (r4 一直保存着jenv)
str r6,[sp,#0x0 ]=>local_40 ;store_register: xxxxxx
ldr r1,[sp,#local_2c ] ; (local_2c保存着thiz指针)
ldr r3,[sp,#local_34 ] ;load_register: xxxxxx
bl _JNIEnv::CallIntMethod ;call _JNIEnv
b LAB_00010f00
ldr r0,[sp,#local_34 ] ; local_34 = _JNIEnv::CallIntMethod(jenv, )
add sp,#0x2c
pop {r4,r5,r6,r7,pc}
查阅相关文档, 函数__aeabi_idivmod
是求商取余的函数, r0
保存商, r1
保存余数. 变量extraout_r1
就是余数. 根据汇编码还原出函数chec
:
public int chec(int input, int s) {
if (s - 1 > 0) {
int t = (s << 1) % 3;
switch(t) {
case 0:
input = check1(input, s - 1);
break;
case 1:
input = check2(input, s - 1);
break;
case 2:
input = check3(input, s - 1);
break;
}
}
return input;
}
可以分析上述代码, chec
的输出值与输入input
之间只有简单的加减乘除运算, 设chec
为
但是进一步分析s
固定为99, 所以它的表达式为chec(0, 99)
, 1835996258 - chec(0, 99)
即为期望的输入:
找个模拟器运行程序, 得到flag(就不逆向stringFromJNI2
了, 麻烦)