Reverse(九)

数学不及格

https://ctf.show/challenges#%E6%95%B0%E5%AD%A6%E4%B8%8D%E5%8F%8A%E6%A0%BC-121

ida64打开找main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax@16
  __int64 v4; // rsi@16
  signed int v5; // [sp+14h] [bp-4Ch]@4
  char *endptr; // [sp+18h] [bp-48h]@4
  char *v7; // [sp+20h] [bp-40h]@4
  char *v8; // [sp+28h] [bp-38h]@4
  char *v9; // [sp+30h] [bp-30h]@4
  __int64 v10; // [sp+38h] [bp-28h]@4
  __int64 v11; // [sp+40h] [bp-20h]@4
  __int64 v12; // [sp+48h] [bp-18h]@4
  __int64 v13; // [sp+50h] [bp-10h]@4
  __int64 v14; // [sp+58h] [bp-8h]@1

  v14 = *MK_FP(__FS__, 40LL);
  if ( argc != 5 )    // 输入参数必须为5个,包含运行文件名,所以关键看剩下四个
  {
    puts("argc nonono");
    exit(1);
  }
  v5 = (unsigned __int64)strtol(argv[4], &endptr, 16) - 25923;    // 这四个都是16进制字符串
  v10 = f(v5);
  v11 = strtol(argv[1], &v7, 16);
  v12 = strtol(argv[2], &v8, 16);
  v13 = strtol(argv[3], &v9, 16);
  if ( v10 - v11 != 151381742876LL )
  {
    puts("argv1 nonono!");
    exit(1);
  }
  if ( v10 - v12 != 117138004530LL )
  {
    puts("argv2 nonono!");
    exit(1);
  }
  if ( v10 - v13 != 155894355749LL )
  {
    puts("argv3 nonono!");
    exit(1);
  }
  if ( v5 + v13 + v12 + v11 != 1349446086540LL )
  {
    puts("argv sum nonono!");
    exit(1);
  }
  puts("well done!decode your argv!");
  result = 0;
  v4 = *MK_FP(__FS__, 40LL) ^ v14;
  return result;
}

__int64 __fastcall f(signed int a1)
{
  __int64 result; // rax@3
  signed int i; // [sp+1Ch] [bp-14h]@4
  __int64 v3; // [sp+20h] [bp-10h]@4
  _QWORD *ptr; // [sp+28h] [bp-8h]@4

  if ( a1 > 1 && a1 <= 200 )    // 输入范围必须在2-200
  {
    ptr = malloc(8LL * a1);
    *ptr = 1LL;
    ptr[1] = 1LL;
    v3 = 0LL;
    for ( i = 2; i < a1; ++i )
    {
      ptr[i] = ptr[i - 1] + ptr[i - 2];    // 斐波那契数列
      v3 = ptr[i];
    }
    free(ptr);
    result = v3;    // 取最后一个
  }
  else
  {
    result = 0LL;
  }
  return result;
}

已知条件:

  • f(argv[4] - 25923) - argv[1] = 151381742876
  • f(argv[4] - 25923) - argv[2] = 117138004530
  • f(argv[4] - 25923) - argv[3] = 155894355749
  • argv[4] - 25923 + argv[1] + argv[2] + argv[3] = 1349446086540

python求解并转为字符串

f = [1, 1]
for i in range(2, 201):
    f.append(f[i-1] + f[i-2])
print(f)
for i in range(2, 201):
    if f[i] * 3 + i+1 == 151381742876 + 117138004530 + 155894355749 + 1349446086540:    # 这里i要注意
        print('i is', i)
        break
print('Argv[1]: %x' % (f[i] - 151381742876))
print('Argv[2]: %x' % (f[i] - 117138004530))
print('Argv[3]: %x' % (f[i] - 155894355749))
print('Argv[4]: %x' % (i + 1 + 25923))
flag = ''
flag += bytes.fromhex(str(hex(f[i] - 151381742876)[2:])).decode('utf-8')
flag += bytes.fromhex(str(hex(f[i] - 117138004530)[2:])).decode('utf-8')
flag += bytes.fromhex(str(hex(f[i] - 155894355749)[2:])).decode('utf-8')
flag += bytes.fromhex(str(hex(i + 1 + 25923)[2:])).decode('utf-8')
print(flag)

image-20231214114755121.png

peter的手机

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

这题血亏,不仅花金币买了文件,还买了wp,最后才发现ida6.8、ida8打开进不去函数,看不了伪代码,各种问题,但是ida7.0就可以::cry::

首先解压ipa文件,找到同名64位可执行文件(FirstOS),定位特殊字符串1997019247Acf,查看伪代码

void __cdecl -[ViewController btn](ViewController *self, SEL a2)
{
  UITextField *v2; // x0
  void *v3; // x0
  void *v4; // ST10_8
  void *v5; // x0
  UILabel *v6; // x0
  void *v7; // ST08_8
  UILabel *v8; // x0
  void *v9; // x0
  __int64 v10; // ST00_8
  void *v11; // [xsp+18h] [xbp-18h]
  SEL v12; // [xsp+20h] [xbp-10h]
  ViewController *v13; // [xsp+28h] [xbp-8h]

  v13 = self;
  v12 = a2;
  NSLog(CFSTR("btn click do"));
  v2 = -[ViewController edtInput](v13, "edtInput");
  v3 = (void *)objc_retainAutoreleasedReturnValue(v2);
  v4 = v3;
  v5 = objc_msgSend(v3, "text");
  v11 = (void *)objc_retainAutoreleasedReturnValue(v5);
  objc_release(v4);
  if ( (unsigned __int64)objc_msgSend(v11, "isEqualToString:", CFSTR("1997019247Acf")) & 1 )
  {
    v6 = -[ViewController EndValue](v13, "EndValue");
    v7 = (void *)objc_retainAutoreleasedReturnValue(v6);
    objc_msgSend(v7, "setText:", CFSTR("right"));
    objc_release(v7);
  }
  else
  {
    v8 = -[ViewController EndValue](v13, "EndValue");
    v9 = (void *)objc_retainAutoreleasedReturnValue(v8);
    objc_msgSend(v9, "setText:", CFSTR("error"), v9);
    objc_release(v10);
  }
  objc_storeStrong(&v11, 0LL);
}

可以看出来大概就是个比较字符串的功能,提交就过了。。。。

这道题是ios文件,简单不涉及算法,直接找字符串,要是难点的就不好做了

easyeasy-200

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

public class MainActivity extends AppCompatActivity {
    String this_is_your_flag;

    static {
        System.loadLibrary("easyeasy");
    }

    public MainActivity() {
        super();
    }

    protected void onCreate(Bundle arg4) {
        ApplicationInfo v1 = this.getApplicationInfo();
        int v2 = v1.flags & 2;
        v1.flags = v2;
        if(v2 != 0) {
            Process.killProcess(Process.myPid());
        }

        super.onCreate(arg4);
        this.setContentView(2130968602);
        this.findViewById(2131427413).setOnClickListener(new View$OnClickListener() {
            public void onClick(View arg8) {
                MainActivity.this.this_is_your_flag = MainActivity.this.findViewById(2131427414).getText().toString();    // 定位赋值
                if(MainActivity.this.this_is_your_flag.length() < 35) {
                    Process.killProcess(Process.myPid());
                }
                else if(MainActivity.this.this_is_your_flag.length() > 39) {
                    Process.killProcess(Process.myPid());
                }
                // flag长度35-39
                MainActivity.this.this_is_your_flag = new Format().form(MainActivity.this.this_is_your_flag);    // 双击查看发现作用是substring(5,38),说明长度38或39
                if(MainActivity.this.this_is_your_flag.length() < 32) {
                    Toast.makeText(MainActivity.this.getApplicationContext(), "No,more.", 1).show();
                }
                else if(new Check().check(MainActivity.this.this_is_your_flag)) {    // 核心判断函数
                    Toast.makeText(MainActivity.this.getApplicationContext(), "Congratulations!You got it.", 1).show();
                }
                else {
                    Toast.makeText(MainActivity.this.getApplicationContext(), "Oh no.Come on!", 1).show();
                }
            }
        });
    }
}

判断函数,经过分析发现这个代码会检查是否用了模拟器,以及调用lib文件里进行判断

public class Check {
    String emulator;
    private static String[] known_pipes;

    static {
        Check.known_pipes = new String[]{"/dev/socket/qemud", "/dev/qemu_pipe"};
    }

    public Check() {
        super();
        this.emulator = Check.checkPipes();
    }

    boolean check(String arg2) {
        boolean v0 = this.checkEmulator(this.emulator) ? false : this.checkPasswd(arg2);
        return v0;
    }

    protected native boolean checkEmulator(String arg1) {
    }

    private native boolean checkPasswd(String arg1) {
    }

    public static String checkPipes() {
        String v3;
        int v0 = 0;
        while(true) {
            if(v0 >= Check.known_pipes.length) {
                return "false";
            }
            else if(new File(Check.known_pipes[v0]).exists()) {
                v3 = "true";
            }
            else {
                ++v0;
                continue;
            }
            return v3;
        }
        return "false";
    }
}

ida64打开so文件(看其他wp说得armeabi-v7a下的so,不清楚具体原因,ida对比查看了下感觉应该是这个反编译的结果最简单?),直接搜索check可以找到对应的函数

bool __fastcall Java_com_example_ring_wantashell_Check_checkPasswd(int a1, int a2, int a3)
{
  int v5; // r4
  const char *v6; // r6
  char *v7; // r4
  size_t v8; // r0
  size_t i; // r1
  char v10; // r2
  size_t v11; // r0
  char *v12; // r4
  char *v14; // [sp+0h] [bp-1Ch] BYREF
  unsigned int v15; // [sp+8h] [bp-14h] BYREF

  v5 = 0;
  v6 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
  if ( v6 )
  {
    v7 = (char *)operator new[](0x21u);    // 开辟v7空间
    strcpy(v7, v6);    // v6赋给v7,猜测v6位我们输入的passwd
    sub_8F7C((int)&v15, (char *)&unk_18843);    // X查看发现被多次调用,猜测是系统调用函数,和加密无关
    v8 = strlen(v7) - 1;
    if ( v8 )
    {
      for ( i = 0; i < v8; ++i )    // 可以看出来做了个倒置字符串操作
      {
        v10 = v7[i];
        v7[i] = v7[v8];
        v7[v8--] = v10;
      }
    }
    v11 = strlen(v7);
    sub_6ED0(&v15, v7, v11);
    encrypt((const char *)&v14, v15);    // 很明显加密函数,猜测上面的函数将v7赋值给了v15,在这里进行加密,加密后的存到v14
    v12 = v14;
    sub_69A4(&v14);
    sub_69A4(&v15);
    (*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)a1 + 680))(a1, a3, v6);
    return sub_7834((int)&secret, v12) == 0;
  }
  return v5;
}

encrypt加密函数如下

// attributes: thunk
int __fastcall encrypt(const char *a1, unsigned int a2)
{
  return _Z7encryptPKcj(a1, a2);
}

int __fastcall encrypt(const char *a1, unsigned int a2, int a3)
{
  int v5; // r10
  int v6; // r9
  char v7; // t1
  int v8; // r5
  int v9; // r0
  int v10; // r0
  int i; // r6
  int v12; // r5
  char v14; // [sp+1h] [bp-17h]
  char v15; // [sp+2h] [bp-16h]
  char v16; // [sp+3h] [bp-15h]
  char v17; // [sp+4h] [bp-14h]
  unsigned __int8 v18; // [sp+5h] [bp-13h] BYREF
  unsigned __int8 v19; // [sp+6h] [bp-12h]
  unsigned __int8 v20; // [sp+7h] [bp-11h]
  int v21; // [sp+8h] [bp-10h]

  v5 = a3;
  *(_DWORD *)a1 = &unk_1D0E0;
  if ( a3 )
  {
    v6 = 0;
    do
    {
      v7 = *(_BYTE *)a2++;
      *(&v18 + v6++) = v7;
      if ( v6 == 3 )    // 看代码可以感觉出来是base64加密
      {
        v8 = 1;
        v9 = v18 >> 2;
        v14 = v18 >> 2;
        v15 = (16 * v18) & 0x30 | (v19 >> 4);
        v16 = (v20 >> 6) & 0xC3 | (4 * (v19 & 0xF));
        v17 = v20 & 0x3F;
        while ( 1 )
        {
          sub_6F28(a1, *(unsigned __int8 *)(dword_1D09C + (unsigned __int8)v9));
          if ( v8 > 3 )
            break;
          LOBYTE(v9) = *(&v14 + v8++);
        }
        v6 = 0;
      }
      --v5;
    }
    while ( v5 );
    if ( v6 )
    {
      if ( v6 <= 2 )
        memset(&v18 + v6, 0, 3 - v6);
      v10 = v18 >> 2;
      v14 = v18 >> 2;
      v15 = (16 * v18) & 0x30 | (v19 >> 4);
      v16 = (v20 >> 6) & 0xC3 | (4 * (v19 & 0xF));
      v17 = v20 & 0x3F;
      if ( v6 >= 0 )
      {
        for ( i = 0; ; ++i )
        {
          sub_6F28(a1, *(unsigned __int8 *)(dword_1D09C + (unsigned __int8)v10));    // 后面可以知道dword_1D09C存储了base64映射表(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/)
          if ( i >= v6 )
            break;
          LOBYTE(v10) = *(&v15 + i);
        }
      }
      v12 = v6 - 1;
      while ( ++v12 <= 2 )
        sub_6F28(a1, 46);
    }
  }
  return _stack_chk_guard - v21;
}

最后的secret位于bss段,通常存放全局变量(未初始化和初始化的)

image-20231216143958364.png

可以X查看调用这个secret的地方(从名字看很有可能是最终加密的字符串),发现sub_520c()同样调用了它,

int sub_520C()
{
  int v1; // [sp+0h] [bp-18h] BYREF
  char v2[4]; // [sp+4h] [bp-14h] BYREF
  int v3; // [sp+8h] [bp-10h]

  sub_8F7C(&secret, "dHR0dGlldmFodG5vZGllc3VhY2VibGxlaHNhdG5hd2k.", (int)&v1);    // 基本实锤这是个赋值字符串的函数
  _cxa_atexit((void (__fastcall *)(void *))sub_69A4, &secret, &unk_1D000);
  sub_8F7C(&dword_1D09C, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", (int)v2);    // 这里也是赋值了
  _cxa_atexit((void (__fastcall *)(void *))sub_69A4, &dword_1D09C, &unk_1D000);
  return _stack_chk_guard - v3;
}

因此加密流程是这样的:

  1. 输入字符串
  2. 先检查环境是否为模拟器,如果是直接返回False,不是调用so文件中的checkPasswd函数检查密码
  3. 密码做一个倒置
  4. 密码base64加密
  5. 和secret("dHR0dGlldmFodG5vZGllc3VhY2VibGxlaHNhdG5hd2k.")cmp对比,记得点号替换为=

解密:

image-20231216145723269.png

easycrack-100

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

jeb查看mainactivity

public class MainActivity extends AppCompatActivity {
    class CheckText implements TextWatcher {
        CheckText(MainActivity arg1) {
            MainActivity.this = arg1;
            super();
        }

        public void afterTextChanged(Editable arg5) {
            MainActivity.this.findViewById(2131427416).setText("Status: " + MainActivity.this.parseText(arg5.toString()));
        }

        public void beforeTextChanged(CharSequence arg1, int arg2, int arg3, int arg4) {
        }

        public void onTextChanged(CharSequence arg1, int arg2, int arg3, int arg4) {
        }
    }

    static {
        System.loadLibrary("native-lib");
    }

    public MainActivity() {
        super();
    }

    public String messageMe() {
        String v3 = "";
        int v4 = 51;
        String[] v1 = this.getApplicationContext().getPackageName().split("\\.");
        char[] v6 = v1[v1.length - 1].toCharArray();
        int v7 = v6.length;
        int v5;
        for(v5 = 0; v5 < v7; ++v5) {
            v4 ^= v6[v5];
            v3 = v3 + (((char)v4));
        }
        return v3;
    }

    protected void onCreate(Bundle arg4) {
        super.onCreate(arg4);
        this.setContentView(2130968603);
        this.findViewById(2131427416).setText(this.stringFromJNI());
        this.findViewById(2131427415).addTextChangedListener(new CheckText(this));
    }

    public native String parseText(String arg1) {
    }

    public native String stringFromJNI() {
    }
}

去看jni调用的代码,这次看的是armeabi里的so文件

int __fastcall Java_com_njctf_mobile_easycrack_MainActivity_parseText(int a1, int a2, int a3)
{
  int v5; // r0
  int v6; // r0
  int v7; // r0
  size_t v8; // r4
  size_t v9; // r6
  size_t v10; // r2
  unsigned int v11; // r3
  unsigned int v12; // r6
  unsigned int v13; // r0
  unsigned __int8 *v14; // r4
  const char *v15; // r5
  size_t v16; // r2
  size_t v18; // r0
  const char *v19; // r1
  const char *v21; // [sp+Ch] [bp-130h]
  unsigned __int8 *v23; // [sp+10h] [bp-12Ch]
  char *v24; // [sp+14h] [bp-128h]
  size_t v25; // [sp+18h] [bp-124h]
  const char *v26; // [sp+18h] [bp-124h]
  _WORD v27[8]; // [sp+1Ch] [bp-120h] BYREF
  char v28[256]; // [sp+2Ch] [bp-110h] BYREF

  v5 = (*(int (__fastcall **)(int, const char *))(*(_DWORD *)a1 + 24))(a1, "com/njctf/mobile/easycrack/MainActivity");
  v6 = (*(int (__fastcall **)(int, int, const char *, const char *))(*(_DWORD *)a1 + 132))(
         a1,
         v5,
         "messageMe",
         "()Ljava/lang/String;");
  v7 = j__JNIEnv::CallObjectMethod(a1, a2, v6);
  v25 = 0;
  v24 = (char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, v7, 0);    // 调用了java里的messageMe(一个加密字符串的函数)
  v21 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
  v8 = j_strlen(v21);    // 输入字符串的长度
  v9 = j_strlen(v24);    // messageMe返回字符串长度
  v23 = (unsigned __int8 *)j_malloc(v8);    // 分配和输入字符串大小相同的空间
  if ( v8 )
  {
    do        // 循环加密,加密后的字符串存到v23
    {
      if ( v9 )
      {
        v10 = 0;
        do
        {
          v23[v25 + v10] = v24[v10] ^ v21[v25 + v10];
          v11 = v25 + v10++ + 1;
        }
        while ( v10 < v9 && v11 < v8 );
      }
      v25 += v9;
    }
    while ( v25 < v8 );
  }
  memset(v28, 0, sizeof(v28));
  strcpy((char *)v27, "I_am_the_key");
  HIBYTE(v27[6]) = 0;    // 这里比较奇怪,0不是相当于提前终止字符串吗
  v27[7] = 0;
  v12 = v8;        // v12存储输入字符串长度
  v13 = j_strlen((const char *)v27);    // 新的长度为6
  j_init((unsigned __int8 *)v28, (unsigned __int8 *)v27, v13);    // 做到这里看到v28是256位空的,v27是密钥,可以联想到之前的RC4加密算法
  v14 = v23;    // v14存储加密后的字符串
  j_crypt((unsigned __int8 *)v28, v23, v12);    // 传入的分别是复制过的256位,v23前面异或过的字符串,v12是长度
  v26 = (const char *)j_malloc((2 * v12) | 1);    // v26大小为2*v12+1,由下面可知存的是2位大写十六进制字符串
  if ( v12 )
  {
    v15 = v26;
    do
    {
      j_snprintf(v15, 3, "%02X", *v14);
      v15 += 2;
      --v12;
      ++v14;
    }
    while ( v12 );
  }
  v16 = j_strlen(v26);    // 长度为2*v12
  if ( v16 && !j_strncmp(v26, compare, v16) )    // compare='C8E4EF0E4DCCA683088134F8635E970EEAD9E277F314869F7EF5198A2AA4'
  {
    j___android_log_print(2, "NJCTF-easycrack", "success: %s", v26);
    v18 = j_strlen(compare);
    if ( !j_strncmp(v26, compare, v18) )
      v19 = "YOU GOT IT!";
    else
      v19 = "Victory is in sight.";
    return (*(int (__fastcall **)(int, const char *))(*(_DWORD *)a1 + 668))(a1, v19);
  }
  else
  {
    j___android_log_print(6, "NJCTF-easycrack", "failed : %s", v26);
    return (*(int (__fastcall **)(int, const char *))(*(_DWORD *)a1 + 668))(a1, "Try again.");
  }
}

分析得到加密流程如下:

  1. 首先得到messageMe返回的字符串==V7D=^,M.E==

image-20231216170929414.png

  1. 接着输入的字符串循环和上面返回的字符串异或
  2. 接着RC4初始化,密钥为"I_am_the_key",这里放张RC4初始化代码和这里j_init的代码对比,可以看到都是两个256循环

image-20231216180055814.png

  1. 接着RC4加密,这里再对比下:

image-20231216180435502.png

  1. 得到的结果存到了v23,也就是v14,接着转为2位长度的16进制,并和compare比较

python脚本解密

def decrypt(v23, v24):
    v8 = len(v23)
    v9 = len(v24)
    v25 = 0
    v21 = []
    v11 = 0
    if v8:
        while v25 < v8:
            if v9:
                v10 = 0
                while v10 < v9 and v11 <= v8:   # 要注意原来C语言判断是执行完一个循环才判断,python执行循环前就要判断,条件不同
                    v21.append(v24[v10] ^ v23[v25 + v10])
                    v10 += 1
                    v11 = v25 + v10 + 1
            v25 += v9
    for i in v21:
        print(chr(i), end='')
    print()


def get_key(key):
    key_len = len(key)
    a1, a2 = [], []
    for i in range(256):
        a1.append(i)
        a2.append(ord(key[i % key_len]))
    v5 = 0
    for i in range(256):
        v5 = (v5 + a1[i] + a2[i]) % 256
        a1[i], a1[v5] = a1[v5], a1[i]
    return a1


def rc4_decrypt(key, cipher):
    v7, v6 = 0, 0
    v23 = []
    for i in range(len(cipher)):
        v7 = (v7 + 1) % 256
        v6 = (v6 + key[v7]) % 256
        key[v7], key[v6] = key[v6], key[v7]
        v23.append(key[(key[v6] + key[v7]) % 256] ^ cipher[i])
    return v23


def messageMe():
    message = 'easycrack'
    new_message = []
    v4 = 51
    for i in range(len(message)):
        v4 ^= ord(message[i])
        new_message.append(v4)
    return new_message


if __name__ == '__main__':
    # RC4初始化
    key = get_key('I_am_the_key')
    # 求enflag
    compare = 'C8E4EF0E4DCCA683088134F8635E970EEAD9E277F314869F7EF5198A2AA4'
    cipher = [int(compare[i]+compare[i+1], 16) for i in range(0, len(compare), 2)]
    # RC4解密
    v23 = rc4_decrypt(key, cipher)
    # 求messageMe返回字符串
    v24 = messageMe()
    decrypt(v23, v24)

结果:It_s_a_easyCrack_for_beginners,太扎心了,怎么觉得不简单。。。

读反编译的代码还是不太顺利,接着练吧

fake-func

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

jeb查看

public class MainActivity extends AppCompatActivity {
    public MainActivity() {
        super();
    }

    protected void onCreate(Bundle arg2) {
        super.onCreate(arg2);
        this.setContentView(2131296284);
        this.findViewById(2131165218).setOnClickListener(new View$OnClickListener() {
            public void onClick(View arg3) {     
                if(check.checkflag(MainActivity.this.findViewById(2131165238).getText().toString())) {    // 很明显这里做了判断
                    Toast.makeText(MainActivity.this, "you are right~!", 1).show();
                }
                else {
                    Toast.makeText(MainActivity.this, "wrong!", 1).show();
                }
            }
        });
    }
}

跳转check发现果然又使用了JNI调用

public class check {
    static {
        System.loadLibrary("checkso");
    }

    public check() {
        super();
    }

    public static native boolean checkflag(String arg0) {
    }
}

轻车熟路apktool解压,ida静态分析lib下的so文件,定位checkflag函数

unsigned int __fastcall Java_com_example_p7xxtmx_1g_fakefunc_check_checkflag(int a1)
{
  const char *v1; // r4@1
  v1 = (const char *)(*(int (**)(void))(*(_DWORD *)a1 + 676))();    // 应该就是我们的输入
  sub_E08();
  return __clz(strcmp(v1, off_6004)) >> 5;    // off_6004='c2RuaXNjc2RuaXNjYWJjZA=='看着很像base64加密,解码得到sdniscsdniscabcd
}

int sub_E08()
{
  char *v0; // r4@1
  size_t v1; // r1@1
  v0 = off_6004;
  v1 = strlen(off_6004);
  return sub_16D8(v0, v1);    // 结合里面的代码以及传入的参数可知是base64解密函数
}

分别尝试这个字符串,以及解码的字符串均不对,联想到题目提示fake func,猜测函数发生变化

首先查看字符串发现除了上面那个还有另一个字符串,双击进去按X查看调用地方,并F5查看伪代码,以及X查看调用函数

image-20231217100602291.png

可以看到strcmp被一个j_registerInlineHook做了处理,并调用了sub_E28函数,里面存储了正好是另一个特殊字符串

int __fastcall sub_E28(int a1)
{
  int v1; // r4@1
  int v2; // r0@1
  int v3; // r0@1
  v1 = a1;
  v2 = sub_E08();        // 上面也出现过,就是base64解密
  v3 = sub_1388(v1, v2);    // v2是解密后的字符串,v1是我们的输入
  return dword_6008(v3, "K4/7/faihmk9/WEMlfuFdpgrP86ckd4oQQ/UeAiZdx8=");    // AES加密后和这个字符串比较
}

int sub_EC8()
{
  int result; // r0@2
  if ( j_registerInlineHook(&strcmp, sub_E28, &dword_6008) )
  {
    result = -1;
  }
  else
  {
    result = j_inlineHook(&strcmp);
    if ( result )
      result = -1;
  }
  return result;
}

网上搜了下直接发现类似代码

image-20231217110451833

registerInlineHook传入的参数:第一个是要hook的目标函数地址,第二个是替换函数的指针,第三个是保留函数原来的指针

因此实际要分析的函数是上面这个sub_E28(),这里强烈安利ida插件findcrypt,真不是一般好用,不过配置了半天发现我这个版本ida不好用,果断换成了吾爱的ida7.7(好用滴很,还自带了python3.8)

结合插件的提示可知,这个函数属于AES加密,那上面

image-20231217153323606.png

在线AES解密如下

image-20231217154148217.png

reverse_re3

__int64 sub_940()
{
  int v0; // eax
  int v2; // [rsp+8h] [rbp-218h]
  int v3; // [rsp+Ch] [rbp-214h]
  char v4[520]; // [rsp+10h] [rbp-210h] BYREF
  unsigned __int64 v5; // [rsp+218h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  v3 = 0;
  memset(v4, 0, 0x200uLL);
  _isoc99_scanf(&unk_1278, v4, v4);
  while ( 1 )    // 由特征可知大概是个迷宫问题,只能按wasd
  {
    do
    {
      v2 = 0;
      sub_86C();
      v0 = v4[v3];
      if ( v0 == 'd' )
      {
        v2 = sub_E23();
      }
      else if ( v0 > 'd' )
      {
        if ( v0 == 's' )
        {
          v2 = sub_C5A();
        }
        else if ( v0 == 'w' )
        {
          v2 = sub_A92();
        }
      }
      else
      {
        if ( v0 == 27 )
          return 0xFFFFFFFFLL;
        if ( v0 == 'a' )
          v2 = sub_FEC();
      }
      ++v3;
    }
    while ( v2 != 1 );
    if ( dword_202AB0 == 2 )
      break;
    ++dword_202AB0;
  }
  puts("success! the flag is flag{md5(your input)}");
  return 1LL;
}

再去看sub_86C函数,里面大致说明了迷宫的形状为3个(15*15)正方形,i表示行,j表示列,且分别赋值给dword_202AB4和dword_202AB8

unsigned __int64 sub_86C()
{
  int i; // [rsp+0h] [rbp-10h]
  int j; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  for ( i = 0; i <= 14; ++i )
  {
    for ( j = 0; j <= 14; ++j )
    {
      if ( dword_202020[225 * dword_202AB0 + 15 * i + j] == 3 )    // 联系dword_202AB0==2退出,说明走三个方形,每个225大小
      {
        dword_202AB4 = i;    // 等于3表示这是当前位置
        dword_202AB8 = j;
        break;
      }
    }
  }
  return __readfsqword(0x28u) ^ v3;
}

再去看按下方向键的操作

__int64 sub_E23()    // 'd'
{
  if ( dword_202AB8 != 14 )    // 首先判断dword_202AB8是否等于14,不等则返回0,继续循环按方向键
  {
    if ( dword_202020[225 * dword_202AB0 + 1 + 15 * dword_202AB4 + dword_202AB8] == 1 )    // 先判断右移是否可以走(是否等于1)
    {
      dword_202020[225 * dword_202AB0 + 1 + 15 * dword_202AB4 + dword_202AB8] = 3;    // 如果可以这个位置置3,说明这是当前位置
      dword_202020[225 * dword_202AB0 + 15 * dword_202AB4 + dword_202AB8] = 1;        // 原来位置置1
    }
    else if ( dword_202020[225 * dword_202AB0 + 1 + 15 * dword_202AB4 + dword_202AB8] == 4 )    // 如果等于4就要退出循环,dword_202AB0++,说明当前这个方形迷宫走完了
    {
      return 1LL;
    }    // 如果不为1也不为0,说明走的不对,走的无效
  }
  return 0LL;
}

__int64 sub_C5A()    // 's'
{
  if ( dword_202AB4 != 14 )
  {
    if ( dword_202020[225 * dword_202AB0 + 15 + 15 * dword_202AB4 + dword_202AB8] == 1 )    // 按's'是直接进入下一行+15
    {
      dword_202020[225 * dword_202AB0 + 15 + 15 * dword_202AB4 + dword_202AB8] = 3;
      dword_202020[225 * dword_202AB0 + 15 * dword_202AB4 + dword_202AB8] = 1;
    }
    else if ( dword_202020[225 * dword_202AB0 + 15 + 15 * dword_202AB4 + dword_202AB8] == 4 )
    {
      return 1LL;
    }
  }
  return 0LL;
}

至此基本分析完成,迷宫为1的能走,为3表示当前位置,为4表示成功走到,关键要看dword_202020里的值。ida在它上面右键convert to python list(dword)

第一个:ddsssddddsssdss

image-20231218101726454

第二个:dddddsssddddsssaassssddds

image-20231218102155982

第三个:ddssddwddssssssdddssssdddss

image-20231218102038385

image-20231218102357832.png

crypt

ida64查看main代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  int v4; // eax
  void *v5; // rax
  void *v7; // rax
  int i; // [rsp+24h] [rbp-D4h]
  _DWORD *v9; // [rsp+28h] [rbp-D0h]
  char v10[32]; // [rsp+30h] [rbp-C8h] BYREF
  char Str[128]; // [rsp+50h] [rbp-A8h] BYREF

  strcpy(Str, "12345678abcdefghijklmnopqrspxyz");// copy到Str变量
  memset(&Str[32], 0, 0x60ui64);                // 填充Str
  memset(v10, 0, 0x17ui64);
  sub_1400054D0("%s", v10);                     // 输入字符串
  v9 = malloc(0x408ui64);                       // 正好是分配了(256+2)*4=0x408个字节
  v3 = strlen(Str);                             // 获取Str长度,应该是31
  sub_140001120(v9, (__int64)Str, v3);          // 获取的了一个初始化的v9,长度为258,内容是0、0、0-255
  v4 = strlen(v10);                             // 获取输入字符串长度
  sub_140001240(v9, (__int64)v10, v4);            // 基本可以判断是rc4加密
  for ( i = 0; i < 22; ++i )
  {
    if ( ((unsigned __int8)v10[i] ^ 0x22) != byte_14013B000[i] )    // 这里还做了个异或
    {
      v5 = (void *)sub_1400015A0(&off_14013B020, "error");
      _CallMemberFunction0(v5, sub_140001F10);
      return 0;
    }
  }
  v7 = (void *)sub_1400015A0(&off_14013B020, "nice job");
  _CallMemberFunction0(v7, sub_140001F10);
  return 0;
}
__int64 __fastcall sub_140001120(_DWORD *a1, __int64 a2, int a3)
{
  __int64 result; // rax
  int i; // [rsp+0h] [rbp-28h]
  int j; // [rsp+0h] [rbp-28h]
  int v6; // [rsp+4h] [rbp-24h]
  int v7; // [rsp+8h] [rbp-20h]
  int v8; // [rsp+Ch] [rbp-1Ch]
  _DWORD *v9; // [rsp+10h] [rbp-18h]

  *a1 = 0;
  a1[1] = 0;
  v9 = a1 + 2;
  for ( i = 0; i < 256; ++i )
    v9[i] = i;                                  // 初始化v9,也就是a1[2]之后的
  v6 = 0;
  result = 0i64;
  LOBYTE(v7) = 0;
  for ( j = 0; j < 256; ++j )
  {
    v8 = v9[j];
    v7 = (unsigned __int8)(*(_BYTE *)(a2 + v6) + v8 + v7);// a2是Str字符串的地址
    v9[j] = v9[v7];
    v9[v7] = v8;                                // 很明显做了交换,结合256,这些都是RC4初始化的特征
    if ( ++v6 >= a3 )                           // a3是Str长度
      v6 = 0;
    result = (unsigned int)(j + 1);
  }
  return result;
}

直接借用以前的脚本解密即可

def get_key(key):
    key_len = len(key)
    a1, a2 = [], []
    for i in range(256):
        a1.append(i)
        a2.append(ord(key[i % key_len]))
    v5 = 0
    for i in range(256):
        v5 = (v5 + a1[i] + a2[i]) % 256
        a1[i], a1[v5] = a1[v5], a1[i]
    return a1


def decrypt(enc_key, enflag):
    v7, v6 = 0, 0
    flag = ''
    for i in range(len(enflag)):
        v7 = (v7 + 1) % 256
        v6 = (v6 + enc_key[v7]) % 256
        enc_key[v7], enc_key[v6] = enc_key[v6], enc_key[v7]
        flag += chr(enc_key[(enc_key[v6] + enc_key[v7]) % 256] ^ enflag[i])
    return flag


if __name__ == '__main__':
    enc_key = get_key('12345678abcdefghijklmnopqrspxyz')
    enflag = [i^0x22 for i in [0x9E, 0xE7, 0x30, 0x5F, 0xA7, 0x01, 0xA6, 0x53, 0x59, 0x1B, 0x0A, 0x20, 0xF1, 0x73, 0xD1, 0x0E, 0xAB, 0x09, 0x84, 0x0E, 0x8D, 0x2B]]
    flag = decrypt(enc_key, enflag)
    print(flag)

bad_python

尝试在线pyc反编译以及uncompyle6均报错编译失败,查询了下可能原因为文件头被篡改,因此尝试手动生成一个pyc文件,替换文件头

https://zhuanlan.zhihu.com/p/617737294

写一个print hi,由于原始pyc是python36编译的,我们也要用python3.6;编译获取pyc命令是python -m py_compile .hi.py,命令会在当前目录下生成__pycache__,里面就是pyc

winhex打开获取前16个字节复制到原来的上面,再使用uncompyle6即可成功反编译,获取的python如下

# uncompyle6 version 3.9.0
# Python bytecode version base 3.6 (3379)
# Decompiled from: Python 3.9.12 (main, Apr  4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: pyre.py
# Compiled at: 2023-12-18 18:22:59
# Size of source mod 2**32: 11 bytes
from ctypes import *
from Crypto.Util.number import bytes_to_long
from Crypto.Util.number import long_to_bytes

def encrypt(v, k):
    v0 = c_uint32(v[0])
    v1 = c_uint32(v[1])
    sum1 = c_uint32(0)
    delta = 195935983
    for i in range(32):
        v0.value += (v1.value << 4 ^ v1.value >> 7) + v1.value ^ sum1.value + k[sum1.value & 3]
        sum1.value += delta
        v1.value += (v0.value << 4 ^ v0.value >> 7) + v0.value ^ sum1.value + k[sum1.value >> 9 & 3]

    return (
     v0.value, v1.value)


if __name__ == '__main__':
    flag = input('please input your flag:')
    k = [255, 187, 51, 68]
    if len(flag) != 32:
        print('wrong!')
        exit(-1)
    a = []
    for i in range(0, 32, 8):
        v1 = bytes_to_long(bytes(flag[i:i + 4], 'ascii'))
        v2 = bytes_to_long(bytes(flag[i + 4:i + 8], 'ascii'))
        a += encrypt([v1, v2], k)

    enc = [
     '4006073346', '2582197823', '2235293281', '558171287', '2425328816', 
     '1715140098', '986348143', '1948615354']
    for i in range(8):
        if enc[i] != a[i]:
            print('wrong!')
            exit(-1)

    print('flag is flag{%s}' % flag)

只在源代码基础上稍加修改即可

# uncompyle6 version 3.9.0
# Python bytecode version base 3.6 (3379)
# Decompiled from: Python 3.9.12 (main, Apr  4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: pyre.py
# Compiled at: 2023-12-18 18:22:59
# Size of source mod 2**32: 11 bytes
from ctypes import *
from Crypto.Util.number import bytes_to_long
from Crypto.Util.number import long_to_bytes

def decrypt(v, k):
    v0 = c_uint32(int(v[0]))
    v1 = c_uint32(int(v[1]))
    sum1 = c_uint32(0)
    delta = 195935983
    sum1.value += delta * 32    # 原来到最后加到了delta * 32,因此逆向初始值为此
    for i in range(32):
        v1.value -= (v0.value << 4 ^ v0.value >> 7) + v0.value ^ sum1.value + k[sum1.value >> 9 & 3]    # 原来加号改为减号
        sum1.value -= delta
        v0.value -= (v1.value << 4 ^ v1.value >> 7) + v1.value ^ sum1.value + k[sum1.value & 3]

    return (
     v0.value, v1.value)


if __name__ == '__main__':
    flag = ''
    k = [255, 187, 51, 68]
    a = []
    enc = [
     '4006073346', '2582197823', '2235293281', '558171287', '2425328816',
     '1715140098', '986348143', '1948615354']
    for i in range(0, 8, 2):
        v1 = enc[i]
        v2 = enc[i+1]
        a += decrypt([v1, v2], k)

    for i in range(8):
        flag += long_to_bytes(a[i]).decode('utf-8')

    print('flag is flag{%s}' % flag)

结果:flag is flag{Th1s_1s_A_Easy_Pyth0n__R3veRse_0}

easyre-xctf

这道题类似misc思路,将flag隐藏到里面没用的函数里。首先upx脱壳,ida64查找到特殊字符串“d_0n3_4nd_tw0}”,同时前面有个==f_part2==名称,再去看函数可以看到有一个part1,里面传递了前半部分flag(按R转为字符,同时小段存储要反转下)

image-20231220111248450

得到flag{UPX_4nd_0n3_4nd_tw0}

CatFly

这题真的是新手题么,太打击了,代码太多了反编译又看不出点头绪,对着wp才分析出代码

首先尝试运行(linux下,记得chmod +x赋予权限)

image-20231221092921497.png

可以看到第一行一直再打印字符,最后一行一直在计数,所以关键是找到打印上面这些无序字符的代码

ida64找到main,再去找printf

  while ( v13 )
  {
    if ( dword_E104 )
      printf("\x1B[H");
    else
      printf("\x1B[u");
    for ( k = dword_E1EC; k < dword_E1F0; ++k )
    {
      for ( m = dword_E1F4; m < dword_E1F8; ++m )
      {
        if ( k <= 23 || k > 42 || m >= 0 )
        {
          if ( m >= 0 && (unsigned int)k <= 0x3F && m <= 63 )
          {
            v19 = off_FA20[v24][k][m];
            off_FA88 = sub_6314((unsigned int)v24, k, m, (__int64)v12);// 这里是关键的赋值
          }
          else
          {
            v19 = 44;
          }
        }
        else
        {
          v18 = (2 - m) % 16 / 8;
          if ( ((v24 >> 1) & 1) != 0 )
            v18 = 1 - v18;
          s[128] = (__int64)",,>>&&&+++###==;;;,,";
          v19 = asc_BFE3[v18 - 23 + k];
          if ( !v19 )
            v19 = 44;
        }
        if ( v25 )
        {
          printf("%s", *((const char **)&unk_FCC0 + v19));
        }
        else if ( v19 == v22 || !*((_QWORD *)&unk_FCC0 + v19) )
        {
          printf("%s", off_FA88);// 这里是关键的打印
        }
        else
        {
          v22 = v19;
          printf("%s%s", *((const char **)&unk_FCC0 + v19), off_FA88);
        }
      }
      sub_65E2(1LL);
    }
    if ( dword_E100 )
    {
      time(&time1);
      v11 = difftime(time1, timer);
      v10 = sub_63FF((unsigned int)(int)v11);
      for ( n = (dword_E1FC - 29 - v10) / 2; n > 0; --n )
        putchar(32);
      dword_E1E8 += printf("\x1B[1;37mYou have nyaned for %d times!\x1B[J\x1B[0m", (unsigned int)++dword_108E0);
    }
    v22 = 0;
    ++v23;
    if ( dword_104C4 && v23 == dword_104C4 )
      sub_6471();
    if ( !off_FA20[++v24] )
      v24 = 0LL;
    usleep(1000 * v27);
  }

重点看sub_6314函数

char *__fastcall sub_6314(__int64 a1, int a2, int a3, __int64 a4)
{
  if ( a2 != 18 )    // 必须a2=18
    return (char *)a4;
  if ( a3 <= 4 || a3 > 54 )    // a3范围5~54,共50个
    return (char *)a4;
  byte_104C9 = 32;
  dword_E120[a3 - 5] ^= sub_62B5();
  if ( (unsigned __int8)sub_62E3(dword_E120[a3 - 5]) )
    byte_104C8 = dword_E120[a3 - 5] & 0x7F;
  else
    byte_104C8 = 32;
  return &byte_104C8;
}

unsigned int dword_E1E8 = 0x00001106;

unsigned int dword_E120[50] = {
    0x000027FB, 0x000027A4, 0x0000464E, 0x00000E36, 0x00007B70, 0x00005E7A, 0x00001A4A, 0x000045C1, 
    0x00002BDF, 0x000023BD, 0x00003A15, 0x00005B83, 0x00001E15, 0x00005367, 0x000050B8, 0x000020CA, 
    0x000041F5, 0x000057D1, 0x00007750, 0x00002ADF, 0x000011F8, 0x000009BB, 0x00005724, 0x00007374, 
    0x00003CE6, 0x0000646E, 0x0000010C, 0x00006E10, 0x000064F4, 0x00003263, 0x00003137, 0x000000B8, 
    0x0000229C, 0x00007BCD, 0x000073BD, 0x0000480C, 0x000014DB, 0x000068B9, 0x00005C8A, 0x00001B61, 
    0x00006C59, 0x00005707, 0x000009E6, 0x00001FB9, 0x00002AD3, 0x000076D4, 0x00003113, 0x00007C7E, 
    0x000011E0, 0x00006C70
};

__int64 sub_62B5()
{
  dword_E1E8 = 1103515245 * dword_E1E8 + 12345;
  return (dword_E1E8 >> 10) & 0x7FFF;
}

_BOOL8 __fastcall sub_62E3(char a1)
{
  return (a1 & 0x7Fu) <= 0x7E && (a1 & 0x7Fu) > 0x20;
}

解密

#include<iostream>
#include<string.h>
#include <time.h>
using namespace std;

unsigned int dword_E1E8 = 0x00001106;
unsigned int dword_E120[50] = {
    0x000027FB, 0x000027A4, 0x0000464E, 0x00000E36, 0x00007B70, 0x00005E7A, 0x00001A4A, 0x000045C1, 
    0x00002BDF, 0x000023BD, 0x00003A15, 0x00005B83, 0x00001E15, 0x00005367, 0x000050B8, 0x000020CA, 
    0x000041F5, 0x000057D1, 0x00007750, 0x00002ADF, 0x000011F8, 0x000009BB, 0x00005724, 0x00007374, 
    0x00003CE6, 0x0000646E, 0x0000010C, 0x00006E10, 0x000064F4, 0x00003263, 0x00003137, 0x000000B8, 
    0x0000229C, 0x00007BCD, 0x000073BD, 0x0000480C, 0x000014DB, 0x000068B9, 0x00005C8A, 0x00001B61, 
    0x00006C59, 0x00005707, 0x000009E6, 0x00001FB9, 0x00002AD3, 0x000076D4, 0x00003113, 0x00007C7E, 
    0x000011E0, 0x00006C70
};

int sub_62B5()
{
  dword_E1E8 = 1103515245 * dword_E1E8 + 12345;
  return (dword_E1E8 >> 10) & 0x7FFF;
}

bool sub_62E3(char a1)
{
  return (a1 & 0x7Fu) <= 0x7E && (a1 & 0x7Fu) > 0x20;
}

int calc(int cnt) {
    int i=0;
    while(cnt){
        cnt=cnt/10;
        i++;
    }
    return i;
}

int main () {
    int cnt = 0;
    clock_t start, end; //定义clock_t变量
    start = clock(); //开始时间
    while (1) {
        char flag[50];
        for (int i = 0; i < 50; i++) {
            dword_E120[i] ^= sub_62B5();
            if (sub_62E3(dword_E120[i]))
                flag[i] = dword_E120[i] & 0x7F;
            else
                flag[i] = ' ';
        }
        if (!strncmp(flag, "CatCTF", 6)) {
            cout << flag << endl;
            break;
        }
        cnt++;
        dword_E1E8 += 41;
        dword_E1E8 += calc(cnt);    // 后面记得+了printf返回值
    }
    cout << "cnt: " << cnt << endl;
    end = clock();   //结束时间
    cout << "took " << double(end-start)/CLOCKS_PER_SEC << "s" << endl;  //输出时间(单位:s)
}

BABYRE

ida64查看main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[24]; // [rsp+0h] [rbp-20h] BYREF
  int v5; // [rsp+18h] [rbp-8h]
  int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; i <= 181; ++i )
    judge[i] ^= 0xCu;    // 这里查看是数组形式,实际上异或完是汇编代码
  printf("Please input flag:");
  __isoc99_scanf("%20s", s);
  v5 = strlen(s);
  if ( v5 == 14 && (*(unsigned int (__fastcall **)(char *))judge)(s) )    // 但是这里却看起来judge是个函数
    puts("Right!");
  else
    puts("Wrong!");
  return 0;
}

这道题实际上judge数组是汇编指令,但ida反编译识别成了数据

image-20231221172329610

因此需要调试,运行到judge异或完查看其具体代码。这里学习到了如何远程连接Linux来进行调试(这里文件只能在linux上跑)

https://blog.csdn.net/m0_46296905/article/details/115794076

在printf上打断点,然后F5查看judge,并按下C转换为代码,如下图,已经有函数代码的逻辑

image-20231221172717614.png

再按下P生成函数,F5即可查看伪代码

__int64 __fastcall judge(__int64 a1)
{
  char v2[5]; // [rsp+8h] [rbp-20h] BYREF
  char v3[9]; // [rsp+Dh] [rbp-1Bh] BYREF
  int i; // [rsp+24h] [rbp-4h]

  qmemcpy(v2, "fmcd", 4);
  v2[4] = 127;
  qmemcpy(v3, "k7d;V`;np", sizeof(v3));
  for ( i = 0; i <= 13; ++i )
    *(_BYTE *)(i + a1) ^= i;    // 先按位异或
  for ( i = 0; i <= 13; ++i )
  {
    if ( *(_BYTE *)(i + a1) != v2[i] )    // 再和v2比较(实际上v2+v3)
      return 0LL;
  }
  return 1LL;
}

image-20231221180040422.png

parallel-comparator-200

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

#define FLAG_LEN 20

void * checking(void *arg) {
    char *result = malloc(sizeof(char));
    char *argument = (char *)arg;
    *result = (argument[0]+argument[1]) ^ argument[2];
    return result;
}

int highly_optimized_parallel_comparsion(char *user_string)
{
    int initialization_number;
    int i;
    char generated_string[FLAG_LEN + 1];
    generated_string[FLAG_LEN] = '\0';

    while ((initialization_number = random()) >= 64);    // 随机数大于等于64,运行代码发现固定41
  
    int first_letter;
    first_letter = (initialization_number % 26) + 97;

    pthread_t thread[FLAG_LEN];
    char differences[FLAG_LEN] = {0, 9, -9, -1, 13, -13, -4, -11, -9, -1, -7, 6, -13, 13, 3, 9, -13, -11, 6, -7};
    char *arguments[20];
    for (i = 0; i < FLAG_LEN; i++) {
        arguments[i] = (char *)malloc(3*sizeof(char));
        arguments[i][0] = first_letter;
        arguments[i][1] = differences[i];
        arguments[i][2] = user_string[i];

        pthread_create((pthread_t*)(thread+i), NULL, checking, arguments[i]);    // 创建线程,调用checking函数,传入参数arguments[i]
    }

    void *result;
    int just_a_string[FLAG_LEN] = {115, 116, 114, 97, 110, 103, 101, 95, 115, 116, 114, 105, 110, 103, 95, 105, 116, 95, 105, 115};    // 没用
    for (i = 0; i < FLAG_LEN; i++) {
        pthread_join(*(thread+i), &result);    // 等待线程结束,result接收该线程返回值
        generated_string[i] = *(char *)result + just_a_string[i];
        free(result);
        free(arguments[i]);
    }

    int is_ok = 1;
    for (i = 0; i < FLAG_LEN; i++) {
        if (generated_string[i] != just_a_string[i])    // 要想相等,result为0
            return 0;
    }
    return 1;
}

int main()
{
    char *user_string = (char *)calloc(FLAG_LEN+1, sizeof(char));
    fgets(user_string, FLAG_LEN+1, stdin);
    int is_ok = highly_optimized_parallel_comparsion(user_string);
    if (is_ok)
        printf("You win!\n");
    else
        printf("Wrong!\n");
    return 0;
}

题目很简单,解题过程中配c语言环境搞了半天,两点要注意的

  • 用gcc编译而不是g++
  • 再配置中加上参数-lpthread才行,不然会报错
#include <iostream>
using namespace std;
int main() {
    int FLAG_LEN = 20;
    char differences[FLAG_LEN] = {0, 9, -9, -1, 13, -13, -4, -11, -9, -1, -7, 6, -13, 13, 3, 9, -13, -11, 6, -7};
    for (int j = 0; j < 26; j++) {
        for (int i = 0; i < FLAG_LEN; i++) {
            printf("%c", (differences[i]+j+97)^0);
        }
        printf("\n");
    }
}

可以找到一个规律的字符串lucky_hacker_you_are

secret-galaxy-300

这题类似于Misc隐藏信息的思路,题目压缩包三种文件是不同平台的可执行文件

首先ida32查到很多打印的字符串,主要都是星系,ollydbg下个断点,可以找到很多星系名

image-20231221215357729.png

发现打印只打印前五个,所以第六个隐藏猫腻

int __cdecl print_starbase(int a1)
{
  int result; // eax
  const char *v2; // edx
  int i; // [esp+1Ch] [ebp-Ch]

  puts("--------------GALAXY DATABASE-------------");
  printf("%10s | %s | %s\n", "Galaxy name", "Existence of life", "Distance from Earth");
  result = puts("-------------------------------------------");
  for ( i = 0; i <= 4; ++i )    // 前五个
  {
    if ( *(_DWORD *)(24 * i + a1 + 8) == 1 )
      v2 = "INHABITED";
    else
      v2 = "IS NOT INHABITED";    // 都是这个选项
    result = printf("%11s | %17s | %d\n", *(const char **)(24 * i + a1), v2, *(_DWORD *)(24 * i + a1 + 4));
  }
  return result;
}

因此 ollydbg改汇编代码判断,并没有看到什么flag

image-20231221215515304.png

因此转变思路去找函数,发现一个没有被主函数调用的函数

int __libc_csu_gala()
{
  int result; // eax
  sc[0] = off_409014;
  sc[3] = &byte_40DAC0;
  sc[1] = 31337;
  sc[2] = 1;    // 可以看到连续的字节被赋值,且off这些变量都是字符串数组
  byte_40DAC0 = off_409004[0][8];
  byte_40DAC1 = off_409010[0][7];
  byte_40DAC2 = off_409008[0][4];
  byte_40DAC3 = off_409004[0][6];
  byte_40DAC4 = off_409004[0][1];
  byte_40DAC5 = off_409008[0][2];
  byte_40DAC6 = '_';    // 95正好是下划线
  byte_40DAC7 = off_409004[0][8];
  byte_40DAC8 = off_409004[0][3];
  byte_40DAC9 = off_40900C[0][5];
  byte_40DACA = 95;
  byte_40DACB = off_409004[0][8];
  byte_40DACC = off_409004[0][3];
  byte_40DACD = off_409004[0][4];
  byte_40DACE = off_409010[0][6];
  byte_40DACF = off_409010[0][4];
  byte_40DAD0 = off_409004[0][2];
  byte_40DAD1 = 95;
  byte_40DAD2 = off_409010[0][6];
  result = *((unsigned __int8 *)off_409008[0] + 3);
  byte_40DAD3 = off_409008[0][3];
  byte_40DAD4 = 0;
  return result;
}

看到字符串内容的方法就是ida断点到return result前,双击可以查看,发现特殊字符串即为flag

image-20231221220106369.png

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