11. getit
进入环境,下载附件,使用exeinfo Exe打开,发现没有加壳,如图:
解法1. 静态分析,得到答案
我们直接将文件丢入IDA,找到main函数,F5反编译,查看代码,如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v3; // al
__int64 v5; // [rsp+0h] [rbp-40h]
int i; // [rsp+4h] [rbp-3Ch]
FILE *stream; // [rsp+8h] [rbp-38h]
char filename[8]; // [rsp+10h] [rbp-30h]
unsigned __int64 v9; // [rsp+28h] [rbp-18h]
v9 = __readfsqword(0x28u);
LODWORD(v5) = 0; # 令V5=0
while ( (signed int)v5 < strlen(s) )
{
if ( v5 & 1 )
v3 = 1;
else
v3 = -1;
*(&t + (signed int)v5 + 10) = s[(signed int)v5] + v3; # 对t字符的内容进行改写,与v3内容有关
LODWORD(v5) = v5 + 1; # v5++
}
strcpy(filename, "/tmp/flag.txt"); # 创建一个flag.txt文件
stream = fopen(filename, "w");
fprintf(stream, "%s\n", u, v5);
for ( i = 0; i < strlen(&t); ++i ) # 依次将t字符写入文件中
{
fseek(stream, p[i], 0);
fputc(*(&t + p[i]), stream);
fseek(stream, 0LL, 0);
fprintf(stream, "%s\n", u);
}
fclose(stream);
remove(filename);
return 0;
}
可以看到,上面的代码主要是针对v3得到的值存入t中,再对t写入文件操作,那么t就是最终的flag值!我们尝试双击t字符,查看内容,如图:
我们右键,将53h转成字符,如图;
同理查看字符s,如图:
那么我们可以知道,字符s和t分别为:
t = SharifCTF{????????????????????????????????}
s = c61b68366edeb7bdce3c6820314b7498
接着代码复现:
#include <stdio.h>
#include <string.h>
int main()
{
char s[] = "c61b68366edeb7bdce3c6820314b7498";
char t[] = "SharifCTF{????????????????????????????????}";
int v5 = 0;
char v3;
while(v5 < strlen(s)){
if (v5 & 1)
v3 = 1;
else
v3 = -1;
*(t + v5 + 10) = s[v5] + v3;
v5++;
}
printf("%s\n",t);
return 0;
}
最终flag为:SharifCTF{b70c59275fcfa8aebf2d5911223c6589}
解法2. 通过IDA的patch功能
经过上述代码分析,发现运行文件没有的原因是最后将文件通过remove命令删除了,而且在for循环里,通过fprintf函数将u,也就是一堆*******符号写入flag文件。那么我们先找到这两处地方,如图:
找到位置后,通过小手轻轻的触动鼠标的右键,如图:
将其内容替换成nop,如图:
将其文件导出,并扔进kali中,通过./文件,运行更改后的文件。
最后我们可以通过命令得到结果:
cat /tmp/flag.txt
结果如图:
12. maze
下载附件,直接用IDA打开,查看内容,找到main函数,F5反编译,得到代码,如图:
一大堆代码,我们逐一分析:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
const char *v3; // rsi
signed __int64 v4; // rbx
signed int v5; // eax
char v6; // bp
char v7; // al
const char *v8; // rdi
__int64 v10; // [rsp+0h] [rbp-28h]
v10 = 0LL;
puts("Input flag:");
scanf("%s", &s1, 0LL); // s1保存输入的字符
if ( strlen(&s1) != 24 || (v3 = "nctf{", strncmp(&s1, "nctf{", 5uLL)) || *(&byte_6010BF + 24) != 125 ) // 可以知道s1的长度为24,且s1的前5位为nctf{
{
LABEL_22:
puts("Wrong flag!");
exit(-1);
}
v4 = 5LL; // v4的值为5的长整型(也就是int64)
if ( strlen(&s1) - 1 > 5 ) // 为什么是len(s1) - 1,其实是为了保证最后一个}符号,相当于截取nctf{?}中的?信息
{
while ( 1 )
{
v5 = *(&s1 + v4); // 取v5 = s1[5]
v6 = 0;
if ( v5 > 78 ) // 78的ascii为N,(实在搞不懂这个判断逻辑有毛用)
{
v5 = (unsigned __int8)v5;
if ( (unsigned __int8)v5 == 79 ) // 如果v5的值为79(也就是为'O'时候,进行左移)
{
v7 = sub_400650((char *)&v10 + 4, v3); // sub_400650进行操作:1. v10--,表示位置的列数-1; 2. 返回边界判断,是否越界
goto LABEL_14;
}
if ( v5 == 111 ) // 如果v5的值为111(也就是为'o'时候,进行右移)
{
v7 = sub_400660((char *)&v10 + 4, v3); // sub_400660进行操作:1. v10++,表示位置的列数+1; 2. 返回边界判断,是否越界
goto LABEL_14;
}
}
else
{
v5 = (unsigned __int8)v5; // 如果v5的值为46(也就是为'.'时候,进行上移)
if ( (unsigned __int8)v5 == 46 )
{
v7 = sub_400670(&v10, v3); // sub_400670进行操作:1. v10--,表示位置的行数-1; 2. 返回边界判断,是否越界
goto LABEL_14;
}
if ( v5 == 48 ) // 如果v5的值为48(也就是为'0'时候,进行下移)
{
v7 = sub_400680(&v10, v3); // sub_400680进行操作:1. v10++,表示位置的列数+1; 2. 返回边界判断,是否越界
LABEL_14:
v6 = v7; // 将v7的值赋给v6,用于是否越界判断
goto LABEL_15;
}
}
LABEL_15:
v3 = (const char *)HIDWORD(v10);
if ( !(unsigned __int8)sub_400690(asc_601060, HIDWORD(v10), (unsigned int)v10) ) //判断该位置的字符是' ', '#'或者'*'中的哪种,如果是'*'则,返回再次寻路(这儿代码很让我头疼)
goto LABEL_22;
if ( ++v4 >= strlen(&s1) - 1 ) // v4用于控制中间的24 - 5 - 1 = 18个字符(去除nctf{})
{
if ( v6 )
break;
LABEL_20:
v8 = "Wrong flag!";
goto LABEL_21;
}
}
}
if ( asc_601060[8 * (signed int)v10 + SHIDWORD(v10)] != 35 ) // //判断是否到达'#'处,没有到达,继续返回循环,进行移动
goto LABEL_20;
v8 = "Congratulations!";
LABEL_21:
puts(v8);
return 0LL;
}
同时我们双击asc_601060变量,得到的是一串字符串,如下:
.data:0000000000601060 asc_601060 db ' ******* * **** * **** * *** *# *** *** *** *********',0
再来看看难顶的函数:
__int64 __fastcall sub_400690(__int64 a1, int a2, int a3)
{
__int64 result; // rax
result = *(unsigned __int8 *)(a1 + a2 + 8LL * a3); //a1是迷宫,a2是列,a3是行。通过行列计算迷宫对应位置的字符
LOBYTE(result) = (_DWORD)result == 32 || (_DWORD)result == 35; //如果是' '或者'#‘返回TRUE,是'*’返回FALSE
return result;
}
00******
*000*00*
***0*0**
**00*0**
*00*#00*
**0***0*
**00000*
********
起点(1,1),只能走0,要走到‘#’,找到路径右下右右下下左下下下右右右右上上左左
则对应的代码中字符为:o0oo00O000oooo…OO
带上前置括号,最终的字符为:nctf{o0oo00O000oooo…OO}
这个题感觉看的稀里糊涂的,后面再回头重做一下,代码还是太难阅读了。