avatar

XGCTF-2019年首届Re

0x1 easy re

为了方便大家理解,我附上程序源码
1
2
3
4
5
6
7
8
#include<stdio.h>
#include<Windows.h>
int main() {
char hint[] = "The next chars is flag,you can use base64 to decode it!";
char flag[] = "eGdjdGZ7V2VsY29tZV90b19YR0NURn0=";
::MessageBoxA(0, "快去找flag啦", "xgctf:",0);
return 0;
}
我们现在打开吾爱破解OD,将程序拖入OD中,我们可以看到如下情况

1.png

在左上角的窗口中,右键--------中文搜索引擎------------智能搜索 具体步骤如下

2.png

我们可以查看到这个程序大部分的字符串

3.png

我们看到第二行字符串为
1
The next chars is flag,you can use base64 to decode it!
它告诉我们,下一个字符串就是flag,我们需要用base64解密它
1
eGdjdGZ7V2VsY29tZV90b19YR0NURn0=   //第三行字符串
我们将它进行base64解密即可

4.png

得到flag: xgctf{Welcome_to_XGCTF}

0x2 跳到对的地方

为了方便大家理解,我放上它的源码
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
#include<stdio.h>
#include<Windows.h>
void getflag();
void f3() {
int a = 0;
a = 1 + 1;
a -= 1;
}
void f1() {
int a = 0;
a = 1 + 1;
a -= 1;
}
int main() {
MessageBoxA(0, "flag不在这里啦,你要再仔细找找哦。", "(*^▽^*)", 0);
system("pause");
return 0;
}
void f2() {
int a = 0;
a = 1 + 1;
a -= 1;
}
void getflag() {
//flag{Jump_And_Get_Flag}
char cn_hint[] = "只要代码执行这里就可以得到flag哦";
char en_hint[] = "If you could execute this function,you will get flag!";
char false_flag[] = "xgctf{hdxg_ctf_ctf}";
int flag[] = { 0x78,0x67,0x63,0x74,0x66,0x7b,0x4a,0x75,0x6d,0x70,0x5f,0x41,0x6e,0x64,0x5f,0x47,0x65,0x74,0x5f,0x46,0x6c,0x61,0x67,0x7d };
for (int i = 0; i < 24; i++) {
printf("%c", (char)flag[i]);
}
printf("\n");
}
看到了代码,我们很明显知道main函数中没有调用flag这个函数,但是我们必须要调用他,而且程序中还包含了很多垃圾字符串来误导选手,但是提示说明只要执行了代码就可以得到flag
在汇编语言中,寄存器EIP指向下一个被调用的指令,所以我们可以将EIP改变到getflag函数的头部,然后让它执行即可
接下来,我们打开OD,并导入程序,如下图

5.png

右键----------中文搜索引擎-----------智能搜索 具体操作如下

6.png

于是我们看到如下情况

7.png

我们可以从上面的源代码知道 xgctf{hdxg_ctf_ctf} 是假的flag,根据提示
1
2
3
地址=000919BD
反汇编=mov esi,0x3.00097B70
文本字符串=只要代码执行这里就可以得到flag哦
我们双击 只要代码执行这里就可以得到flag哦 这一行

8.png

就会自动跳转到对应的汇编代码

9.png

一般情况下,一个函数转为机器指令后,由于函数的调用会影响栈,所以函数的头部一般以以下汇编代码开头
1
2
3
push ebp
mov ebp,esp
sub esp,0x1BC
我们将EIP设置在函数头部,即 push ebp
具体操作:选中要跳转到的指令----------右键-------------设置新的EIP

10.png

注意:可能会弹出信息框询问跳转到EIP可能存在危险,点 是 即可,如以下情况
接下来只需单步(按F8一条一条执行指令)

11.png

大家会遇到一个循环,如下

12.png

对于这种循环,我们只需要在jmp指令下一条指令,右键-------断点--------运行到选定位置(F4)

13.png

此时我们回过头来看看程序是什么情况

14.png

得到flag:xgctf{Jump_And_Get_Flag}

0x3简单的XOR

首先大家要理解 A XOR B XOR B = A 的含义。
为了方便大家理解,我附上程序的源代码
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
#include<stdio.h>
#include<stdlib.h>
int main() {
//char flag[25] = "xgctf{This_is_easy_xor!}";
char key[] = "hang_dian_xin_gong_ctf!!";
__int8 result[24]= { 16,6,13,19,57,31,61,9,7,44,39,0,29,0,2,14,29,30,0,27,27,20,0,92 };
char temp[25];
char input[25];
scanf_s("%s", &input,25);
for (int i = 0; i < 24; i++) {
//result[i] = flag[i]^key[i];
temp[i] = (char)(input[i] ^ result[i]);
}
for (int i = 0; i < 24; i++) {
//result[i] = flag[i]^key[i];
if (temp[i] != key[i]) {
printf("You should try again!\n");
system("pause");
return 0;
}
}
printf("The flag is your input!\n");
system("pause");
return 0;
}
我们将程序导入IDA

15.png

选择 下方按钮OK 即可

16.png

然后F5 反编译为伪代码

17.png

由于它是一个C++程序,而不是C语言程序,导入之后不会直接进入main函数,所以我们需要找到main函数
方法有两种,第一种就是在左侧的函数表里慢慢找,第二种方法是针对于C++程序的通用方法

18.png

双击sub_4124A0

19.png

双击sub_412120

20.png

双击sub_412140

21.png

双击 Code = sub_412410(); 的 sub_412410

22.png

双击 sub_4112B7

23.png

双击 sub_411920

#####成功到达真正的main函数
24.png

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
  sub_411221(&unk_41C004);
strcpy(v32, "hang_dian_xin_gong_ctf!!");
v8 = 16;
v9 = 6;
v10 = 13;
v11 = 19;
v12 = 57;
v13 = 31;
v14 = 61;
v15 = 9;
v16 = 7;
v17 = 44;
v18 = 39;
v19 = 0;
v20 = 29;
v21 = 0;
v22 = 2;
v23 = 14;
v24 = 29;
v25 = 30;
v26 = 0;
v27 = 27;
v28 = 27;
v29 = 20;
v30 = 0;
v31 = 92;
sub_411154("%s", (unsigned int)v6);
for ( i = 0; i < 24; ++i )
v7[i] = *(&v8 + i) ^ v6[i];
for ( j = 0; j < 24; ++j )
{
if ( v7[j] != v32[j] )
{
sub_41104B("You should try again!\n", v3);
system("pause");
((void (*)(void))sub_41122B)();
goto LABEL_10;
}
}
sub_41104B("The flag is your input!\n", v3);
system("pause");
((void (*)(void))sub_41122B)();
LABEL_10:
v1 = v0;
sub_41124E(&savedregs, &dword_411AE0, 0);
return sub_41122B((unsigned int)&savedregs ^ v33, v1);
分析伪代码,我们通过 v7[i] = *(&v8 + i) ^ v6[i]; 可知我们输入的数据和v8-v31的数据进行异或,然后存在v7,&v8+i其实是一种数组偏移,然后后面 if ( v7[j] != v32[j] ) 可知v32和v7进行比较,我们不知道v32是什么东西。
我们可以这样思考,我们输入的字符串和另一个字符串进行异或后然后和再一个字符串进行比较,即 A XOR B == C,A是我们输入的,若假设这条式子成立,由于异或的可逆性,正确的A = C XOR B
现在我们需要找到B,带伪代码的开头我们看到如下代码

25.png

1
v32 = "hang_dian_xin_gong_ctf!!"
到这里静态分析完毕
我们要开始写代码直接算出正确的flag
这里我使用python语言,大家的编程语言不受限制
1
2
3
4
5
6
key = [16,6,13,19,57,31,61,9,7,44,39,0,29,0,2,14,29,30,0,27,27,20,0,92]
s = "hang_dian_xin_gong_ctf!!"
temp = ''
for i in range(0,24):
temp += chr(key[i]^ord(s[i]))
print temp
执行结果如下

26.png

得到flag: xgctf{This_is_easy_xor!}

其实用纯OD调试也是可以解出答案的,只需要在 v7[j] != v32[j] 的时候把v32的变量和"hang_dian_xin_gong_ctf!!"从内存中取出然后进行xor运算即可,但是汇编语言代码较多,且较为难懂,这里就不给出纯OD的解法了,但是大家可以尝试一下,这里给出的答案是最容易理解的。

0x3 多密码表

根据提示,可能存在几个序列进行调换,很可能会使用取余的方法
该程序是linux x64位程序,我们选择用ida x64 来静态分析代码

27.png

按F5进行反编译

28.png

点击v15-v9 然后按r,即可转化为字符串

29.png

这是一个有规律的序列,因为它不重复且等长
然后我们分析这一条代码
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
for ( j = 0; j <= 18; ++j )
{
if ( buf[j] <= '@' || buf[j] > 'Z' )
{
if ( buf[j] <= '`' || buf[j] > 'z' )
{
if ( buf[j] <= '/' || buf[j] > '9' )
{
if ( buf[j] == '_' )
v14[j] = '!';
}
else
{
v14[j] = *((_BYTE *)&v7 + buf[j] - 48);
}
}
else
{
v14[j] = *((_BYTE *)&v20 + buf[j] - 97);
}
}
else
{
v14[j] = *((_BYTE *)&v15 + buf[j] - 65);
}
}
直接看中间 if ( buf[j] == ‘_’ ) v14[j] = ‘!’; 是将 _ 换为 !
我们可以改写它的if语句,但是效果不变,比如
1
if ( buf[j] <= '@' || buf[j] > 'Z' ) else {............}
可以改写成
1
2
if(buf[j]>='A' && buf[j] <='Z')
v14[j] = *((_BYTE *)&v15 + buf[j] - 65);
如法炮制,相信大家看懂了代码的意思
若这个字符若是大写就在大写的密码表(即 MNBVCXZASDFGHJKLPOIUYQWERT)里,以该字符的ascii-65作为下标读取上述密码表
若这个字符若是小写就在小写的密码表(即 qeadzcwsxryfhvntgbmlkjuiop)里,以该字符的ascii-97作为下标读取上述密码表
剩余密码表方法雷同
那么我们可以自己写个程序,顺着这个思路把 A-Z a-z 0-9 ! 输入进去,然后按照这个形式,一对一的把 9F!R9k!w3U!tM88W9Od 转化为原文
附上我C++程序
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
#include<iostream>
using namespace std;
int main() {
char input[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!";
char table1[] = "qeadzcwsxryfhvntgbmlkjuiop";
char table2[] = "MNBVCXZASDFGHJKLPOIUYQWERT";
char table3[] = "9517384206";
char table4[] = "!";
char txt[63] = "";
for (int i = 0; i < 63;i++) {
if (input[i] >= 'A' && input[i] <= 'Z') {
txt[i] = table1[input[i] - 65];
printf("%c -----> %c\n", input[i], txt[i]);
continue;
}
if (input[i] >= 'a' && input[i] <= 'z') {
txt[i] = table2[input[i] - 97];
printf("%c -----> %c\n", input[i], txt[i]);
continue;
}
if (input[i] >= '0' && input[i] <= '9') {
txt[i] = table3[input[i] - 48];
printf("%c -----> %c\n", input[i], txt[i]);
continue;
}
if (input[i] == '_') {
txt[i] = table4[0];
printf("%c -----> %c\n", input[i], txt[i]);
continue;
}
}
return 0;
}
根据上面这个程序我们会得到 输入原文 加密为密文 的密码对应表

31.png

我们根据 密文 9F!R9k!w3U!tM88W9Od 一个个对应关系换成原本
换完后为 0k_y0U_G4t_Pa55w0rD ,根据题目要求加上xgctf{}
我们测试一下

32.png

得到flag: xgctf{0k_y0U_G4t_Pa55w0rD}

文章作者: 咲夜南梦
文章链接: http://yoursite.com/2019/03/25/XGCTF-2019%E5%B9%B4%E9%A6%96%E5%B1%8ARe/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 咲夜南梦's 博客
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论