Reverse(六)

signin

https://ctf.bugku.com/challenges/detail/id/136.html

这是一道安卓逆向题目,之前接触了一点点安卓逆向,但工具很不好用,用的手机模拟器里的MT管理器和NP管理器(都是操作安装包的工具,功能有些复杂还付费,感觉主要是用来破解挺不错的)

首先就是试下LOGIN看有什么反应,抓住关键词Try again去搜索找到在classes.dex(一般都是在这里,Java逻辑代码)

image-20231118191135870

定位后在MainActivity里,还原smali为Java:

image-20231118191357416

代码如下:关键在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 点开结构层次即可看到主要类

image-20231118193622259.png

  • 在某一类上按q即可反编译为java代码

去找前面出现的那串数字2131427360,定位如下(一个资源管理分配的id,id由R来管),toString是一个字符串,在string.xml中(values)

image-20231118193917030.png

image-20231118194502500.png

复制,逆转,解码得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文件夹,进去发现如下

image-20231130203012864

wp告诉我去看123,里面有一串base64加密字符,后面告诉这是你的flag,解密得flag {my_name_is_shumu}

image-20231130203153070.png

Timer(阿里CTF)

https://ctf.bugku.com/challenges/detail/id/117.html

看图和代码意思是倒计时完才给你看flag(右图为jeb2逆向查看mainactivity)

image-20231130203914915image-20231130204154302

image-20231130204717992.png

关键是需要知道k值才能显示flag,timer为200000,每次now+1直到beg-now<0才显示答案,其中不能删除if来绕过因为k值需要在200000的过程中计算出来,因此需要求k(is2函数计算)

image-20231130212025432.png

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);
    }
}

image-20231130213321456.png

安卓模拟器MT管理器反编译(操作有点麻烦)

得到k值为1616384,再去安卓模拟器的mt管理器编辑代码为该值(传入k前赋个值),注意定位到带dolar的那个源文件,里面才有run函数

image-20231130222443679.png

定位flag前面的if,删除它 !

image-20231130222700816

看不太清,但有答案了 alictf{Y0vAr3TimerMa3te7}

image-20231130222942338

Androidkiller反编译(apktool老是反编译失败)

然后还要去除那个if条件判断直接显示,定位flag前的if,直接删除或者改成if-lez

image-20231130214648663

image-20231130214924947

如果编译失败的话:https://www.52pojie.cn/thread-887543-1-1.html,https://blog.csdn.net/pla12147111/article/details/95135104(删除1.apk),和apktool版本有关系

最后我的解决方法是换成apktool2.9(一开始就要这样用,不然前后编译不一致报错),编译完成后最底下有新的apk路径

image-20231201110140666

模拟器打开即可看到flag

mobile1(gctf)

https://ctf.bugku.com/challenges/detail/id/137.html

首先jeb2查看mainactivity反编译的java主函数代码:结合

image-20231201131839587

结合app具体界面报错“错误”关键词去搜索strings.xml,

image-20231201131938006

搜到name,再去搜R类下的id,id16进制转10进制值为2131099678,定位代码可以看出关键逻辑代码为onclick里的if判断

image-20231201132023554

关注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的核心函数

image-20231201160417218

双击查看encode函数

image-20231201160448106

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我都不知道这么多字符串咋找的

image-20231201162144151

image-20231201162430960

NoString

https://ctf.bugku.com/challenges/detail/id/229.html

打开测试发现如下:

image-20231201163218675

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解下壳

image-20231201165012136

image-20231201165007417

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分析函数

image-20231201184109781

ida定位并跳转伪代码

image-20231201185013114

感觉我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代码分析
  • 最后理解了这些,还要写代码逆向回去,代码中如果出现多个循环,很容易搞错开始和结束的条件

此外还看到题目下面一个评论,思路好强!不用写代码,让他自己计算,得到差值即可(我没有去验证)

image-20231201203431150.png

最后修改:2023 年 12 月 14 日
如果觉得我的文章对你有用,请随意赞赏