2022CTF培训(七)逆向专项练习
创始人
2024-03-06 12:11:57
0

附件下载链接

babyre

首先是一个迷宫,由于答案不唯一,因此到 dfs 求出所有路径。

#include constexpr char s[] = "**************.****.**s..*..******.****.***********..***..**..#*..***..***.********************.**..*******..**...*..*.*.**.*";int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int n = std::strlen(s);int p = -1;for (int i = 0; i < n; i++) {if (s[i] == '#') {p = i;break;}}std::queue q;std::vector vis(n);std::string ans;std::function dfs = [&](int x) {if (x == p) {std::cout << ans << std::endl;return;}for (auto[d, c]:{std::make_pair(-5, 'w'), {5, 's'}, {1, 'd'}, {-1, 'a'}, {25, 'x'}, {-25, 'y'}}) {int y = x + d;if (y < 0 || y >= n || s[y] == '*' || vis[y]) {continue;}vis[y] = true;ans.push_back(c);dfs(y);ans.pop_back();vis[y] = false;}};vis[22] = true;dfs(22);return 0;
}

求得路径如下:

saxssd
saxsds
saxdss
saxdsasd
sxss
sxsasd
sxassd
sxasds
ddwwxxssxaxwwwasasasyywwdwwdss
ddwwxxssxaxwwwasasasyywwdwwydxss
ddwwxxssxaxwwwasasasyywwdwds
ddwwxxssxaxwwwasasasyywwdd
ddwwxxssxaxwwaasasyywwdwwdss
ddwwxxssxaxwwaasasyywwdwwydxss
ddwwxxssxaxwwaasasyywwdwds
ddwwxxssxaxwwaasasyywwdd
ddwwxxsdwwdwwdss
ddwwxxsdwwdwwydxss
ddwwxxsdwwdwds
ddwwxxsdwwdd
ddwwxxdwdwwdss
ddwwxxdwdwwydxss
ddwwxxdwdwds
ddwwxxdwdd

第二部分加密函数前半段没用后面每次取 4 次 6 比特转成 3 个 8 比特存在 a2 中,猜测是 base64 。

  while ( v11 > 0 ){v7 -= v14[*v13] == '@';v6 = v14[*v13] & 0x3F | (v6 << 6);if ( ++v8 == 4 ){v8 = 0;if ( v7 ){v2 = v10++;a2[v2] = BYTE2(v6);}if ( v7 > 1 ){v3 = v10++;a2[v3] = BYTE1(v6);}if ( v7 > 2 ){v4 = v10++;a2[v4] = v6;}}++v13;--v11;}

观察 v14 发现是 base64 码表的反向映射,确认是 base64 。因此该部分输入为 c2N0Zl85MTAy
在这里插入图片描述
第三部分求逆,解得答案为 g4lf_si_u_0s!y1g

#include 
#include int dword_55B52EE01940[280] = {214, 144, 233, 254, 204, 225, 61, 183, 22, 182, 20, 194, 40, 251, 44, 5, 43, 103, 154, 118, 42, 190, 4, 195, 170, 68, 19, 38, 73, 134, 6, 153, 156, 66, 80, 244, 145, 239, 152, 122, 51, 84, 11, 67, 237, 207, 172, 98, 228, 179, 28, 169, 201, 8, 232, 149, 128, 223, 148, 250, 117, 143, 63, 166, 71, 7, 167, 252, 243, 115, 23, 186, 131, 89, 60, 25, 230, 133, 79, 168, 104, 107, 129, 178, 113, 100, 218, 139, 248, 235, 15, 75, 112, 86, 157, 53, 30, 36, 14, 94, 99, 88, 209, 162, 37, 34, 124, 59, 1, 33, 120, 135, 212, 0, 70, 87, 159, 211, 39, 82, 76, 54, 2, 231, 160, 196, 200, 158, 234, 191, 138, 210, 64, 199, 56, 181, 163, 247, 242, 206, 249, 97, 21, 161, 224, 174, 93, 164, 155, 52, 26, 85, 173, 147, 50, 48, 245, 140, 177, 227, 29, 246, 226, 46, 130, 102, 202, 96, 192, 41, 35, 171, 13, 83, 78, 111, 213, 219, 55, 69, 222, 253, 142, 47, 3, 255, 106, 114, 109, 108, 91, 81, 141, 27, 175, 146, 187, 221, 188, 127, 17, 217, 92, 65, 31, 16, 90, 216, 10, 193, 49, 136, 165, 205, 123, 189,45, 116, 208, 18, 184, 229, 180, 176, 137, 105, 151, 74, 12, 150, 119, 126, 101, 185, 241, 9, 197, 110, 198, 132, 24, 240, 125, 236, 58, 220, 77, 32, 121, 238, 95, 62, 215, 203, 57, 72, 198, 186, 177, 163, 80, 51, 170, 86, 151, 145, 125, 103, 220, 34, 112, 178, 0, 0, 0, 0, 0, 0, 0, 0
};unsigned int __ROL4__(unsigned int x, int i) {return (x << i) | (x >> (32 - i));
}unsigned int __ROR4__(unsigned int x, int i) {return (x >> i) | (x << (32 - i));
}unsigned int sub_55B52EE01464(unsigned int a1) {int v2; // [rsp+18h] [rbp-498h]v2 = (dword_55B52EE01940[BYTE2(a1)] << 16) | dword_55B52EE01940[(unsigned __int8) a1] | (dword_55B52EE01940[BYTE1(a1)] << 8) | (dword_55B52EE01940[(a1 >> 24) & 0xFF] << 24);return __ROL4__(v2, 12) ^ (unsigned int) (__ROL4__(v2, 8) ^ __ROR4__(v2, 2)) ^ __ROR4__(v2, 6);
}unsigned int sub_55B52EE0143B(unsigned int a1, unsigned int a2, unsigned int a3, unsigned int a4) {return a1 ^ (unsigned int) sub_55B52EE01464(a2 ^ a3 ^ a4);
}int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int v9[16];v9[0] = 190;v9[1] = 4;v9[2] = 6;v9[3] = 128;v9[4] = 197;v9[5] = 175;v9[6] = 118;v9[7] = 71;v9[8] = 159;v9[9] = 204;v9[10] = 64;v9[11] = 31;v9[12] = 216;v9[13] = 191;v9[14] = 146;v9[15] = 239;unsigned int v10[30];v10[29] = v9[15] | (v9[14] << 8) | (v9[13] << 16) | (v9[12] << 24);v10[28] = v9[11] | (v9[10] << 8) | (v9[9] << 16) | (v9[8] << 24);v10[27] = v9[7] | (v9[6] << 8) | (v9[5] << 16) | (v9[4] << 24);v10[26] = v9[3] | (v9[2] << 8) | (v9[1] << 16) | (v9[0] << 24);for (int i = 25; i >= 0; i--) {v10[i] = sub_55B52EE0143B(v10[i + 4], v10[i + 1], v10[i + 2], v10[i + 3]);}for (int i = 0; i < 4; i++) {_byteswap_ulong(v10[i]);}std::string s(16, 0);for (int i = 0, j = 0; i < 16; i += 4, j++) {s[i] = v10[j] >> 24 & 0xFF;s[i + 1] = v10[j] >> 16 & 0xFF;s[i + 2] = v10[j] >> 8 & 0xFF;s[i + 3] = v10[j] & 0xFF;}std::cout << s << std::endl;return 0;
}

把所有合法的 flag 交一遍,可得flag为sctf{ddwwxxssxaxwwaasasyywwdd-c2N0Zl85MTAy(fl4g_is_s0_ug1y!)}

Activity

盲猜 FlagActivity 类中的 onClick 方法是打印 flag 的。

public void onClick(View arg3) {((TextView)this.findViewById(0x7F080181)).setText(Integer.toString(FlagActivity.access$004(FlagActivity.this)));  // id:textView2if(FlagActivity.this.cnt >= 10000) {Toast.makeText(FlagActivity.this, ((EditText)this.findViewById(0x7F080097)).getText().toString(), 0).show();  // id:editTextTextPersonName2}
}

将资源导出,搜索 0x7F080097 得资源名称
在这里插入图片描述
再搜索资源名称,text 属性即为 flag 。
在这里插入图片描述

R2c3

通过 ida 调试,将 shellcode 解密

__int64 __fastcall judge(char *a1)
{char v2[5]; // [rsp+8h] [rbp-20h] BYREFchar v3[9]; // [rsp+Dh] [rbp-1Bh] BYREFint 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 )a1[i] ^= i;for ( i = 0; i <= 13; ++i ){if ( a1[i] != v2[i] )return 0LL;}return 1LL;
}

解密得 flag 为 flag{n1c3_j0b}

#include int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::string s;s = std::string("fmcd") + char(127) + "k7d;V`;np";for (int i = 0; i <= 13; i++) {s[i] ^= i;}std::cout << s << std::endl;return 0;
}

ezre

程序加壳。IDA 在壳代码末尾跳转处下断点然后动态调试运行到该处,此时搜索字符串即可定位到关键函数:

__int64 sub_7FF7B5681C70()
{char *v0; // rdi__int64 i; // rcxchar v3[32]; // [rsp+0h] [rbp-20h] BYREF__int64 v4; // [rsp+20h] [rbp+0h] BYREFchar input[228]; // [rsp+30h] [rbp+10h] BYREFint v6; // [rsp+114h] [rbp+F4h]__int64 v7; // [rsp+1E8h] [rbp+1C8h]v0 = v3;for ( i = 126i64; i; --i ){*(_DWORD *)v0 = -858993460;v0 += 4;}sub_7FF7B568108C(&unk_7FF7B5692008);printf("<printf("Wrong.\n\n");exit(0);}v6 = AlwaysReturn0(&qword_7FF7B568D0C0[78], v3);if ( v6 ){if ( v6 == 1 ){printf("Wrong.\n\n");exit(0);}printf("Right!\n\n");exit(0);}EncryptAndCheckContext(input, 20i64);sub_7FF7B5681375((__int64)v3, (__int64)&unk_7FF7B568AE80);return sub_7FF7B56810E1((unsigned __int64)&v4 ^ v7);
}

首先在 CheckLengthAndEnctypt 函数逻辑如下:

__int64 __fastcall sub_7FF7B56817A0(char *input)
{_DWORD *v1; // rdi__int64 i; // rcx_BYTE v4[36]; // [rsp+0h] [rbp-20h] BYREFint v5; // [rsp+24h] [rbp+4h]v1 = v4;for ( i = 66i64; i; --i )*v1++ = -858993460;sub_7FF7B568108C(&unk_7FF7B5692008);v5 = AlwaysReturn0((__int64)&qword_7FF7B568D0C0[46], (__int64)v4);if ( v5 )return v5 == 20;elsereturn sub_7FF7B5681230(input);
}

虽然不清楚 AlwaysReturn0 函数有什么作用,但通过分析汇编可知该函数无论参数如何始终返回 0 ,因此紧接着调用 sub_7FF7B5681230 函数。sub_7FF7B5681230 函数逻辑如下:

__int64 __fastcall sub_7FF7B5681840(char *input)
{__int64 *v1; // rdi__int64 i; // rcx__int64 v4; // [rsp+0h] [rbp-20h] BYREFunsigned int j; // [rsp+24h] [rbp+4h]v1 = &v4;for ( i = 66i64; i; --i ){*(_DWORD *)v1 = -858993460;v1 = (__int64 *)((char *)v1 + 4);}sub_7FF7B568108C(&unk_7FF7B5692008);j = strlen(input);if ( j != 20 )return CheckDebug(&qword_7FF7B568D0C0[46], j);for ( j = 0; (int)j < 36; ++j )input[j] ^= 0x66u;return CheckDebug(&qword_7FF7B568D0C0[46], 20i64);
}

这里叫做 CheckDebug 函数是因为单步跟进去程序会在执行到某一步时退出。
分析该函数逻辑发现,函数首先判断输入长度是否为 20 字节,根据实际调试发现,只有等于 20 字节的时候才不会在进入 CheckDebug 后直接退出。同时也可以发现输入被逐字节异或了 0x66 。

回到 sub_7FF7B5681C70 函数,如果输入长度正确则在执行完 CheckLengthAndEnctypt 函数后会执行 AlwaysReturn0 函数,因为该函数返回 0,因此接下来执行的是 EncryptAndCheckContext 函数,该函数逻辑如下:

__int64 __fastcall sub_7FF7B56819B0(char *input, int len)
{__int64 *v2; // rdi__int64 i; // rcx__int64 v5; // [rsp+0h] [rbp-20h] BYREFunsigned __int64 j; // [rsp+28h] [rbp+8h]v2 = &v5;for ( i = 66i64; i; --i ){*(_DWORD *)v2 = -858993460;v2 = (__int64 *)((char *)v2 + 4);}sub_7FF7B568108C(&unk_7FF7B5692008);for ( j = 0i64; j < len; ++j )check((unsigned int)j, (input[j] + 10) ^ 0x50u);return CheckDebug((__int64)&qword_7FF7B568D0C0[78], 2i64);
}

可以看出,该函数对输入逐字节加 10 并异或 0x50 ,然后调用 check 函数对其进行检查,check 函数逻辑如下:

__int64 __fastcall sub_7FF7B5681920(int index, int input_i)
{__int64 *v2; // rdi__int64 i; // rcx__int64 result; // rax__int64 v5; // [rsp+0h] [rbp-20h] BYREFv2 = &v5;for ( i = 58i64; i; --i ){*(_DWORD *)v2 = -858993460;v2 = (__int64 *)((char *)v2 + 4);}sub_7FF7B568108C(&unk_7FF7B5692008);result = index;if ( DATA[index] != input_i )return CheckDebug((__int64)&qword_7FF7B568D0C0[78], 1i64);return result;
}

通过调试可知,只要不满足 DATA[index] != input_i 则程序会立即退出。
根据上述分析可以编写解密程序如下:

#include constexpr unsigned char DATA[20] = {75, 72, 121, 19, 69, 48, 92, 73, 90, 121, 19, 112, 109, 120, 19, 111, 72, 93, 100, 100};int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::string flag(20, 0);for (int i = 0; i < 20; i++) {flag[i] = ((DATA[i] ^ 0x50) - 10) ^ 0x66;}std::cout << flag << std::endl;return 0;
}

运行得 flag:why_m0dify_pUx_SheLL

oldRE

程序加壳,调试得关键函数如下:

int sub_401000()
{void (__cdecl *printf)(char *); // esiint v2; // eaxchar input[52]; // [esp+4h] [ebp-38h] BYREFmemset(input, 0, 50);printf = (void (__cdecl *)(char *))::printf;::printf(aPleaseInputFla);gets_s(input, 44);if ( strlen(input) == 42 ){v2 = 0;while ( (input[v2] ^ KEY[v2 % 16]) == DATA[v2] ){if ( ++v2 >= 42 ){printf(aRight);return 0;}}printf(aError);return 0;}else{printf(aError);return -1;}
}

写出对应的解密程序可得 flag 为 flag{59b8ed8f-af22-11e7-bb4a-3cf862d1ee75}

#include constexpr unsigned int DATA[42] = {18, 4, 8, 20, 36, 92, 74, 61, 86, 10, 16, 103, 0, 65, 0, 1, 70, 90, 68, 66, 110, 12, 68, 114, 12, 13, 64, 62, 75, 95, 2, 1, 76, 94, 91, 23, 110, 12, 22, 104, 91, 18};
constexpr unsigned char KEY[16] = {0x74, 0x68, 0x69, 0x73, 0x5F, 0x69, 0x73, 0x5F, 0x6E, 0x6F, 0x74, 0x5F, 0x66, 0x6C, 0x61, 0x67};int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::string flag(42, 0);for (int i = 0; i < 42; i++) {flag[i] = DATA[i] ^ KEY[i % 16];}std::cout << flag << std::endl;return 0;
}

codeRE

0x080487BF 地址有一处花指令:
在这里插入图片描述
0x080487F3 地址有一处花指令:
在这里插入图片描述
0x08048812 地址有一处花指令,不过不能简单的把跳过的字节修改成 nop ,因为后面会用到这里的数据。
在这里插入图片描述
0x08048851 地址有一处花指令。
在这里插入图片描述
花指令跳转之后会变成一个死循环
在这里插入图片描述
通过在 OD 中模拟这部分花指令也可以确定,这部分花指令确实是一个死循环。
在这里插入图片描述
最终还原出的关键函数逻辑如下:

unsigned int sub_80487C4()
{size_t len; // eaxsize_t v1; // eaxunsigned __int8 *md5; // [esp+14h] [ebp-74h]char s[20]; // [esp+18h] [ebp-70h] BYREFunsigned int v5; // [esp+7Ch] [ebp-Ch]v5 = __readgsdword(0x14u);memset(s, 0, sizeof(s));read(0, s, 0x14u);if ( !strncmp(s, "F1@gA", 5u) ){puts("You are very close! Now patch me~");if ( true ){while ( 1 );}len = strlen(s);md5 = MD5(&s[1], len, 0);dump(md5, 0x10u);}else{v1 = strlen(s);dump((unsigned __int8 *)s, v1 - 1);}fflush(stdout);return __readgsdword(0x14u) ^ v5;
}

根据程序中的提示(玄学)应当将死循环 patch 掉然后输入 F1@gA 即可输出 flag 内容。
运行时可能会缺少 libcrypto.so.1.0.0 。由于是 32 位程序,因此该库的路径应为 /usr/lib/i386-linux-gnu/libcrypto.so.1.0.0 。如果没有则考虑安装该库:

sudo apt install libssl1.0.0:i386

或者 ldd 查看该文件依赖的库路径是否正确,如果不正确则使用 patchelf 更改依赖库的路径。
运行结果如下:
在这里插入图片描述

babyAndroid

关键函数为 CheckString

public void onClick(View v) {if(cyberpeace.CheckString(((EditText)MainActivity.this.findViewById(0x7F070031)).getText().toString()) == 1) {  // id:editTextToast.makeText(MainActivity.this, "验证通过!", 1).show();return;}Toast.makeText(MainActivity.this, "验证失败!", 1).show();
}

该函数位于 libcyberpeace.so 中,内容如下:

_BOOL4 __cdecl Java_com_testjava_jack_pingan2_cyberpeace_CheckString(int a1, int a2, int a3)
{size_t len; // edichar *input; // esisize_t cur; // edichar v6; // alchar v7; // alsize_t v8; // edichar v9; // alconst char *v11; // [esp+18h] [ebp-14h]v11 = (const char *)(*(int (__cdecl **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);len = strlen(v11);input = (char *)malloc(len + 1);memset(&input[len], 0, len != -1);memcpy(input, v11, len);if ( strlen(input) >= 2 ){cur = 0;do{v6 = input[cur];input[cur] = input[cur + 16];input[cur++ + 16] = v6;}while ( cur < strlen(input) >> 1 );}v7 = *input;if ( *input ){*input = input[1];input[1] = v7;if ( strlen(input) >= 3 ){v8 = 2;do{v9 = input[v8];input[v8] = input[v8 + 1];input[v8 + 1] = v9;v8 += 2;}while ( v8 < strlen(input) );}}return strcmp(input, "f72c5a36569418a20907b55be5bf95ad") == 0;
}

写出对应的解密函数:

#include int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::string s = "f72c5a36569418a20907b55be5bf95ad";for (int i = 0; i < s.size(); i += 2) {std::swap(s[i], s[i + 1]);}for (int i = 0; i < s.size() / 2; i++) {std::swap(s[i], s[i + 16]);}std::cout << s << std::endl;return 0;
}

ezAndroid

类 b 中的 a 方法这两句反编译反了,原本应该是模拟类似循环移位的操作。
在这里插入图片描述
根据代码逻辑分析可知,每个字节的生成都仅与输入的前缀有关,且输入前缀越长生成的序列也越长。
因此可以把反编译后的代码复制下来,然后逐字节爆破。部分代码如下,具体见附件。


public class MainActivity {private static char a(String f, b tb, a ta) {return ta.a(tb.a(f));}static int a(String arg1) {return MainActivity.b(arg1);}private static int b(String arg8) {arg8 = "flag{" + arg8 + "}";int v0 = 0;String flag = arg8.substring(5, arg8.length() - 1);b tb = new b(2);a ta = new a(3);StringBuilder v3 = new StringBuilder();int v1 = 0;while (v0 < flag.length()) {v3.append(MainActivity.a(flag.charAt(v0) + "", tb, ta));if (tb.b() / 25 > v1 && tb.b() / 25 >= 1) {++v1;}++v0;}String ss = v3.toString();String tt = "wigwrkaugala";for (int i = 0; i < Math.min(ss.length(), tt.length()); i++) {if (ss.charAt(i) != tt.charAt(i)) {return i;}}return Math.min(ss.length(), tt.length());}//    static String charSet = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";static String charSet="abcdefghijklmnopqrstuvwxyz";private static void dfs(String s, int l) {if (l == 12) {System.out.println("flag{" + s + "}");}for (int i = 0; i < charSet.length(); i++) {String t = s + charSet.charAt(i);int nl = a(t);if (nl > l) {dfs(t, nl);}}}public static void main(String[] args) {dfs("", 0);}
}

如果仅枚举小写字母则合法的 flag 如下:

flag{veniviaivici}
flag{veniviaivicr}
flag{veniviaivkci}
flag{veniviaivkcr}
flag{venividivici}
flag{venividivicr}
flag{venividivkci}
flag{venividivkcr}

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...