天天看点

xctf攻防世界 REVERSE 薪手练习区(二)

6. re1

进入环境,下载附件后发现给出的是一个exe文件,运行一下如图:

xctf攻防世界 REVERSE 薪手练习区(二)

6.1 查看文件

我们用Exeinfo PE打开查看文件信息,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

没有加壳,丢入IDA Pro中:

xctf攻防世界 REVERSE 薪手练习区(二)

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,跳转到位置如图:

xctf攻防世界 REVERSE 薪手练习区(二)

仅下面一行右键点击S+,让其变成字符显示,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

与dos框的结果对应上了耶!接下来查看unk_413E90段对应的内容,同样双击后跳转到对应位置,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

显示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对应的变量。双击变量,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

我们将其变成字符形式,如图:

xctf攻防世界 REVERSE 薪手练习区(二)
xctf攻防世界 REVERSE 薪手练习区(二)

咦,内容缺失啊!再往下看看那一串dq,尝试也解析一下,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

注:这里要跟大家普及一个知识了,及大端与小端

假设一个十六进制数0x12345678

大端的存储方式是:12,34,56,78,然后读取的时候也是从前往后读

小端的存储方式是:78,56,34,12,然后读取的时候是从后往前读取

特么的转换一下:最后的flag应该是:​

​DUTCTF{We1c0met0DUTCTF}​

为啥多了一个小端模式,我是没看懂。这个题是真的难,记录一步步操作,希望能收获进步!

7. game

7.1 解法1

下载附件后,发现是一个exe文件,让玩游戏,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

初始灯都是关闭状态,要让所有灯亮起来,且8个灯中选择第i个灯时候,都会影响前一个和后一个灯的状态,经过尝试,其实是一个取非的逻辑门,我们依次输入:

1 -> 3 -> 5 -> 7 -> 2 -> 4 -> 6 -> 8      

结果如图:

xctf攻防世界 REVERSE 薪手练习区(二)

最终答案为:​

​zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}​

7.2 解法二

使用Exeinfo PE打开,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

纯粹exe文件,没有任何加壳,于是,使用IDA Pro打开文件,找到main函数,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

按F5进行反编译,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

双击main_0进入函数,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

红色区域表示所有的8个灯状态全为1时候,执行sub_457AB4函数,双击进入:

xctf攻防世界 REVERSE 薪手练习区(二)

继续双击进入sub_45E940函数,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

可以看到函数做了两个异或(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文件,打开如图:

xctf攻防世界 REVERSE 薪手练习区(二)

使用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,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

可以看到输出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进行反编译,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

双击setlocal函数,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

是一个extern的外部函数,并没有什么实质作用。再看banner函数:

xctf攻防世界 REVERSE 薪手练习区(二)

返回的变量如图:

xctf攻防世界 REVERSE 薪手练习区(二)

其实就是做了个输出而已。再看函数prompt_authentication:

xctf攻防世界 REVERSE 薪手练习区(二)

也只是做了个输出而已。再看authenticate:

xctf攻防世界 REVERSE 薪手练习区(二)

发现到,返回变量unk_8048B44和unk_8048BA4,双击对应的字符分别为:success和access deny,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

要执行if函数,那么需要w2和s2要相同,那么​

​s2 = decrypt(&s, &dword_8048A90);​

​函数至关重要。

这里需要GDB动态调试。第一次接触,才学。在kali环境下,首先需要安装gdb,如何安装?

接着我们将下载的文件复制到kali环境下,重命名文件为st,用gdb将文件加载到内存中

gdb st      
xctf攻防世界 REVERSE 薪手练习区(二)

接下来我们需要设置运行的断点,因为需要了解到decrypt后的值,那么我们使用命令

b decrypt      

如图:

xctf攻防世界 REVERSE 薪手练习区(二)

设置好断点后,我们让其run起来,直接输入r,如果提示权限不足,我们先修改权限

chmod 777 st    # 修改权限      

其次输入r去让程序debug运行。如图:

xctf攻防世界 REVERSE 薪手练习区(二)

接着,我们在断点处进入函数运行,输入n,代表next,下一步的意思,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

接着我们需要找到s的值,如何找?在ida中找到关键信息,如图

xctf攻防世界 REVERSE 薪手练习区(二)

可以看到调用decrypt函数的汇编代码,其接下来的mov指令将值送入eax寄存器中。那么先看看寄存器,输入info reg,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

可以看到eflags相关的寄存器的地址,那么读取其数据:

xctf攻防世界 REVERSE 薪手练习区(二)

复制所有的数字,并转成字符,脚本如下:

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检查,没有套壳。双击打开文件,一堆乱码,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

老规矩,丢入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功能

我们看看函数执行流程图,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

暂定两个区域way1和way2,函数走下来后,正常进行判断语句,执行到call ds:IsDebuggerPresent,way1是未执行if语句的流程,way2是执行if后里面的流程,那么我们可以尝试修改执行流程,思路为:

  • 想办法让程序执行way2
  • 其次通过way2后再弹窗显示flag

最终逻辑从左图变成右图:

xctf攻防世界 REVERSE 薪手练习区(二)

如何改变运行流程?此处参考bilibili的博主​​Keypatch如何使用​​ 右键要改变的位置:

xctf攻防世界 REVERSE 薪手练习区(二)

int3时IsDebuggerPresent触发后的中断操作,将其nop,同时修改程序执行的跳转地址,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

完事后,将程序重新输出,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

最终成功将源程序的执行流程修改,得到的结果如图:

xctf攻防世界 REVERSE 薪手练习区(二)

最终答案为:​

​flag{reversing_is_not_that_hard!}​

解法二:使用OllyDbg

先在论坛上下载OllyDbg:​​http://www.ollydbg.net/​​,下载如图:

xctf攻防世界 REVERSE 薪手练习区(二)

随后使用管理员权限打开exe,我们将程序拖入到OllyDbg中,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

该工具和IDA的Patch功能类似,都是通过修改程序的执行顺序来达到运行出flag的目的。在程序中,我们需要搜索定位到flag(如何搜索,这功能第一次用贼基把难用!!!参考链接:https://jingyan.baidu.com/article/48b37f8ddaf0b11a6464880d.html)

xctf攻防世界 REVERSE 薪手练习区(二)

在右侧可以看到IsDebuggerPresent函数,那么执行顺序就可以看到了,双击定位到如图位置,修改数据:

xctf攻防世界 REVERSE 薪手练习区(二)
xctf攻防世界 REVERSE 薪手练习区(二)
xctf攻防世界 REVERSE 薪手练习区(二)

修改完三处后,如图:

xctf攻防世界 REVERSE 薪手练习区(二)

程序执行位置已经更改,重新运行(快捷键F9),如图:

xctf攻防世界 REVERSE 薪手练习区(二)