附件下载链接
babyre
首先是一个迷宫,由于答案不唯一,因此到 dfs 求出所有路径。
#include <bits/stdc++.h>
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<int> q;
std::vector<int> vis(n);
std::string ans;
std::function<void(int)> 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 <bits/stdc++.h>
#include <wsdxml.h>
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:textView2
if(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] 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 )
a1[i] ^= i;
for ( i = 0; i <= 13; ++i )
{
if ( a1[i] != v2[i] )
return 0LL;
}
return 1LL;
}
解密得 flag 为 flag{n1c3_j0b}
#include <bits/stdc++.h>
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; // rcx
char v3[32]; // [rsp+0h] [rbp-20h] BYREF
__int64 v4; // [rsp+20h] [rbp+0h] BYREF
char input[228]; // [rsp+30h] [rbp+10h] BYREF
int 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("<<Input your flag: \n");
scanf("%200s", input);
if ( !(unsigned int)CheckLengthAndEnctypt(input) )
{
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] BYREF
int 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;
else
return 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] BYREF
unsigned 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] BYREF
unsigned __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] BYREF
v2 = &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 <bits/stdc++.h>
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 *); // esi
int v2; // eax
char input[52]; // [esp+4h] [ebp-38h] BYREF
memset(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 <bits/stdc++.h>
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; // eax
size_t v1; // eax
unsigned __int8 *md5; // [esp+14h] [ebp-74h]
char s[20]; // [esp+18h] [ebp-70h] BYREF
unsigned 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:editText
Toast.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; // edi
char *input; // esi
size_t cur; // edi
char v6; // al
char v7; // al
size_t v8; // edi
char v9; // al
const 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 <bits/stdc++.h>
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}