2023NewStarCTF Week1 WP(Reverse)
前段时间参加了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
即可
根据语义输出即可
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}