攻防世界Reverse新手区小结

退役ACMer(其实ACM也才划水了一个多月)艰难入门CTF逆向工程,第一发学习小结。(啥也不会,只能对着IDA出的代码懵逼,天天坐牢)

Hello,CTF

考察sprintf函数

可以看出输入一个字符串v9,并判断长度小于等于17,再把v9的每个字符赋值给v4,最后通过sprintf函数将v4通过%x(16进制)输出为Buffer字符串,再通过strcat函数将Buffer字符串给v10,最后将v10与v13进行比较。
程序的意思是字符串转为16进制,那么我们只需要逆向把16进制转为字符串就可以了。

flag:

insanity

shift+F12快速打开string窗口找到flag

python-trade

发现附件是一个pyc文件
于是通过在线反编译网站得到python代码

import base64
def encode(message):
    s = ""
    for i in message:
        x = ord(i) ^ 32
        x = x + 16
        s += chr(x)
    return base64.b64encode(s)
correct = "XlNkVmtUI1MgXWBZXCFeKY+AaXNt"
flag = ""
print "Input flag:"
flag = raw_input()
if encode(flag) == correct:
    print "correct"
else:
    print "wrong"

发现是将输入的flag通过encode函数变成和correct相同的字符串。那么对encode函数逆向decode即可。
分析encode函数,发现ord()函数就是返回一个字符串的ASCII码,chr()函数就是返回一个ASCII码对应的字符串,最后通过base64encode加密。那么decode脚本就好写了。
先把correct字符串base64decode

#include<iostream>
#include<cstring>
#include<string>
using namespace std;
char correct[100]="^SdVkT#S ]`Y!^)ism";
char flag[40];
int main()
{
    for(int i=0;i<strlen(correct);i++)
    {
        char x=(correct[i]-16)^32;
        cout<<x;
    }
}

不会python,就用C++写了,得到flag。

re1

IDA之后F5查看伪代码,发现
可疑,进入xmmword后,按A键直接转化为字符串得到flag

game

IDA后,ctrl+F寻找main函数,得到源代码

发现了通过游戏的判断条件,双击进入sub函数,发现是对两个数组进行了两次异或运算
由于地址是连续的,所以v3,v4,以及strcpy的内容其实都属于v2,于是逆向写脚本得到flag。

open-source
附件是一段C代码,亲切,直接根据提示修改输出语句即可得到flag。


改成

,并将前面的代码全部删去确保能跑到这一步即可。

simple-unpack


拖入Exeinfo查壳发现有壳,需要用upx脱壳。
将upx.exe和目标文件放入同一目录,打开cmd,输入指令脱壳。

可以发现脱壳以后文件变大了,再次查壳,发现没了。

拖入IDA64,反编译后进入flag,直接得到flag

logmein

先查壳,发现没壳,拖入IDA,反编译找到main函数(有注释):

void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  size_t v3; // rsi
  int i; // [rsp+3Ch] [rbp-54h]
  char s[36]; // [rsp+40h] [rbp-50h] BYREF
  int v6; // [rsp+64h] [rbp-2Ch]
  __int64 v7; // [rsp+68h] [rbp-28h]
  char v8[28]; // [rsp+70h] [rbp-20h] BYREF
  int v9; // [rsp+8Ch] [rbp-4h]

  v9 = 0;
  strcpy(v8, ":"AL_RT^L*.?+6/46");//给v8赋值
  v7 = 0x65626D61726168LL;//16进制数
  v6 = 7;
  printf("Welcome to the RC3 secure password guesser.
");
  printf("To continue, you must enter the correct password.
");
  printf("Enter your guess: ");
  __isoc99_scanf("%32s", s);//输入s
  v3 = strlen(s);
  if ( v3 < strlen(v8) )//长度不小于v8
    sub_4007C0();
  for ( i = 0; i < strlen(s); ++i )
  {
    if ( i >= strlen(v8) )//长度不大于v8
      sub_4007C0();
    if ( s[i] != (char)(*((_BYTE *)&v7 + i % v6) ^ v8[i]) )//据此直接输出flag
      sub_4007C0();
  }
  sub_4007F0();
}

于是开始写脚本。

#include<bits/stdc++.h>
#define _BYTE unsigned char
using namespace std;

char a[20]=":"AL_RT^L*.?+6/46";
long long b=0x65626D61726168LL;
int c=7;
char flag[40];
int main()
{
    for(int i=0;i<strlen(a);i++)
    {
        flag[i]=(char)(*((_BYTE *)&b + i % c) ^ a[i]);
    }
    cout<<flag;
}

注:_BYTE为1个字节范围是0-255和unsigned char相同。
得到flag

no-strings-attached

拖入IDA发现 比较可疑

查看源代码
发现flag就是通过的decrypt得到的s2,由于之前查出来的
所以可以用ubuntu上的GDB跑一下。
先查main的汇编找到目标函数再查目标函数的汇编(gdb是真难用)

在decrypt处设置断点并执行。

由于
说明返回值在eax寄存器中,于是单步调试调用decrpt之后查询eax即可。

getit

拖入IDA得到代码,查看字符串内容:

用快捷键A把完整的t字符串显示出来,发现前缀,猜测应该是通过

将?填充进去获得完整flag(t[i+10]正好是从第一个问号开始的下标)
于是开始写脚本。

#include<bits/stdc++.h>
using namespace std;
char s[50]="c61b68366edeb7bdce3c6820314b7498";
char t[50]="SharifCTF{????????????????????????????????}";
int main()
{
    for(int i=0;i<strlen(s);i++)
    {
        int x;
        if(i&1)x=1;
        else x=-1;
        t[i+10]=s[i]+x;
    }
    cout<<t;
}

得到flag:

csaw2013reversing2

先运行,弹出一个窗口,标题为flag,但是是乱码,猜测最后的flag应该也是通过这种形式弹出。扔进Exeinfo PE查壳,没壳,直接扔进IDA反编译出代码:

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, SourceSize + 1);
  memcpy_s(lpMem, SourceSize, &unk_409B10, SourceSize);
  if ( !sub_40102A() && !IsDebuggerPresent() )
  {
    MessageBoxA(0, lpMem + 1, "Flag", 2u);
    HeapFree(hHeap, 0, lpMem);
    HeapDestroy(hHeap);
    ExitProcess(0);
  }
  __debugbreak();
  sub_401000(v3 + 4, lpMem);
  ExitProcess(0xFFFFFFFF);
}

注意到有IsDebuggerPresent()函数用来反调试,MessageBoxA是用来弹出一个窗口,IpMem+1即为弹出的内容。
然后是进入函数sub_401000(),发现很像一个解密函数,那么猜测应该是要执行这个解密函数之后才能改变乱码得到flag。
考虑动态调试,拖入Ollydbg,先找到IsDebuggerPresent:

先把它给nop了,然后运行,发现还是弹出了乱码窗口,继续读汇编,发现通过je命令直接跳到了第二个MessageBoxA处,弹出乱码。
又看到这一行:
int3是一个断点指令,通过IDA的反编译可以查出是对应__debugbreak()函数,那么下面call的函数很可能是该解密函数,考虑修改je指令跳到该函数前一步:

又发现后面的jmp指令把两个MessageBoxA都跳过了

先尝试第一个MessageBoxA,把下面的jmp语句直接nop掉,然后运行:

弹出空白flag。
观察两个MessageBoxA的区别:


考虑到反编译代码中输出的pMem是从1开始的,输出空白flag很可能是因为有占位符'',而下面的MessageBoxA猜测是通过一个寄存器的操作跳过了第一个占位符,那么考虑jmp到下面的MessageBoxA处,修改后的汇编如下:

运行得到flag:

maze

看题目就知道要找地图,直接扔进IDApro反编译:

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  __int64 v3; // rbx
  int v4; // eax
  char v5; // bp
  char v6; // al
  const char *v7; // rdi
  unsigned int v9; // [rsp+0h] [rbp-28h] BYREF
  int v10[9]; // [rsp+4h] [rbp-24h] BYREF

  v10[0] = 0;
  v9 = 0;
  puts("Input flag:");
  scanf("%s", &s1);
  if ( strlen(&s1) != 24 || strncmp(&s1, "nctf{", 5uLL) || *(&byte_6010BF + 24) != 125 )
  {
LABEL_22:
    puts("Wrong flag!");
    exit(-1);
  }
  v3 = 5LL;
  if ( strlen(&s1) - 1 > 5 )
  {
    while ( 1 )
    {
      v4 = *(&s1 + v3);
      v5 = 0;
      if ( v4 > 78 )
      {
        if ( (unsigned __int8)v4 == 79 )
        {
          v6 = sub_400650(v10);
          goto LABEL_14;
        }
        if ( (unsigned __int8)v4 == 111 )
        {
          v6 = sub_400660(v10);
          goto LABEL_14;
        }
      }
      else
      {
        if ( (unsigned __int8)v4 == 46 )
        {
          v6 = sub_400670(&v9);
          goto LABEL_14;
        }
        if ( (unsigned __int8)v4 == 48 )
        {
          v6 = sub_400680(&v9);
LABEL_14:
          v5 = v6;
        }
      }
      if ( !(unsigned __int8)sub_400690(asc_601060, (unsigned int)v10[0], v9) )
        goto LABEL_22;
      if ( ++v3 >= strlen(&s1) - 1 )
      {
        if ( v5 )
          break;
LABEL_20:
        v7 = "Wrong flag!";
        goto LABEL_21;
      }
    }
  }
  if ( asc_601060[8 * v9 + v10[0]] != 35 )
    goto LABEL_20;
  v7 = "Congratulations!";
LABEL_21:
  puts(v7);
  return 0LL;
}

先找到跑图代码:

对照ASCII码发现:
O:向左
o:向右
0:向下
.:向上
然后找到到达终点的代码 盲猜是一个8行的图,且重点为ASCII为35的字符(#)
进入asc_601060数组,用16进制窗口打开,应该就是地图: 发现地图是8*8的。
手动画图得到地图:

..******
*...*..*
***.*.**
**..*.**
*..*#..*
**.***.*
**.....*
********

跑图得到flag:nctf{o0oo00O000oooo..OO}

至此,攻防世界新手区完结撒花qwq❀

原文地址:https://www.cnblogs.com/THRANDUil/p/15492622.html