Reverse(六)
signin
https://ctf.bugku.com/challenges/detail/id/136.html
这是一道安卓逆向题目,之前接触了一点点安卓逆向,但工具很不好用,用的手机模拟器里的MT管理器和NP管理器(都是操作安装包的工具,功能有些复杂还付费,感觉主要是用来破解挺不错的)
首先就是试下LOGIN看有什么反应,抓住关键词Try again去搜索找到在classes.dex(一般都是在这里,Java逻辑代码)
定位后在MainActivity里,还原smali为Java:
代码如下:关键在checkPassword函数里,先getFlag(),再反转,在base64解码输入;其中getFlag()的getBaseContext().getString(2131427360)这个不好确定
//
// Decompiled by Jadx (from NP Manager)
//
package re.sdnisc2018.sdnisc_apk1;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Base64;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
public void checkPassword(String str) {
if (str.equals(new String(Base64.decode(new StringBuffer(getFlag()).reverse().toString(), 0)))) {
showMsgToast("Congratulations !");
} else {
showMsgToast("Try again.");
}
}
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(2131296283);
((Button) findViewById(2131165261)).setOnClickListener(new 1(this));
}
private String getFlag() {
return getBaseContext().getString(2131427360);
}
private void showMsgToast(String str) {
Toast.makeText(this, str, 1).show();
}
}
试图搜索数字搜不到,大概能猜到跟string有些关系,应该是存储在哪里了。看了别人的wp,要用jeb(1. https://down.52pojie.cn/Tools/Android_Tools/ 2. 安装jdk1.8.0 121,https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html)
具体使用方法:
- 运行bat文件,打开apk文件
- 点击bytecode 点开结构层次即可看到主要类
- 在某一类上按q即可反编译为java代码
去找前面出现的那串数字2131427360,定位如下(一个资源管理分配的id,id由R来管),toString是一个字符串,在string.xml中(values)
复制,逆转,解码得flag {Her3_i5_y0ur_f1ag_39fbc_}
树木的小秘密
https://ctf.bugku.com/challenges/detail/id/355.html
本题考察的是pyinstaller的逆向(一个打包python程序为exe的程序)
逆向的代码:https://sourceforge.net/projects/pyinstallerextractor/,py文件
方法:python .pyinstxtractor.py easy_reverse.exe,会在当前目录下生成easy_reverse.exe_extracted文件夹,进去发现如下
wp告诉我去看123,里面有一串base64加密字符,后面告诉这是你的flag,解密得flag {my_name_is_shumu}
Timer(阿里CTF)
https://ctf.bugku.com/challenges/detail/id/117.html
看图和代码意思是倒计时完才给你看flag(右图为jeb2逆向查看mainactivity)
关键是需要知道k值才能显示flag,timer为200000,每次now+1直到beg-now<0才显示答案,其中不能删除if来绕过因为k值需要在200000的过程中计算出来,因此需要求k(is2函数计算)
java代码模拟跑一下
public class timer {
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;
}
}
// return false;
}
v1 = false;
}
else if(arg4 <= 1) {
v1 = false;
}
return v1;
}
public static void main(String[] args) {
int beg = 200000, k = 0;
while (beg>0) {
if (is2(beg))
k += 100;
else
k--;
beg--;
}
System.out.println(k);
}
}
安卓模拟器MT管理器反编译(操作有点麻烦)
得到k值为1616384,再去安卓模拟器的mt管理器编辑代码为该值(传入k前赋个值),注意定位到带dolar的那个源文件,里面才有run函数
定位flag前面的if,删除它 !
看不太清,但有答案了 alictf{Y0vAr3TimerMa3te7}
Androidkiller反编译(apktool老是反编译失败)
然后还要去除那个if条件判断直接显示,定位flag前的if,直接删除或者改成if-lez
如果编译失败的话:https://www.52pojie.cn/thread-887543-1-1.html,https://blog.csdn.net/pla12147111/article/details/95135104(删除1.apk),和apktool版本有关系
最后我的解决方法是换成apktool2.9(一开始就要这样用,不然前后编译不一致报错),编译完成后最底下有新的apk路径
模拟器打开即可看到flag
mobile1(gctf)
https://ctf.bugku.com/challenges/detail/id/137.html
首先jeb2查看mainactivity反编译的java主函数代码:结合
结合app具体界面报错“错误”关键词去搜索strings.xml,
搜到name,再去搜R类下的id,id16进制转10进制值为2131099678,定位代码可以看出关键逻辑代码为onclick里的if判断
关注checkSN函数,第一个参数为用户名Tenshine,第二个为输入的密码,关键是返回True
private boolean checkSN(String arg11, String arg12) {
boolean v7 = false;
if(arg11 != null) {
try {
if(arg11.length() == 0) {
return v7;
}
if(arg12 == null) {
return v7;
}
if(arg12.length() != 22) {
return v7; // 密码长22位
}
MessageDigest v1 = MessageDigest.getInstance("MD5");
v1.reset();
v1.update(arg11.getBytes()); // md5加密用户名
String v3 = MainActivity.toHexString(v1.digest(), ""); // 转为16进制
StringBuilder v5 = new StringBuilder();
int v4;
for(v4 = 0; v4 < v3.length(); v4 += 2) {
v5.append(v3.charAt(v4)); // 每两个取一次字符
}
if(!"flag{" + v5.toString() + "}".equalsIgnoreCase(arg12)) {
return v7;
}
}
catch(NoSuchAlgorithmException v2) {
goto label_40;
}
v7 = true;
}
return v7;
label_40:
v2.printStackTrace();
return v7;
}
写个python脚本解密
import hashlib
md5_object = hashlib.md5()
md5_object.update(b'Tenshine')
md5_result = md5_object.hexdigest()
print(md5_result)
for i in range(0, len(md5_result), 2):
print(md5_result[i], end="")
结果为bc72f242a6af3857,带上即为flag{bc72f242a6af3857}
First_Mobile(xman)
https://ctf.bugku.com/challenges/detail/id/139.html
过于简单,一个脚本即可算出值。jeb查看OnCreate的核心函数
双击查看encode函数
python脚本求解:得到LOHILMNMLKHILKHI
b = [23, 22, 26, 26, 25, 25, 25, 26, 27, 28, 30, 30, 29, 30, 32, 32]
for i in range(16):
for j in range(128):
if (j + b[i]) % 61 * 2 - i == j:
print(chr(j), end="")
马老师杀毒卫士
https://ctf.bugku.com/challenges/detail/id/145.html?id=145&page=2
栅栏密码加密,在strings里的,不看wp我都不知道这么多字符串咋找的
NoString
https://ctf.bugku.com/challenges/detail/id/229.html
打开测试发现如下:
ida查找字符串找不到,估计混淆了,和题目有些相呼应,搜main伪代码,发现如下,基本可以猜到error变体为了l{{f{,逻辑代码就是这个
int wmain()
{
signed int v0; // ecx
signed int i; // eax
signed int v2; // ecx
signed int j; // eax
int k; // eax
int v5; // eax
signed int v6; // ecx
signed int m; // eax
signed int v8; // ecx
signed int n; // eax
char v11; // [esp+0h] [ebp-18h] BYREF
__int128 v12; // [esp+1h] [ebp-17h]
__int16 v13; // [esp+11h] [ebp-7h]
v0 = strlen(Format);
for ( i = 0; i < v0; ++i )
Format[i] ^= 9u;
printf("yelhzl)`gy|})|)oehnl3");
v11 = 0;
v13 = 0;
v12 = 0i64;
v2 = strlen(a80z);
for ( j = 0; j < v2; ++j )
a80z[j] ^= 9u;
scanf(a80z, &v11); // 赋值给v11
for ( k = 0; k < 19; ++k )
*(&v11 + k) ^= 9u; // v11加密
v5 = strcmp(&v11, aOehnl3rHfCcgpt); // 和这个变量比较,双击查看值为oehnl3r=<?=hF@CCGPt,可以利用异或特点求得v11
if ( v5 )
v5 = v5 < 0 ? -1 : 1;
if ( v5 )
{
v6 = strlen(aLF);
for ( m = 0; m < v6; ++m )
aLF[m] ^= 9u;
printf("l{{f{"); // error的混淆
}
else
{
v8 = strlen(aNa);
for ( n = 0; n < v8; ++n )
aNa[n] ^= 9u;
printf("{`na}");
}
printf("\r\n");
system("pause"); // 最后退出
return 0;
}
python脚本如下:得到flage:{4564aOIJJNY}
s = "oehnl3r=<?=hF@CCGPt"
for i in range(len(s)):
print(chr(ord(s[i])^9), end="")
ez fibon
https://ctf.bugku.com/challenges/detail/id/280.html
exeinfo发现有壳,用upd解下壳
ida定位具体代码,分析后发现其实是做了个混淆,实际功能很简单
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v3; // edx
int v5[24]; // [rsp+20h] [rbp-60h]
char Str[524]; // [rsp+80h] [rbp+0h] BYREF
int j; // [rsp+28Ch] [rbp+20Ch]
int v8; // [rsp+290h] [rbp+210h]
int v9; // [rsp+294h] [rbp+214h]
int i; // [rsp+298h] [rbp+218h]
int v11; // [rsp+29Ch] [rbp+21Ch]
_main();
v11 = 1;
puts("please input your flag:");
gets(Str);
for ( i = 0; i <= 21; ++i )
*(_DWORD *)&Str[4 * i + 112] = Str[i];
if ( strlen(Str) == 22 ) // 长度必须为22
{ // 斐波那契数列 v8、v9
v9 = 1;
v8 = 1;
for ( j = 0; j <= 21; ++j )
{
if ( (j & 1) != 0 ) // j为奇数
{
v8 += v9;
v3 = (v8 + j + *(_DWORD *)&Str[4 * j + 112]) % 64 + 64;
}
else // j为偶数
{
v9 += v8;
v3 = (v9 + j + *(_DWORD *)&Str[4 * j + 112]) % 64 + 64;
}
*(_DWORD *)&Str[4 * j + 112] = v3;
}
v5[0] = 100;
v5[1] = 121;
v5[2] = 110;
v5[3] = 118;
v5[4] = 70;
v5[5] = 85;
v5[6] = 123;
v5[7] = 109;
v5[8] = 64;
v5[9] = 94;
v5[10] = 109;
v5[11] = 99;
v5[12] = 116;
v5[13] = 81;
v5[14] = 109;
v5[15] = 86;
v5[16] = 83;
v5[17] = 126;
v5[18] = 119;
v5[19] = 101;
v5[20] = 110;
v5[21] = 114;
for ( j = 0; j <= 21; ++j )
{
if ( v5[j] != *(_DWORD *)&Str[4 * j + 112] )
v11 = 0;
}
if ( !v11 )
printf("wrong!");
if ( v11 == 1 )
printf("right flag!");
}
else
{
printf("wrong lenth!");
}
return 0;
}
python脚本解密:得到bugku{So_Ez_Fibon@cci}
fib = [2, 3]
k = 2
while len(fib) <= 22:
fib.append(fib[k-1]+fib[k-2])
k += 1
s = [100, 121, 110, 118, 70, 85, 123, 109, 64, 94, 109, 99, 116, 81, 109, 86, 83, 126, 119, 101, 110, 114]
for i in range(len(s)):
print(chr((s[i]-i-fib[i]+64)%64+64), end="")
LoopAndLoop(阿里CTF)
https://ctf.bugku.com/challenges/detail/id/120.html
jeb查看MainAcitivity,关键函数为check
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("lhm");
}
public MainActivity() {
super();
}
public native int chec(int arg1, int arg2) {
}
public int check(int arg2, int arg3) {
return this.chec(arg2, arg3);
}
public int check1(int arg4, int arg5) {
int v1 = arg4;
int v0;
for(v0 = 1; v0 < 100; ++v0) {
v1 += v0;
}
return this.chec(v1, arg5);
}
public int check2(int arg5, int arg6) {
int v2;
int v3 = 1000;
int v1 = arg5;
if(arg6 % 2 == 0) {
int v0;
for(v0 = 1; v0 < v3; ++v0) {
v1 += v0;
}
v2 = this.chec(v1, arg6);
}
else {
for(v0 = 1; v0 < v3; ++v0) {
v1 -= v0;
}
v2 = this.chec(v1, arg6);
}
return v2; // 这里是唯一返回不是函数而是值,照应最后一个1是模3为1
}
public int check3(int arg4, int arg5) {
int v1 = arg4;
int v0;
for(v0 = 1; v0 < 10000; ++v0) {
v1 += v0;
}
return this.chec(v1, arg5);
}
public String messageMe(String arg3) {
return "LoopOk" + arg3;
}
protected void onCreate(Bundle arg6) {
super.onCreate(arg6);
this.setContentView(2130968600);
this.findViewById(2131492946).setOnClickListener(new View$OnClickListener(this.findViewById(2131492944), this.findViewById(2131492945), this.findViewById(2131492947)) {
public void onClick(View arg7) {
int v1;
String v2 = this.val$ed.getText().toString();
try {
v1 = Integer.parseInt(v2); // 要求必须是整型数字
}
catch(NumberFormatException v0) {
this.val$tv1.setText("Not a Valid Integer number");
return;
}
if(MainActivity.this.check(v1, 99) == 1835996258) { // 关键点,v1是输入的数字
this.val$tv1.setText("The flag is:");
this.val$tv2.setText("alictf{" + MainActivity.this.stringFromJNI2(v1) + "}");
}
else {
this.val$tv1.setText("Not Right!");
}
}
});
}
public boolean onCreateOptionsMenu(Menu arg3) {
this.getMenuInflater().inflate(2131558400, arg3);
return 1;
}
public boolean onOptionsItemSelected(MenuItem arg3) {
boolean v1 = arg3.getItemId() == 2131492961 ? true : super.onOptionsItemSelected(arg3);
return v1;
}
public native String stringFromJNI2(int arg1) {
}
}
这题难点在于代码都在native层(c代码),无法直接看到,要用提取出来用ida分析函数
ida定位并跳转伪代码
感觉我java/c++学得好烂啊,这些都没学过,和JNI有关(Java Native Interface)
int __fastcall Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec(int a1, int a2, int a3, int a4)
{
int v5; // r7
int v10[9]; // [sp+1Ch] [bp-24h] BYREF
v5 = (*(int (__fastcall **)(int, const char *))(*(_DWORD *)a1 + 24))(
a1,
"net/bluelotus/tomorrow/easyandroid/MainActivity");
v10[0] = _JNIEnv::GetMethodID(a1, v5, "check1", "(II)I"); // 三种调用方法,方法在MainActivity里
v10[1] = _JNIEnv::GetMethodID(a1, v5, "check2", "(II)I"); // 第三个表示java里的函数名,第四个括号里表示两个INT参数,括号外表示返回类型参数
v10[2] = _JNIEnv::GetMethodID(a1, v5, "check3", "(II)I"); // 前两个参数实在晕,但可以确定的是和调用的函数参数无关
if ( a4 - 1 <= 0 )
return a3;
else
return _JNIEnv::CallIntMethod(a1, a2, v10[2 * a4 % 3], a3, a4 - 1); // v10里的是具体选哪个方法
} // a3是第一个参数,a4-1是第二个参数,由此可见每次第二个参数都要-1
具体的逻辑就是,初始check下的chec的a4为99,选择了check1,check1里又去做了一次chec,这时a4为98(在c代码中减了1),然后直到a4-1<=0即a4=0为止
需要做的是逆向java里的三个check函数,使得最终值为1835996258,因此逆向初始值为1835996258
def check1(a1, a2):
for i in range(1, 100):
a1 -= i
return a1
def check2(a1, a2):
if a2 % 2 == 0:
for i in range(1, 1000):
a1 -= i
else:
for i in range(1, 1000):
a1 += i
return a1
def check3(a1, a2):
for i in range(1, 10000):
a1 -= i
return a1
if __name__ == '__main__':
k = 1835996258
for i in range(2, 100):
if i * 2 % 3 == 0:
k = check1(k, i-1)
elif i * 2 % 3 == 1:
k = check2(k, i-1)
else:
k = check3(k, i-1)
print(k)
运行脚本得到236492408,app运行后输入即可得到flag
小结
本题是逆向到现在感觉最复杂的题目,难点不在于写脚本啥的,而是真正去理解逆向的思维,举例来说
- 首先要懂得反编译(jeb)查找主要Java逻辑代码,并基本读懂
- 接着要理解native(JNI)的原理,提取库文件,用ida分析
- 到了ida还要懂得定位调用的地方,结合java代码分析
- 最后理解了这些,还要写代码逆向回去,代码中如果出现多个循环,很容易搞错开始和结束的条件
此外还看到题目下面一个评论,思路好强!不用写代码,让他自己计算,得到差值即可(我没有去验证)