6. re1
进入环境,下载附件后发现给出的是一个exe文件,运行一下如图:
6.1 查看文件
我们用Exeinfo PE打开查看文件信息,如图:
没有加壳,丢入IDA Pro中:
6.2 反编译
对mian函数进行反编译(这个反编译好坑,一定要用IDA32位的程序打开才行,MD搞了好久)
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
__int128 v5; // [esp+0h] [ebp-44h]
__int64 v6; // [esp+10h] [ebp-34h]
int v7; // [esp+18h] [ebp-2Ch]
__int16 v8; // [esp+1Ch] [ebp-28h]
char v9; // [esp+20h] [ebp-24h]
_mm_storeu_si128((__m128i *)&v5, _mm_loadu_si128((const __m128i *)&xmmword_413E34));
v7 = 0;
v6 = qword_413E44;
v8 = 0;
printf("欢迎来到DUTCTF呦\n");
printf("这是一道很可爱很简单的逆向题呦\n");
printf("输入flag吧:");
scanf("%s", &v9);
v3 = strcmp((const char *)&v5, &v9);
if ( v3 )
v3 = -(v3 < 0) | 1;
if ( v3 )
printf(aFlag_0);
else
printf((const char *)&unk_413E90);
system("pause");
return 0;
}
6.3 代码分析
if ( v3 )
printf(aFlag_0);
else
printf((const char *)&unk_413E90);
如果v3为TRUE,则输出aFlag_0,双击aFlag_0,跳转到位置如图:
仅下面一行右键点击S+,让其变成字符显示,如图:
与dos框的结果对应上了耶!接下来查看unk_413E90段对应的内容,同样双击后跳转到对应位置,如图:
显示flag get,也就是说需要让函数走到else执行,即v3值为FALSE。也就是v3的值为0。再来分析代码:
v3 = strcmp((const char *)&v5, &v9);
if ( v3 )
v3 = -(v3 < 0) | 1;
strcmp(str1,str2),若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数。让v3等于0,避免了if语句的执行,还能跳转到正确结果!猜测题意上让我们输入的v9,实现 v5==v9。继续分析代码:
_mm_storeu_si128((__m128i *)&v5, _mm_loadu_si128((const __m128i *)&xmmword_413E34));
可以看到v5值存在上述指令,_mm_loadu_si128表示读取xmmword_413E34,_mm_storeu_si128(des,src)表示将src的值存入des中,即v5的值为xmmword_413E34对应的变量。双击变量,如图:
我们将其变成字符形式,如图:
咦,内容缺失啊!再往下看看那一串dq,尝试也解析一下,如图:
注:这里要跟大家普及一个知识了,及大端与小端
假设一个十六进制数0x12345678
大端的存储方式是:12,34,56,78,然后读取的时候也是从前往后读
小端的存储方式是:78,56,34,12,然后读取的时候是从后往前读取
特么的转换一下:最后的flag应该是:
DUTCTF{We1c0met0DUTCTF}
为啥多了一个小端模式,我是没看懂。这个题是真的难,记录一步步操作,希望能收获进步!
7. game
7.1 解法1
下载附件后,发现是一个exe文件,让玩游戏,如图:
初始灯都是关闭状态,要让所有灯亮起来,且8个灯中选择第i个灯时候,都会影响前一个和后一个灯的状态,经过尝试,其实是一个取非的逻辑门,我们依次输入:
1 -> 3 -> 5 -> 7 -> 2 -> 4 -> 6 -> 8
结果如图:
最终答案为:
zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}
7.2 解法二
使用Exeinfo PE打开,如图:
纯粹exe文件,没有任何加壳,于是,使用IDA Pro打开文件,找到main函数,如图:
按F5进行反编译,如图:
双击main_0进入函数,如图:
红色区域表示所有的8个灯状态全为1时候,执行sub_457AB4函数,双击进入:
继续双击进入sub_45E940函数,如图:
可以看到函数做了两个异或(xor)运算,xor的逆运算也是xor,那么我们就可以根据这个运算来写脚本得到最后的flag:
data1 = [18, 64, 98, 5, 2, 4, 6, 3, 6, 48, 49, 65, 32, 12, 48, 65, 31, 78, 62, 32, 49, 32, 1, 57, 96, 3, 21, 9, 4, 62,
3, 5, 4, 1, 2, 3, 44, 65, 78, 32, 16, 97, 54, 16, 44, 52, 32, 64, 89, 45, 32, 65, 15, 34, 18, 16, 0]
data2 = [123, 32, 18, 98, 119, 108, 65, 41, 124, 80, 125, 38, 124, 111, 74, 49, 83, 108, 94, 108, 84, 6, 96, 83, 44,
121, 104, 110, 32, 95, 117, 101, 99, 123, 127, 119, 96, 48, 107, 71, 92, 29, 81, 107, 90, 85, 64, 12, 43, 76,
86, 13, 114, 1, 117, 126, 0]
assert len(data1) == len(data2)
flag = ''
for i in range(len(data1)):
data1[i] ^= data2[i]
data1[i] ^= 0x13
flag += chr(data1[i])
print(flag)
最终结果为:
zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}
8. Hello, CTF
给出了一个exe文件,打开如图:
使用IDA Pro打开文件,找到main函数,按F5进行反编译,得到代码如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
signed int v3; // ebx
char v4; // al
int result; // eax
int v6; // [esp+0h] [ebp-70h]
int v7; // [esp+0h] [ebp-70h]
char v8; // [esp+12h] [ebp-5Eh]
char v9[20]; // [esp+14h] [ebp-5Ch]
char v10; // [esp+28h] [ebp-48h]
__int16 v11; // [esp+48h] [ebp-28h]
char v12; // [esp+4Ah] [ebp-26h]
char v13; // [esp+4Ch] [ebp-24h]
strcpy(&v13, "437261636b4d654a757374466f7246756e");
while ( 1 )
{
memset(&v10, 0, 0x20u);
v11 = 0;
v12 = 0;
sub_40134B(aPleaseInputYou, v6);
scanf(aS, v9);
if ( strlen(v9) > 0x11 )
break;
v3 = 0;
do
{
v4 = v9[v3];
if ( !v4 )
break;
sprintf(&v8, asc_408044, v4);
strcat(&v10, &v8);
++v3;
}
while ( v3 < 17 );
if ( !strcmp(&v10, &v13) )
sub_40134B(aSuccess, v7);
else
sub_40134B(aWrong, v7);
}
sub_40134B(aWrong, v7);
result = stru_408090._cnt-- - 1;
if ( stru_408090._cnt < 0 )
return _filbuf(&stru_408090);
++stru_408090._ptr;
return result;
}
代码分析:
if ( !strcmp(&v10, &v13) )
sub_40134B(aSuccess, v7);
else
sub_40134B(aWrong, v7);
我们看到,要执行sub_40134B函数,双击aSuccess,如图:
可以看到输出success,如果输入错误,则输出wrong。那么可以看到需要让函数执行到sub_40134B(aSuccess, v7);那么需要让v10 == v13。
scanf(aS, v9);
if ( strlen(v9) > 0x11 )
break;
v3 = 0;
do{
v4 = v9[v3];
if ( !v4 )
break;
sprintf(&v8, asc_408044, v4);
strcat(&v10, &v8);
++v3;
}
while ( v3 < 17 );
0x11表示17,输入v9,如果长度大于17则停止,do…while循环则是每次拿输入的v9的一个字符,将v4转换成16进制(asc_408044对应代码段字符为x%)赋值v8,再将v8值粘贴在v10后面。
strcpy(&v13, "437261636b4d654a757374466f7246756e");
表示v13的值为437261636b4d654a757374466f7246756e,但是字符长度大于17,尝试将其转换为ascii字符:
print(bytes.fromhex("437261636b4d654a757374466f7246756e"))
得到字符:CrackMeJustForFun,小黑框弹出success!
因此最终的答案为:
CrackMeJustForFun
9. no-strings-attached
进入环境,下载附件,扔进winhex发现是elf文件,直接搜flag并没有发现任何有用的信息。遂直接扔进IDA中进行查看。直接找到main函数,F5进行反编译,如图:
双击setlocal函数,如图:
是一个extern的外部函数,并没有什么实质作用。再看banner函数:
返回的变量如图:
其实就是做了个输出而已。再看函数prompt_authentication:
也只是做了个输出而已。再看authenticate:
发现到,返回变量unk_8048B44和unk_8048BA4,双击对应的字符分别为:success和access deny,如图:
要执行if函数,那么需要w2和s2要相同,那么
s2 = decrypt(&s, &dword_8048A90);
函数至关重要。
这里需要GDB动态调试。第一次接触,才学。在kali环境下,首先需要安装gdb,如何安装?
接着我们将下载的文件复制到kali环境下,重命名文件为st,用gdb将文件加载到内存中
gdb st
接下来我们需要设置运行的断点,因为需要了解到decrypt后的值,那么我们使用命令
b decrypt
如图:
设置好断点后,我们让其run起来,直接输入r,如果提示权限不足,我们先修改权限
chmod 777 st # 修改权限
其次输入r去让程序debug运行。如图:
接着,我们在断点处进入函数运行,输入n,代表next,下一步的意思,如图:
接着我们需要找到s的值,如何找?在ida中找到关键信息,如图
可以看到调用decrypt函数的汇编代码,其接下来的mov指令将值送入eax寄存器中。那么先看看寄存器,输入info reg,如图:
可以看到eflags相关的寄存器的地址,那么读取其数据:
复制所有的数字,并转成字符,脚本如下:
data = [57, 52, 52, 55, 123, 121, 111, 117, 95, 97, 114, 101, 95, 97, 110, 95, 105, 110, 116, 101, 114, 110, 97, 116,
105, 111, 110, 97, 108, 95, 109, 121, 115, 116, 101, 114, 121, 125]
flag = ''
for i in range(len(data)):
flag += chr(data[i])
print(flag)
最终答案为:
9447{you_are_an_international_mystery}
最后,这道题是真的难哎。。。
10. csaw2013reversing2
下载附件,使用exeinfo检查,没有套壳。双击打开文件,一堆乱码,如图:
老规矩,丢入ida中,找到main函数,F5反编译,查看执行:
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // ecx
CHAR *lpMem; // [esp+8h] [ebp-Ch]
HANDLE hHeap; // [esp+10h] [ebp-4h]
hHeap = HeapCreate(0x40000u, 0, 0);
lpMem = (CHAR *)HeapAlloc(hHeap, 8u, MaxCount + 1);
memcpy_s(lpMem, MaxCount, &unk_409B10, MaxCount);
if ( sub_40102A() || IsDebuggerPresent() )
{
__debugbreak();
sub_401000(v3 + 4, lpMem);
ExitProcess(0xFFFFFFFF);
}
MessageBoxA(0, lpMem + 1, "Flag", 2u);
HeapFree(hHeap, 0, lpMem);
HeapDestroy(hHeap);
ExitProcess(0);
}
可以看到,MessageBoxA(0, lpMem + 1, “Flag”, 2u);是弹出的对话窗口,但是弹窗是乱码,那么继续上看,
if ( sub_40102A() || IsDebuggerPresent() )
IsDebuggerPresent()这个函数看起来就是判断是否处于调试器中,满足条件后,进入执行语句。再来看看sub_40102A()函数
int sub_40102A()
{
char v0; // t1
v0 = *(_BYTE *)(*(_DWORD *)(__readfsdword(0x18u) + 48) + 2);
return 0;
}
返回了0,而且不是调试模式,那么if函数中两个逻辑都是FALSE。因此执行逻辑是跳过了if直接显示了弹出框。我们再看看if里面的内容:
__debugbreak(); 函数是Microsoft 专用,将在代码中引起断点,并在其中提示用户运行调试程序。我们继续双击进入sub_401000()函数:
unsigned int __fastcall sub_401000(int a1, int a2)
{
int v2; // esi
unsigned int v3; // eax
unsigned int v4; // ecx
unsigned int result; // eax
v2 = dword_409B38;
v3 = a2 + 1 + strlen((const char *)(a2 + 1)) + 1;
v4 = 0;
result = ((v3 - (a2 + 2)) >> 2) + 1;
if ( result )
{
do
*(_DWORD *)(a2 + 4 * v4++) ^= v2;
while ( v4 < result );
}
return result;
}
看起来是个加密函数,应该和flag有关,将v2进行一系列操作后返回,然后退出,弹出flag。然而事实上并未显示flag,所以我们猜测这个函数并没有被执行,想办法让其执行呗。
解法一:使用IDA的Keypatch功能
我们看看函数执行流程图,如图:
暂定两个区域way1和way2,函数走下来后,正常进行判断语句,执行到call ds:IsDebuggerPresent,way1是未执行if语句的流程,way2是执行if后里面的流程,那么我们可以尝试修改执行流程,思路为:
- 想办法让程序执行way2
- 其次通过way2后再弹窗显示flag
最终逻辑从左图变成右图:
如何改变运行流程?此处参考bilibili的博主Keypatch如何使用 右键要改变的位置:
int3时IsDebuggerPresent触发后的中断操作,将其nop,同时修改程序执行的跳转地址,如图:
完事后,将程序重新输出,如图:
最终成功将源程序的执行流程修改,得到的结果如图:
最终答案为:
flag{reversing_is_not_that_hard!}
解法二:使用OllyDbg
先在论坛上下载OllyDbg:http://www.ollydbg.net/,下载如图:
随后使用管理员权限打开exe,我们将程序拖入到OllyDbg中,如图:
该工具和IDA的Patch功能类似,都是通过修改程序的执行顺序来达到运行出flag的目的。在程序中,我们需要搜索定位到flag(如何搜索,这功能第一次用贼基把难用!!!参考链接:https://jingyan.baidu.com/article/48b37f8ddaf0b11a6464880d.html)
在右侧可以看到IsDebuggerPresent函数,那么执行顺序就可以看到了,双击定位到如图位置,修改数据:
修改完三处后,如图:
程序执行位置已经更改,重新运行(快捷键F9),如图: