攻防世界-re部分题解(三)

题目还不是很难,所以还是放在一起来写

tt3441810

这题并不知道是在干什么,IDA打开是个dumpfile,打开给了很多16进制,看到了0x68这个经典数字(push指令的编码),后面接了两个字符,看到fl,感觉有问题,找到后面很多个0x68,每个后买你都跟了两个字符,像是把flagpush进栈的操作,所以把这些数据导出,然后写个脚本跑一下,验证猜想

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <cstring>
#include <string>


using namespace std;


int main() {

unsigned char ida_chars[] =
{
0x68, 0x66, 0x6C, 0x00, 0x00, 0x48, 0xBF, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x34, 0x24, 0x48,
0xBA, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48,
0xB8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F,
0x05, 0x68, 0x61, 0x67, 0x00, 0x00, 0x48, 0xBF, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x34, 0x24,
0x48, 0xBA, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0xB8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0x05, 0x68, 0x7B, 0x70, 0x00, 0x00, 0x48, 0xBF, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x34,
0x24, 0x48, 0xBA, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x48, 0xB8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0F, 0x05, 0x68, 0x6F, 0x70, 0x00, 0x00, 0x48, 0xBF,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D,
0x34, 0x24, 0x48, 0xBA, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x48, 0xB8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0F, 0x05, 0x68, 0x70, 0x6F, 0x00, 0x00, 0x48,
0xBF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48,
0x8D, 0x34, 0x24, 0x48, 0xBA, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0xB8, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0x05, 0x68, 0x70, 0x72, 0x00, 0x00,
0x48, 0xBF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0x8D, 0x34, 0x24, 0x48, 0xBA, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0xB8, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0F, 0x05, 0x68, 0x65, 0x74, 0x00,
0x00, 0x48, 0xBF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x48, 0x8D, 0x34, 0x24, 0x48, 0xBA, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xB8, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x05, 0x68, 0x7D, 0x0A,
0x00, 0x00, 0x48, 0xBF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x48, 0x8D, 0x34, 0x24, 0x48, 0xBA, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xB8, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x05, 0x48, 0x31,
0xFF, 0x48, 0xB8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0F, 0x05
};
int len = sizeof(ida_chars)/ sizeof(ida_chars[0]);
string flag;
int i =0;
while(i!=len) {
if(ida_chars[i]==0x68)
{
while(ida_chars[++i]!=0x00)
{
flag+=ida_chars[i];
}
}
else
{
i++;
}
}
cout<<flag<<endl;
return 0;
}

输出flag

1
flag{poppopret}

提交的时候只需要中间的部分

re2-cpp-is-awesome

这题有很多string类,所以还是要慢慢分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char *v3; // rbx
__int64 v4; // rax
__int64 v5; // rdx
__int64 v6; // rax
__int64 v7; // rdx
__int64 v8; // rdx
__int64 v9; // rdx
__int64 i; // [rsp+10h] [rbp-60h]
char v12; // [rsp+20h] [rbp-50h]
char v13; // [rsp+4Fh] [rbp-21h]
__int64 v14; // [rsp+50h] [rbp-20h]
int v15; // [rsp+5Ch] [rbp-14h]

if ( a1 != 2 )
{
v3 = *a2;
v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Usage: ", a3);
v6 = std::operator<<<std::char_traits<char>>(v4, v3, v5);
std::operator<<<std::char_traits<char>>(v6, " flag\n", v7);
exit(0);
}
std::allocator<char>::allocator(&v13, a2, a3);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&v12, a2[1], &v13);
std::allocator<char>::~allocator(&v13);
v15 = 0;
for ( i = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(&v12); ; sub_400D7A(&i) )
{
v14 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(&v12);
if ( !sub_400D3D((__int64)&i, (__int64)&v14) )
break;
v9 = *(unsigned __int8 *)sub_400D9A((__int64)&i);
if ( (_BYTE)v9 != off_6020A0[dword_6020C0[v15]] )
sub_400B56((__int64)&i, (__int64)&v14, v9);
++v15;
}
sub_400B73((__int64)&i, (__int64)&v14, v8);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&v12);
return 0LL;
}

真正有用的内容只有一个for循环,在这之前我们输入的字符串被传入了v12,然后用迭代器进行循环,遍历整个字符串,每个字符被赋值给了v9,然后进行判断,如果判断可以通过,我们输入的就是正确的flag,判断的条件是v9 = off_6020A0[dword_6020C0[v15]],一个嵌套索引,把数据导出之后很容易得到结果

1
2
3
4
5
6
7
8
target = "L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t"
dword_6020C0 = [36, 0, 5, 54, 101, 7, 39, 38, 45, 1, 3, 0, 13, 86, 1, 3, 101, 3, 45, 22, 2, 21, 3, 101, 0, 41, 68, 68,
1, 68, 43]
flag = ''
for i in dword_6020C0:
# print(i)
flag += target[i]
print(flag)

输出结果

1
ALEXCTF{W3_L0v3_C_W1th_CL45535}

流浪者

这题是MFC,题目中的函数很多,打开发现输入有错误提示,还是从字符串入手,找到了几个很有用的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BOOL __cdecl sub_4017F0(int a1)
{
BOOL result; // eax
char Str1[28]; // [esp+D8h] [ebp-24h]
int v3; // [esp+F4h] [ebp-8h]
int v4; // [esp+F8h] [ebp-4h]

v4 = 0;
v3 = 0;
while ( *(_DWORD *)(a1 + 4 * v4) < 62 && *(_DWORD *)(a1 + 4 * v4) >= 0 )
{
Str1[v4] = alphabet[*(_DWORD *)(a1 + 4 * v4)];
++v4;
}
Str1[v4] = 0;
if ( !strcmp(Str1, "KanXueCTF2019JustForhappy") )
result = pass();
else
result = fail();
return result;
}

更改了一些函数和变量名,更容易辨认,这个函数传入了a1之后,对a1之后的地址上的内容作为索引值依次连接组成了一个新的字符串,所以a1显然应该是一个地址,或者说是一个数组的首地址,而这个数组的内容我们很容易就可以得到,接着再看到底在哪里引用了这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int __thiscall sub_401890(CWnd *this)
{
struct CString *v1; // ST08_4
CWnd *v2; // eax
int v3; // eax
int v5[26]; // [esp+4Ch] [ebp-74h]
int i; // [esp+B4h] [ebp-Ch]
char *Str; // [esp+B8h] [ebp-8h]
CWnd *v8; // [esp+BCh] [ebp-4h]

v8 = this;
v1 = (CWnd *)((char *)this + 100);
v2 = CWnd::GetDlgItem(this, 1002);
CWnd::GetWindowTextA(v2, v1);
v3 = sub_401A30((char *)v8 + 100);
Str = CString::GetBuffer((CWnd *)((char *)v8 + 100), v3);
if ( !strlen(Str) )
return CWnd::MessageBoxA(v8, &Qingshuru, 0, 0);
for ( i = 0; Str[i]; ++i )
{
if ( Str[i] > 57 || Str[i] < 48 )
{
if ( Str[i] > 122 || Str[i] < 97 )
{
if ( Str[i] > 90 || Str[i] < 65 )
fail();
else
v5[i] = Str[i] - 29;
}
else
{
v5[i] = Str[i] - 87;
}
}
else
{
v5[i] = Str[i] - 48;
}
}
return sub_4017F0((int)v5);
}

这个函数也很简单,把输入的内容赋给Str,然后对str里面的字符进行一个变换,把变换后的数组传给刚刚分析的那个函数,所以想要逆向解出输入的字符就很简单了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
str1 = 'KanXueCTF2019JustForhappy'
alphabet = 'abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ'
a1 = []
for i in str1:
j = alphabet.index(i)
a1.append(j)
# print(a1)
flag = ''
for i in a1:
if 0 <= i <= 9:
flag += chr(i + 48)
elif i <= 35:
flag += chr(i + 87)
else:
flag += chr(i + 29)
print(flag)

根据题目要求把输出的内容套上flag

1
flag{j0rXI4bTeustBiIGHeCF70DDM}

easyRE1

毫无意义的题目,打开就能看到flag,套上flag{}直接交上去就行了

debug

题如其名,peid打开发现是c#,所以直接dnspy打开,发现flag是直接计算出来的,并且没有进行任何的反调试,所以直接调试运行几步就得到了flag

1
flag{967DDDFBCD32C1F53527C221D9E40A0B}

Guess-the-Number

这题是java逆向,cfr反编译之后,查看源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.io.PrintStream;
import java.math.BigInteger;

public class guess {
static String XOR(String _str_one, String _str_two) {
BigInteger i1 = new BigInteger(_str_one, 16);
BigInteger i2 = new BigInteger(_str_two, 16);
BigInteger res = i1.xor(i2);
String result = res.toString(16);
return result;
}

public static void main(String[] args) {
block5: {
int guess_number = 0;
int my_num = 349763335;
int my_number = 1545686892;
int flag = 345736730;
if (args.length > 0) {
try {
guess_number = Integer.parseInt(args[0]);
if (my_number / 5 == guess_number) {
String str_one = "4b64ca12ace755516c178f72d05d7061";
String str_two = "ecd44646cfe5994ebeb35bf922e25dba";
my_num += flag;
String answer = guess.XOR(str_one, str_two);
System.out.println("your flag is: " + answer);
break block5;
}
System.err.println("wrong guess!");
System.exit(1);
}
catch (NumberFormatException e) {
System.err.println("please enter an integer \nexample: java -jar guess 12");
System.exit(1);
}
} else {
System.err.println("wrong guess!");
int num = 1000000;
++num;
System.exit(1);
}
}
}
}

程序很简单,我们当然可以根据算法来算出来flag的值,但是完全可以得到需要输入的数,所以直接运行就好了

1
>java -jar Guess-the-Number.jar 309137378

输出结果

1
your flag is: a7b08c546302cc1fd2a4d48bf2bf2ddb

直接提交即可

EASYHOOK

这题很有意思,值得好好分析一下,IDA打开,找到main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
HANDLE v4; // eax
DWORD NumberOfBytesWritten; // [esp+4h] [ebp-24h]
char Buffer; // [esp+8h] [ebp-20h]

puts((int)aPleaseInputFla);
scanf(a31s, &Buffer);
if ( strlen(&Buffer) == 19 )
{
sub_401220();
v4 = CreateFileA(FileName, 0x40000000u, 0, 0, 2u, 0x80u, 0);
WriteFile(v4, &Buffer, 19u, &NumberOfBytesWritten, 0);
sub_401240(&Buffer, &NumberOfBytesWritten);
if ( NumberOfBytesWritten == 1 )
puts((int)aRightFlagIsYou);
else
puts((int)aWrong);
system(aPause);
result = 0;
}
else
{
puts((int)aWrong);
system(aPause);
result = 0;
}
return result;
}

第一反应当然是查看一下sub_401240()的内容,毕竟这是最后一个调用NumberOfBytesWritten的函数,而这个值,经过writeFile()之后应该是19

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
signed int __cdecl sub_401240(const char *a1, _DWORD *a2)
{
signed int result; // eax
unsigned int v3; // kr04_4
char v4[24]; // [esp+Ch] [ebp-18h]

result = 0;
strcpy(v4, "This_is_not_the_flag");
v3 = strlen(a1) + 1;
if ( (signed int)(v3 - 1) > 0 )
{
while ( v4[a1 - v4 + result] == v4[result] )
{
if ( ++result >= (signed int)(v3 - 1) )
{
if ( result == 21 )
{
result = (signed int)a2;
*a2 = 1;
}
return result;
}
}
}
return result;
}

看到This_is_not_the_flag感觉有些不对,仔细观察发现这个字符串有20的字符,而下面的判断需要我们的输入和这个字符串相等,但是我们只输入19个字符,所以这个函数永远不可能返回我们想要的结果,肯定是有什么东西被漏掉了,所以返回main,前面还有一个sub_401220()没有看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int sub_401220()
{
HMODULE v0; // eax
DWORD v2; // eax

v2 = GetCurrentProcessId();
hProcess = OpenProcess(0x1F0FFFu, 0, v2);
v0 = LoadLibraryA(LibFileName);
dword_40C9C4 = (int)GetProcAddress(v0, unk_40A05C);// 找到writefile的地址
lpAddress = (LPVOID)dword_40C9C4;
if ( !dword_40C9C4 )
return puts((int)&dword_40A044);
unk_40C9B4 = *(_DWORD *)lpAddress;
*((_BYTE *)&unk_40C9B4 + 4) = *((_BYTE *)lpAddress + 4);
byte_40C9BC = 0xE9u;
dword_40C9BD = (char *)sub_401080 - (char *)lpAddress - 5;
return sub_4010D0();
}

在这里发现了问题,这里找到了储存writefile()地址的位置,然后保存前五个字节的内容,后面进行了一些赋值,看到0xE9就发现这里想要修改writefile()函数地址,跳转到另一个函数sub_401080(),然后函数进入sub_4010D0()

1
2
3
4
5
6
7
8
9
10
BOOL sub_4010D0()
{
DWORD v1; // [esp+4h] [ebp-8h]
DWORD flOldProtect; // [esp+8h] [ebp-4h]

v1 = 0;
VirtualProtectEx(hProcess, lpAddress, 5u, 4u, &flOldProtect);
WriteProcessMemory(hProcess, lpAddress, &byte_40C9BC, 5u, 0);
return VirtualProtectEx(hProcess, lpAddress, 5u, flOldProtect, &v1);
}

这个函数把刚刚的修改写进了相应的地址,此时程序一旦调用writefile(),就会跳转到sub_401080(),所以转到这个函数

1
2
3
4
5
6
7
8
9
10
11
int __stdcall sub_401080(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
{
signed int v5; // ebx

v5 = sub_401000((int)lpBuffer, nNumberOfBytesToWrite);
sub_401140();
WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
if ( v5 )
*lpNumberOfBytesWritten = 1;
return 0;
}

如果sub_401000()返回值是1,就会输出我们的输入是正确的,sub_401140()是用来恢复writefile()函数的地址,就不再赘述,所以重点就是sub_401000()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
signed int __cdecl sub_401000(int a1, signed int a2)
{
char v2; // al
char v3; // bl
char v4; // cl
int v5; // eax

v2 = 0;
if ( a2 > 0 )
{
do
{
if ( v2 == 18 )
{
*(_BYTE *)(a1 + 18) ^= 0x13u;
}
else
{
if ( v2 % 2 )
v3 = *(_BYTE *)(v2 + a1) - v2;
else
v3 = *(_BYTE *)(v2 + a1 + 2);
*(_BYTE *)(v2 + a1) = v2 ^ v3;
}
++v2;
}
while ( v2 < a2 );
}
v4 = 0;
if ( a2 <= 0 )
return 1;
v5 = 0;
while ( aAjygkfmSv8mln[v5] == *(_BYTE *)(v5 + a1) )
{
v5 = ++v4;
if ( v4 >= a2 )
return 1;
}
return 0;
}

这里的操作就很简单了,也不需要再多说,我们的输入经过处理后如果和内存中的字符串相同,就对了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

using namespace std;

int main() {

char str[20] =
"ajygkFm.\x7F_~-SV{8mLn";
char a1[20] = {0};
for (int i = 0; i < 19; i++) {
if (i == 18) {
a1[i] = str[18] ^ 0x13;

}
if (i % 2) {
a1[i] = (str[i] ^ i) + i;
} else {
a1[i + 2] = (str[i] ^ i);
}
}
a1[0]='f';
cout << a1 << endl;
}

这里有一点需要注意,这个程序是没法检测第一位输入的,中间的过程也完全没有用到第一位,所以根据输出结果手动添加了’f’,输出

1
flag{Ho0k_w1th_Fun}

可以验证一下,第一位其实并不影响结果

EASYHOOK-1

发现第一位改成其他的字符也是正确的

reverse-for-the-holy-grail-350

这题还算是比较简单的题目,很容易就可以找到关键函数,然后发现所有的数据处理和验证全部都在这个函数里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
__int64 __fastcall stringMod(__int64 *a1)
{
__int64 v1; // r9
__int64 v2; // r10
__int64 i; // rcx
signed int v4; // er8
int *v5; // rdi
int *v6; // rsi
signed int v7; // ecx
signed int v8; // er9
int v9; // er10
unsigned int v10; // eax
int v11; // esi
int v12; // esi
int v14[24]; // [rsp+0h] [rbp-60h]
int _48[24]; // [rsp+48h] [rbp-18h]

memset(v14, 0, 0x48uLL);
v1 = a1[1];
if ( v1 )
{
v2 = *a1;
i = 0LL;
v4 = 0;
do
{
v12 = *(char *)(v2 + i);
v14[i] = v12;
if ( 3 * ((unsigned int)i / 3) == (_DWORD)i && v12 != firstchar[(unsigned int)i / 3] )// 3的倍数等于firstchar
v4 = -1;
++i;
}
while ( i != v1 );
}
else
{
v4 = 0;
}
v5 = v14;
v6 = v14;
v7 = 666;
do
{
*v6 = v7 ^ *(unsigned __int8 *)v6;
v7 += v7 % 5;
++v6;
}
while ( _48 != v6 );
v8 = 1;
v9 = 0;
v10 = 1;
v11 = 0;
do
{
if ( v11 == 2 )
{
if ( *v5 != thirdchar[v9] )
v4 = -1;
if ( v10 % *v5 != masterArray[v9] )
v4 = -1;
++v9;
v10 = 1;
v11 = 0;
}
else
{
v10 *= *v5;
if ( ++v11 == 3 )
v11 = 0;
}
++v8;
++v5;
}
while ( v8 != 19 );
return (unsigned int)(v7 * v4);
}

发现把输入分成了三个部分,处理起来也很简单,写脚本处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>

using namespace std;

int main() {
int firstchar[8] =
{65, 105, 110, 69, 111, 97};
int thirdchar[8] =
{751, 708, 732, 711, 734, 764};
int masterArray[6] =
{471, 12, 580, 606, 147, 108};
int xor_[24] = {0};
char flag[24] = {0};
xor_[0] = 666;
for (int i = 1; i < 24; i++) {
xor_[i] = xor_[i - 1] + xor_[i - 1] % 5;
}
for (int i = 0; i < 6; i++) {
flag[3 * i] = firstchar[i];
flag[3 * i + 2] = thirdchar[i] ^ xor_[3 * i + 2];
for (int j = 48; j < 122; j++) {
if ((j >= 48 && j <= 57) || (j >= 65 && j <= 90) || (j >= 97 && j <= 122)) {
int tmp = j ^xor_[3 * i + 1];
if (tmp * (flag[3 * i] ^ xor_[3 * i]) % thirdchar[i] == masterArray[i]) {
flag[3 * i + 1] = j;
break;
}
} else
continue;
}
}

cout << flag << endl;

}

输出flag,再套上main里面提供的格式即可

1
tuctf{AfricanOrEuropean?}

android-app-100

反编译之后发现还调用了c的库,于是IDA打开so文件,看到了混淆,但是内容比较好猜,所以直接找到字符串计算md5或者在apk里面提交就好了

1
Sharif_CTF(833489ef285e6fa80690099efc5d9c9d)

dmd-50

打开直接找到输入的flag的md5值,md5解密找到flag

1
b781cbb29054db12f88f08c6e161c199

Windows_Reverse1

这道题很有意思,首先查壳,发现是upx,直接脱壳,IDA打开,F5查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp+4h] [ebp-804h]
char v5; // [esp+5h] [ebp-803h]
char v6; // [esp+404h] [ebp-404h]
char Dst; // [esp+405h] [ebp-403h]

v6 = 0;
memset(&Dst, 0, 0x3FFu);
v4 = 0;
memset(&v5, 0, 0x3FFu);
printf("please input code:");
scanf("%s", &v6);
sub_401000(&v6);
if ( !strcmp(&v4, "DDCTF{reverseME}") )
printf("You've got it!!%s\n", &v4);
else
printf("Try again later.\n");
return 0;
}

表面上看我们需要输入一个字符串,然后经过sub_401000的变换之后变成DDCTF{reverseME},然后查看一下加密函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int __cdecl sub_401000(const char *a1)
{
_BYTE *v1; // ecx
unsigned int v2; // edi
unsigned int result; // eax
int v4; // ebx

v2 = 0;
result = strlen(a1);
if ( result )
{
v4 = a1 - v1;
do
{
*v1 = byte_402FF8[(char)v1[v4]];
++v2;
++v1;
result = strlen(a1);
}
while ( v2 < result );
}
return result;
}

看起来是一个换表操作,但是在这里遇到了两个让人疑惑的事情,第一个,我们输入的字符串是怎么传入这个函数里面来的,第二个,在0x402FF8处什么都没有,这个表在哪里

所以还是需要仔细看看汇编代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.text:004010A4				   lea     edx, [esp+824h+var_404]
.text:004010AB push edx
.text:004010AC push offset aS ; "%s"
.text:004010B1 call ds:scanf
.text:004010B7 lea eax, [esp+82Ch+var_404]
.text:004010BE push eax
.text:004010BF lea ecx, [esp+830h+var_804]
.text:004010C3 call sub_401000

.text:00401000 sub_401000 proc near ; CODE XREF: _main+73↓p
.text:00401000
.text:00401000 arg_0 = dword ptr 4
.text:00401000
.text:00401000 push ecx
.text:00401001 push ebp
.text:00401002 mov ebp, [esp+8+arg_0]
.text:00401006 push esi
.text:00401007 mov eax, ebp
.text:00401009 push edi
.text:0040100A xor edi, edi
.text:0040100C lea esi, [eax+1]
.text:0040100F nop

这里应该是人为修改了代码,可以看到我们输入的字符串地址被赋给了ecx,然后再我们的解密函数最开始,先把ecx里面的值压进了栈,所以这个函数用了ecx寄存器来传参,而这是个32位的程序,所以IDA这里检测的时候出现了一点问题,第一个问题解决了,再看看第二个,先来研究里面的索引值

1
2
3
4
5
6
7
8
9
10
11
12
if ( result )
{
v4 = a1 - v1;
do
{
*v1 = byte_402FF8[(char)v1[v4]];
++v2;
++v1;
result = strlen(a1);
}
while ( v2 < result );
}

v1[v4]这个表达形式可以看得出来就是a1的首地址,而a1就是我们输入的字符串,都是可见字符,所以每个字符的值都要大于32,所以在0x402FF8这个位置向下偏移至少32的位置寻找,找到了真正的字母表

分析结束,可以直接动手逆向了,不过在导出这个字母表的时候很凑巧的发现这个字母表的值刚好是从126-32的排列,这说明我们输入的和变换出来的值相加正好为158,这就提供了一个更简单的逆向思路

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <string>

using namespace std;

int main() {
string target = "DDCTF{reverseME}";
string flag = "";
for (int i = 0; i < target.length(); i++) {
flag += 158 - target[i];
}
cout << flag << endl;
}

输出套上flag即可

1
ZZ[JX#,9(9,+9QY!

babymips

这个题本身没有什么,很简单的算法,也没有什么加密、加壳,主要是有些受不了IDA+Retdec反汇编出来的代码,所以试了一下Ghidra

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void FUN_004009a8(void)

{
int iVar1;
int iStack48;
byte abStack44 [36];

setbuf(stdout,(char *)0x0);
setbuf(stdin,(char *)0x0);
printf("Give me your flag:");
scanf("%32s",abStack44);
iStack48 = 0;
while (iStack48 < 0x20) {
abStack44[iStack48] = abStack44[iStack48] ^ 0x20U - (char)iStack48;
iStack48 = iStack48 + 1;
}
iVar1 = strncmp((char *)abStack44,_fdata,5);
if (iVar1 == 0) {
FUN_004007f0(abStack44);
}
else {
puts("Wrong");
}
return;
}

首先是输入flag,然后进行一个变换,先比较前5位,如果一致进入下一个变换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void FUN_004007f0(char *param_1)

{
size_t sVar1;
int iVar2;
uint uStack16;

uStack16 = 5;
while (sVar1 = strlen(param_1), uStack16 < sVar1) {
if ((uStack16 & 1) == 0) {
param_1[uStack16] =
(byte)((uint)((int)param_1[uStack16] << 0x1a) >> 0x18) | param_1[uStack16] >> 6;
}
else {
param_1[uStack16] =
param_1[uStack16] >> 2 | (byte)((uint)((int)param_1[uStack16] << 0x1e) >> 0x18);
}
uStack16 = uStack16 + 1;
}
iVar2 = strncmp(param_1 + 5,PTR_DAT_00410d04,0x1b);
if (iVar2 == 0) {
puts("Right!");
}
else {
puts("Wrong!");
}
return;
}

后面对奇数位和偶数位进行了两个不同的位运算,很清晰,仔细分析一下会发现实际上是前后位置的一个交换,并且奇偶运算就是互为逆运算,逆向的时候更方便处理了。最后是进行简单的字符串比较,然后程序就结束了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <string>

using namespace std;

int main() {
string target = "Q|j{g\x52\xfd\x16\xa4\x89\xbd\x92\x80\x13\x41\x54\xa0\x8d\x45\x18\x81\xde\xfc\x95\xf0\x16\x79\x1a\x15\x5b\x75\x1f";
string flag = "";
for (int i = 0; i < target.length(); i++) {
if(i>=5)
{
int32_t tmp=target[i];
if(i%2==0)
{
tmp=((tmp&0x3)<<6)|((tmp&0xfc)>>2);
}
else
{
tmp=((tmp&0xc0)>>6)|((tmp&0x3f)<<2);
}
flag+=(tmp^(0x20-i));
}
else
{
flag+=(target[i]^(0x20-i));
}

}
cout << flag << endl;
}

需要注意的两点,运算符优先级的问题,加减运算要先于移位运算先于位运算,处理的时候需要注意,还有,移位的时候要注意把多余的位置清0,这里用的是与运算

输出flag

1
qctf{ReA11y_4_B@89_mlp5_4_XmAn_}
攻防世界-re部分题解(四) 攻防世界-re部分题解(二)

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×