2023NewStarCTF Week1 WP(Reverse)

警告
本文最后更新于 2024-03-20,文中内容可能已过时。

前段时间参加了NewStarCTF的比赛,赛制是每个礼拜上一周的新题。由于前段时间比较忙,所以没怎么看,现在空下来,来复现一下,同时提升自己的逆向能力

1 AndroXor

使用 jadx 工具反编译 APK 文件,具体的代码包在 com.chick.androxor 中,且具体的加密代码如下

public String Xor(String str, String str2) {
        char[] cArr = {14, '\r', 17, 23, 2, 'K', 'I', '7', ' ', 30, 20, 'I', '\n', 2, '\f', '>', '(', '@', 11, '\'', 'K', 'Y', 25, 'A', '\r'};
        char[] cArr2 = new char[str.length()];
        String str3 = str.length() != 25 ? "wrong!!!" : "you win!!!";
        for (int i = 0; i < str.length(); i++) {
            char charAt = (char) (str.charAt(i) ^ str2.charAt(i % str2.length()));
            cArr2[i] = charAt;
            if (cArr[i] != charAt) {
                return "wrong!!!";
            }
        }
        return str3;
}

但是他这里的输入,包含了明文的同时,还输入了一个 key 用于异或,所以找到哪里调用了这个 Xor 函数即可

在这个函数上右键查找用例,可以定位到调用该函数的具体位置,调用过程如下

Toast.makeText(mainActivity, mainActivity.Xor(obj, "happyx3"), 1).show();

这里可以发现,key 就是这个 happyx3,所以解密脚本如下

flag = [14, '\r', 17, 23, 2, 'K', 'I', '7', ' ', 30, 20, 'I', '\n', 2, '\f', '>', '(', '@', 11, '\'', 'K', 'Y', 25, 'A', '\r']
key = 'happyx3'
for i,v in enumerate(flag):
    if type(v) == str:
        v = ord(v)
    flag[i] = chr( v ^ ord(key[ i % len(key) ]))
    
print(''.join(flag))
#flag{3z_And0r1d_X0r_x1x1}

2 easy_RE

该题目有个 readme.txt,内容是 打开就有

使用 ida 打开该程序,定位到 main 函数,先看汇编,发现初始化的时候就赋值了明文的 flag

具体代码如下

v5[0] = 102;
v5[1] = 108;
v5[2] = 97;
v5[3] = 103;
v5[4] = 123;
v5[5] = 119;
v5[6] = 101;
v5[7] = 49;
v5[8] = 99;
v5[9] = 48;
v5[10] = 109;
std::string::string(v7, "e_to_rev3rse!!}", &v10);

导出即可

flag{we1c0me_to_rev3rse!!}

3 ELF

使用 ida 进行反编译后,就能得到主要代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // edx
  char *s1; // [rsp+0h] [rbp-20h]
  char *v6; // [rsp+8h] [rbp-18h]
  char *s; // [rsp+10h] [rbp-10h]

  s = (char *)malloc(0x64uLL);
  printf("Input flag: ");
  fgets(s, 100, stdin);
  s[strcspn(s, "\n")] = 0;
  v6 = (char *)encode(s);	//第一次加密
  v3 = strlen(v6);
  s1 = (char *)base64_encode(v6, v3); //第二次加密
  if ( !strcmp(s1, "VlxRV2t0II8kX2WPJ15fZ49nWFEnj3V8do8hYy9t") ) //加密后对比
    puts("Correct");
  else
    puts("Wrong");
  free(v6);
  free(s1);
  free(s);
  return 0;
}

发现输入密文后进行了两次加密,一次是 encode(s),第二次就是 base64_encode(v6,v3)

先看 base64_encode 函数

_BYTE *__fastcall base64_encode(__int64 a1, int a2)
{
  int v3; // eax
  int v4; // eax
  int v5; // eax
  int v6; // eax
  int v7; // eax
  int v8; // eax
  int v9; // eax
  int v10; // eax
  int v11; // eax
  char v12[72]; // [rsp+10h] [rbp-70h] BYREF
  unsigned int v13; // [rsp+58h] [rbp-28h]
  int v14; // [rsp+5Ch] [rbp-24h]
  int v15; // [rsp+60h] [rbp-20h]
  int v16; // [rsp+64h] [rbp-1Ch]
  _BYTE *v17; // [rsp+68h] [rbp-18h]
  int v18; // [rsp+70h] [rbp-10h]
  int i; // [rsp+74h] [rbp-Ch]
  int v20; // [rsp+78h] [rbp-8h]
  int v21; // [rsp+7Ch] [rbp-4h]

  strcpy(v12, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
  v18 = 4 * ((a2 + 2) / 3);
  v17 = malloc(v18 + 1);
  if ( !v17 )
    return 0LL;
  v21 = 0;
  v20 = 0;
  while ( v21 < a2 )
  {
    v3 = v21++;
    v16 = *(unsigned __int8 *)(v3 + a1);
    if ( v21 >= a2 )
    {
      v5 = 0;
    }
    else
    {
      v4 = v21++;
      v5 = *(unsigned __int8 *)(v4 + a1);
    }
    v15 = v5;
    if ( v21 >= a2 )
    {
      v7 = 0;
    }
    else
    {
      v6 = v21++;
      v7 = *(unsigned __int8 *)(v6 + a1);
    }
    v14 = v7;
    v13 = (v15 << 8) + (v16 << 16) + v7;
    v8 = v20++;
    v17[v8] = v12[(v13 >> 18) & 0x3F];
    v9 = v20++;
    v17[v9] = v12[(v13 >> 12) & 0x3F];
    v10 = v20++;
    v17[v10] = v12[(v13 >> 6) & 0x3F];
    v11 = v20++;
    v17[v11] = v12[v13 & 0x3F];
  }
  for ( i = 0; (3 - a2 % 3) % 3 > i; ++i )
    v17[v18 - 1 - i] = 61;
  v17[v18] = 0;
  return v17;
}

这里就是正常的 base64 加密函数实现过程,并未发现替换表的行为,而且给的明文表就是原表

接下来看 encode 函数的实现过程

_BYTE *__fastcall encode(const char *a1)
{
  size_t v1; // rax
  int v2; // eax
  _BYTE *v4; // [rsp+20h] [rbp-20h]
  int i; // [rsp+28h] [rbp-18h]
  int v6; // [rsp+2Ch] [rbp-14h]

  v1 = strlen(a1);
  v4 = malloc(2 * v1 + 1);
  v6 = 0;
  for ( i = 0; i < strlen(a1); ++i )
  {
    v2 = v6++;
    v4[v2] = (a1[i] ^ 0x20) + 16;
  }
  v4[v6] = 0;
  return v4;
}

输入明文后,该函数体对密文进行了 异或后位移 操作

了解了加密过程,下面就是解密脚本

from base64 import b64decode

flag = "VlxRV2t0II8kX2WPJ15fZ49nWFEnj3V8do8hYy9t"
flag = list(b64decode(flag.encode()))
for i,v in enumerate(flag):
    v = chr((v - 16) ^ 0x20)
    flag[i] = v

print(''.join(flag))
#flag{D0_4ou_7now_wha7_ELF_1s?}

4 Endian

题目主要代码如下

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+4h] [rbp-3Ch]
  char *v5; // [rsp+8h] [rbp-38h]
  char v6[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v7; // [rsp+38h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  puts("please input your flag");
  __isoc99_scanf("%s", v6);
  v5 = v6;
  for ( i = 0; i <= 4; ++i )
  {
    if ( *(_DWORD *)v5 != (array[i] ^ 0x12345678) )
    {
      printf("wrong!");
      exit(0);
    }
    v5 += 4;
  }
  printf("you are right");
  return 0;
}

array 是存储 flag 的数字,但是是以小端序存储,每个空间存储了一段16进制,每个值都会去异或 0x12345678 ,解密脚本如下

flag = [0x75553A1E, 0x7B583A03, 0x4D58220C, 0x7B50383D, 0x736B3819]
for i,v in enumerate(flag):
    flag[i] = bytes.fromhex(hex(v ^ 0x12345678)[2:])[::-1].decode();

print(''.join(flag))
#flag{llittl_Endian_a}
#结尾手动补上 `}` 即可

5 EzPE

使用 file 命令无法识别文件,用 010editor 查看发现不是标准的 exe 文件格式,所以手动修复文件头

修复后成功识别,主要代码如下

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+2Ch] [rbp-4h]

  _main(argc, argv, envp);
  puts(&draw);
  puts("Please enter your flag!\n");
  scanf("%s", input);
  for ( i = 0; i < strlen(input) - 1; ++i )
    input[i] ^= i ^ input[i + 1];
  if ( !strcmp(input, data) )
    puts("You Win!");
  else
    puts("You lose!");
  system("pause");
  return 0;
}

对明文逐个异或加密后,对比加密字符串 data ,所以提取 data 的内容后逐个异或即可,这里要注意的是解密的时候方向得从后往前,因为他异或的对象是未加密前的数据本身的后一位,这里最后一位数据是没被修改的

解密脚本如下

flag = [int(i,16) for i in "0A 0C 04 1F 26 6C 43 2D 3C 0C 54 4C 24 25 11 06 05 3A 7C 51 38 1A 03 0D 01 36 1F 12 26 04 68 5D 3F 2D 37 2A 7D".split(' ')]
for i in range(len(flag) - 2,-1,-1):
    flag[i] ^= i ^ flag[i+1]
print(bytearray(flag).decode())
#flag{Y0u_kn0w_what_1s_PE_File_F0rmat}

6 lazy_activtiy

jadx 反编译,在包 com.droidlean.activity_travel 中有 FlagActivity 类,代码如下

public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.layout_2);
        final TextView textView = (TextView) findViewById(R.id.textView2);
        final EditText editText = (EditText) findViewById(R.id.editTextTextPersonName2);
        ((Button) findViewById(R.id.button)).setOnClickListener(new View.OnClickListener() { // from class: com.droidlearn.activity_travel.FlagActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View view) {
                textView.setText(Integer.toString(FlagActivity.access$004(FlagActivity.this)));
                if (FlagActivity.this.cnt >= 10000) {
                    Toast.makeText(FlagActivity.this, editText.getText().toString(), 0).show();
                }
            }
        });
}

很明显,在页面创建的时候,会输出读取某个 editText 的内容并输出,但是这个页面并没有正常显示,被注释掉了,正常显示的是 MainActivity 的页面

AndroidManifest.xml 文件中可以发现这样一句

<activity android:name="com.droidlearn.activity_travel.FlagActivity" android:exported="false"/>

说明 FlagActivity 的页面被关闭了,但是输出的 flag 是通过读取页面上控件的值,所以我们直接所搜 flag{ 关键字即可

android:text="flag{Act1v1ty_!s_so00oo0o_Impor#an#}"

7 Segments

题目的文件名是 shift_f7 ,同时题目名是 Segments ,所以很明显了,用 ida 打开该文件,直接看 Segments 即可

image-20231126101404781

根据语义输出即可

flag{You_ar3_g0od_at_f1nding_ELF_segments_name}

8 咳

查该文件,发现用 UPX 加了壳,使用命令脱壳即可

upx -d ke.exe

去壳后用 ida 查看该文件,主要代码如下

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned __int64 i; // r10
  char *v4; // kr00_8
  char Str1[96]; // [rsp+20h] [rbp-88h] BYREF
  int v7; // [rsp+80h] [rbp-28h]

  _main();
  memset(Str1, 0, sizeof(Str1));
  v7 = 0;
  Hello();
  scanf("%s", Str1);
  for ( i = 0i64; ; ++i )
  {
    v4 = &Str1[strlen(Str1)];
    if ( i >= v4 - Str1 )
      break;
    ++Str1[i];
  }
  if ( !strncmp(Str1, enc, v4 - Str1) )
    puts("WOW!!");
  else
    puts("I believe you can do it!");
  system("pause");
  return 0;
}

加密过程是逐个加1,最后对比密文。解密脚本如下

flag = list("gmbh|D1ohsbuv2bu21ot1oQb332ohUifG2stuQ[HBMBYZ2fwf2~")
for i,v in enumerate(flag):
    flag[i] = chr(ord(v)-1)

print(''.join(flag))
#flag{C0ngratu1at10ns0nPa221ngTheF1rstPZGALAXY1eve1}
0%