Reverse(十)

csaw2013reversing2

这道题需要ollydbg来做更方便些,需要修改汇编代码

本题直接运行弹窗是乱码的,因此首先ida32查看下代码逻辑

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);    //  此处弹窗显示flag
    HeapFree(hHeap, 0, lpMem);
    HeapDestroy(hHeap);
    ExitProcess(0);
  }
  __debugbreak();
  sub_401000(v3 + 4, lpMem);
  ExitProcess(0xFFFFFFFF);
}

要想获得正确flag,需要经过sub_401000函数处理,因此下图中判断完是否调试器后test完,修改ZF位,使得跳转不成立,去执行sub_401000函数

image-20231223153658173.png

同样要注意INT3是异常中断指令,因此可以右键在下一行mov上选择EIP为此时位置,调用完到JMP时再切换回下面调用MessageBoxA的开始位置(即008B10B9),运行即可显示flag

image-20231223154108485.png

补充:整型溢出

image-20231229174945097.png

re4-unvm-me

pyc文件反编译即可

# uncompyle6 version 3.9.0
# Python bytecode version base 2.7 (62211)
# Decompiled from: Python 3.9.12 (main, Apr  4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: unvm_me.py
# Compiled at: 2016-12-21 05:44:01
import md5
md5s = [
 174282896860968005525213562254350376167, 137092044126081477479435678296496849608, 
 126300127609096051658061491018211963916, 314989972419727999226545215739316729360, 
 256525866025901597224592941642385934114, 115141138810151571209618282728408211053, 
 8705973470942652577929336993839061582, 256697681645515528548061291580728800189, 
 39818552652170274340851144295913091599, 65313561977812018046200997898904313350, 
 230909080238053318105407334248228870753, 196125799557195268866757688147870815374, 
 74874145132345503095307276614727915885]
print 'Can you turn me back to python ? ...'
flag = raw_input('well as you wish.. what is the flag: ')
if len(flag) > 69:
    print 'nice try'
    exit()
if len(flag) % 5 != 0:
    print 'nice try'
    exit()
for i in range(0, len(flag), 5):
    s = flag[i:i + 5]
    if int('0x' + md5.new(s).hexdigest(), 16) != md5s[i / 5]:
        print 'nice try'
        exit()

print 'Congratz now you have the flag'

暴力破解,虽然可能花些时间,有个关键点注意m = hashlib.md5(),这句话必须每次都要创建一个新的对象,否则加密结果是错的,不能重复调用

# 暴力破解5位md5
import hashlib

p = ['', '', '', '', '', '', '', '', '', '', '', '', '']
md5s = [
 174282896860968005525213562254350376167, 137092044126081477479435678296496849608,
 126300127609096051658061491018211963916, 314989972419727999226545215739316729360,
 256525866025901597224592941642385934114, 115141138810151571209618282728408211053,
 8705973470942652577929336993839061582, 256697681645515528548061291580728800189,
 39818552652170274340851144295913091599, 65313561977812018046200997898904313350,
 230909080238053318105407334248228870753, 196125799557195268866757688147870815374,
 74874145132345503095307276614727915885]
md5s = [str(hex(i))[2:] for i in md5s]
for i1 in range(48, 127):
    for i2 in range(48, 127):
        for i3 in range(48, 127):
            for i4 in range(48, 127):
                for i5 in range(48, 127):
                    m = hashlib.md5()
                    str = chr(i1)+chr(i2)+chr(i3)+chr(i4)+chr(i5)
                    m.update(str.encode('utf8'))
                    md5 = m.hexdigest()
                    if md5 in md5s:
                        p[md5s.index(md5)] = str
print(p)    # 

当然最好还是直接找md5解密网站快多了

crackme

exeinfo查看得知是nSpack壳,学习下相关知识

  • esp定律:也称堆栈平衡定律,如果要返回父程序,则当我们在堆栈中进行堆栈的操作的时候,一定要保证在RET这条指令之前,ESP指向的是我们压入栈中的地址。
  • OEP:程序入口点,可以直接OD载入

以题目为例手动脱nSpack壳(北斗壳):

  1. OD载入直接跳到了PUSHFD

image-20231230172218265.png

  1. F8执行到PUSHAD,此时ESP变化,对ESP右键设置断点(HW break)

image-20231230172355310.png

  1. F9执行到POPFD,下面有一条JMP指令

image-20231230172511127.png

  1. F8跳转到JMP位置,00401336就是OEP地址

image-20231230174055022.png

  1. 右键使用Dump Process,将EIP设置为OEP,转储

image-20231230175952059.png

另一种方法是到这里,使用PEtool管理员运行,找到这个程序(3fdxxx.exe)右键Dump all

image-20231230180216359.png

  1. 再去ida反编译可以成功看到伪代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  void (__cdecl *v3)(char *); // esi
  int v5; // eax
  char v6; // [esp+4h] [ebp-38h] BYREF
  char v7[51]; // [esp+5h] [ebp-37h] BYREF

  v6 = 0;
  sub_4018F4(v7, 0, 49);
  v3 = (void (__cdecl *)(char *))dword_402094;
  dword_402094(aPleaseInputFla);                // Please Input Flag:
  dword_402090(&v6, 44);
  if ( strlen(&v6) == 42 )
  {
    v5 = 0;
    while ( (v7[v5 - 1] ^ byte_402130[v5 % 16]) == dword_402150[v5] )// v7其实就是v6数组
    {
      if ( ++v5 >= 42 )
      {
        v3(aRight);                             // right!
        return 0;
      }
    }
    v3(aError);
    return 0;
  }
  else
  {
    v3(aError);
    return -1;
  }
}

image-20231230181223178.png

debug

exeinfo提示dotfuscator进行了加壳混淆,因此使用de4dot来脱壳

image-20231230195310984.png

然后dnspy查看.net程序,并定位关键函数

image-20231230200343781.png

using System;
using System.Security.Cryptography;
using System.Text;

// Token: 0x02000002 RID: 2
internal class Class0
{
    // Token: 0x06000001 RID: 1 RVA: 0x000020C8 File Offset: 0x000002C8
    private static int smethod_0(int int_0, int int_1)
    {
        return (new int[]
        {
            2,
            3,
            5,
            7,
            11,
            13,
            17,
            19,
            23,
            29,
            31,
            37,
            41,
            43,
            47,
            53,
            59,
            61,
            67,
            71,
            73,
            79,
            83,
            89,
            97,
            101,
            103,
            107,
            109,
            113
        })[int_1] ^ int_0;
    }

    // Token: 0x06000002 RID: 2 RVA: 0x000020E8 File Offset: 0x000002E8
    private static string smethod_1(string string_0)
    {
        byte[] bytes = Encoding.ASCII.GetBytes(string_0);
        return "flag{" + BitConverter.ToString(new MD5CryptoServiceProvider().ComputeHash(bytes)).Replace("-", "") + "}";
    }

    // Token: 0x06000003 RID: 3 RVA: 0x00002130 File Offset: 0x00000330
    private static void smethod_2(string string_0, int int_0, ref string string_1)
    {
        int num = 0;
        if (0 < string_0.Length)
        {
            do
            {
                char c = string_0[num];
                int num2 = 1;
                do
                {
                    c = Convert.ToChar(Class0.smethod_0(Convert.ToInt32(c), num2));
                    num2++;
                }
                while (num2 < 15);
                string_1 += c;
                num++;
            }
            while (num < string_0.Length);
        }
        string_1 = Class0.smethod_1(string_1);
    }

    // Token: 0x06000004 RID: 4 RVA: 0x00002198 File Offset: 0x00000398
    private static void Main(string[] args)
    {
        string b = null;
        string value = string.Format("{0}", DateTime.Now.Hour + 1);
        string string_ = "CreateByTenshine";
        Class0.smethod_2(string_, Convert.ToInt32(value), ref b);    // 加密函数赋给b
        string a = Console.ReadLine();
        if (a == b)
        {
            Console.WriteLine("u got it!");
            Console.ReadKey(true);
        }
        else
        {
            Console.Write("wrong");
        }
        Console.ReadKey(true);
    }
}

解密脚本

#include<iostream>
#include<string>
using namespace std;
int main() {
    int a[] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113};
    string s = "CreateByTenshine";
    string flag = "";
    for (int i = 0; i < s.length(); i++) {
        int num2 = 1;
        int c = (int)s[i];
        do {
            c = a[num2] ^ c;
            num2++;
        } while (num2 < 15);
        flag += c;
    }
    cout << flag << endl;
}

得到的结果md5取大写,然后加上flag{}即可

ReverseMe-120

https://blog.csdn.net/xiao__1bai/article/details/120107210

这道题难度在于读懂代码,涉及到很多考点

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // edx
  unsigned int v4; // ecx
  __m128i si128; // xmm1
  unsigned int v6; // esi
  const __m128i *v7; // eax
  __m128i v8; // xmm0
  int v9; // eax
  char v11[100]; // [esp+0h] [ebp-CCh] BYREF
  char v12[100]; // [esp+64h] [ebp-68h] BYREF
  unsigned int v13; // [esp+C8h] [ebp-4h]

  printf("please input your flah:");
  memset(v11, 0, sizeof(v11));
  scanf("%s", v11);                             // 输入赋给了v11
  memset(v12, 0, sizeof(v12));
  sub_401000(v11, strlen(v11));                 // 一个核心函数,后面分析可知是base64解密函数
  v3 = v13;                                     // 这里最开始搞不懂为啥v13没初始化就有值,其实是在上面sub_401000函数里
  v4 = 0;
  if ( v13 )
  {
    if ( v13 >= 0x10 )
    {
      si128 = _mm_load_si128((const __m128i *)&xmmword_414F20);// 用于加载数据,从内存到暂存器,查看可知都是0x25
      v6 = v13 - (v13 & 0xF);
      v7 = (const __m128i *)v12;
      do
      {
        v8 = _mm_loadu_si128(v7);               // 读取16字节
        v4 += 16;
        ++v7;
        v7[-1] = _mm_xor_si128(v8, si128);      // 相当于v12结果每一位和si128即0x25异或
      }
      while ( v4 < v6 );
    }
    for ( ; v4 < v3; ++v4 )    // 没用,貌似得调试发现
      v12[v4] ^= 0x25u;
  }
  v9 = strcmp(v12, "you_know_how_to_remove_junk_code");// 说明有垃圾代码没有运行起干扰作用
  if ( v9 )
    v9 = v9 < 0 ? -1 : 1;
  if ( v9 )
    printf("wrong\n");
  else
    printf("correct\n");
  system("pause");
  return 0;
}

sub_401000之所以参数多于实际参数是因为有寄存器传参

int __usercall sub_401000@<eax>(unsigned int *a1@<edx>, _BYTE *a2@<ecx>, unsigned __int8 *a3, unsigned int a4)
{
  int v4; // ebx
  unsigned int v5; // eax
  int v6; // ecx
  unsigned __int8 *v7; // edi
  int v8; // edx
  bool v9; // zf
  unsigned __int8 v10; // cl
  char v11; // cl
  _BYTE *v12; // esi
  unsigned int v13; // ecx
  int v14; // ebx
  unsigned __int8 v15; // cl
  char v16; // dl
  int v20; // [esp+14h] [ebp-4h]
  unsigned int v21; // [esp+14h] [ebp-4h]
  int i; // [esp+24h] [ebp+Ch]

  v4 = 0;
  v5 = 0;
  v6 = 0;
  v20 = 0;
  if ( !a4 )
    return 0;
  v7 = a3;
  do
  {
    v8 = 0;
    v9 = v5 == a4;
    if ( v5 < a4 )
    {
      do
      {
        if ( a3[v5] != 32 )
          break;
        ++v5;
        ++v8;
      }
      while ( v5 < a4 );
      v9 = v5 == a4;
    }
    if ( v9 )
      break;
    if ( a4 - v5 >= 2 && a3[v5] == 13 && a3[v5 + 1] == 10 || (v10 = a3[v5], v10 == 10) )
    {
      v6 = v20;
    }
    else
    {
      if ( v8 )
        return -44;
      if ( v10 == 61 && (unsigned int)++v4 > 2 )
        return -44;
      if ( v10 > 0x7Fu )
        return -44;
      v11 = byte_414E40[v10];
      if ( v11 == 127 || (unsigned __int8)v11 < 0x40u && v4 )
        return -44;
      v6 = ++v20;
    }
    ++v5;
  }
  while ( v5 < a4 );
  if ( !v6 )
    return 0;
  v12 = a2;
  v13 = ((unsigned int)(6 * v6 + 7) >> 3) - v4;
  if ( a2 && *a1 >= v13 )
  {
    v21 = 3;
    v14 = 0;
    for ( i = 0; v5; --v5 )
    {
      v15 = *v7;
      if ( *v7 != 13 && v15 != 10 && v15 != 32 )
      {
        v16 = byte_414E40[v15];
        v21 -= v16 == 64;
        v14 = v16 & 0x3F | (v14 << 6);    // base64解密特征
        if ( ++i == 4 )
        {
          i = 0;
          if ( v21 )
            *v12++ = BYTE2(v14);
          if ( v21 > 1 )
            *v12++ = BYTE1(v14);
          if ( v21 > 2 )
            *v12++ = v14;
        }
      }
      ++v7;
    }
    *a1 = v12 - a2;
    return 0;
  }
  *a1 = v13;
  return -42;
}

流程:base64解密--xor 0x25--明文比较

逆向:明文逐位和0x25异或--base64加密

得到:XEpQek5LSlJ6TUpSelFKeldASEpTQHpPUEtOekZKQUA=

EASYHOOK

找到主函数和hook函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  HANDLE FileA; // eax
  DWORD NumberOfBytesWritten; // [esp+4h] [ebp-24h] BYREF
  char Buffer[32]; // [esp+8h] [ebp-20h] BYREF

  sub_401370(aPleaseInputFla);
  scanf("%31s", Buffer);
  if ( strlen(Buffer) == 19 )
  {
    sub_401220();                               // hook WriteFile
    FileA = CreateFileA(FileName, 0x40000000u, 0, 0, 2u, 0x80u, 0);// Your_input
    WriteFile(FileA, Buffer, 0x13u, &NumberOfBytesWritten, 0);
    sub_401240(Buffer, &NumberOfBytesWritten);
    if ( NumberOfBytesWritten == 1 )
      sub_401370(aRightFlagIsYou);
    else
      sub_401370(aWrong);
    system(Command);
    return 0;
  }
  else
  {
    sub_401370(aWrong);
    system(Command);
    return 0;
  }
}

int sub_401220()
{
  HMODULE LibraryA; // eax
  DWORD CurrentProcessId; // eax

  CurrentProcessId = GetCurrentProcessId();
  hProcess = OpenProcess(0x1F0FFFu, 0, CurrentProcessId);
  LibraryA = LoadLibraryA(LibFileName);
  WriteFile_0 = (BOOL (__stdcall *)(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED))GetProcAddress(LibraryA, ProcName);
  lpAddress = WriteFile_0;
  if ( !WriteFile_0 )
    return sub_401370(&unk_40A044);
  unk_40C9B4 = *(_DWORD *)lpAddress;
  *((_BYTE *)&unk_40C9B4 + 4) = *((_BYTE *)lpAddress + 4);
  byte_40C9BC = -23;
  dword_40C9BD = (char *)sub_401080 - (char *)lpAddress - 5;    // 计算被替换成的函数与WriteFile函数地址偏移
  return sub_4010D0();    // hook
}

定位hook替换的函数

int __stdcall sub_401080(
        HANDLE hFile,
        LPCVOID lpBuffer,    // 输入的字符串
        DWORD nNumberOfBytesToWrite,
        LPDWORD lpNumberOfBytesWritten,    // 要确保lpNumberOfBytesWritten==1
        LPOVERLAPPED lpOverlapped)
{    // 实际上被hook替换成的函数
  int v5; // ebx

  v5 = sub_401000(lpBuffer, nNumberOfBytesToWrite);    // 关键函数
  sub_401140();
  WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped); // 原来的功能
  if ( v5 )    // v5必须不为0
    *lpNumberOfBytesWritten = 1;
  return 0;
}

int __cdecl sub_401000(int a1, int a2)
{
  char i; // al
  char v3; // bl
  char v4; // cl
  int v5; // eax

  for ( i = 0; i < a2; ++i )    // 对输入字符串加密
  {
    if ( i == 18 )
    {
      *(_BYTE *)(a1 + 18) ^= 0x13u;
    }
    else
    {
      if ( i % 2 )
        v3 = *(_BYTE *)(i + a1) - i;
      else
        v3 = *(_BYTE *)(i + a1 + 2);
      *(_BYTE *)(i + a1) = i ^ v3;
    }
  }
  v4 = 0;
  if ( a2 <= 0 )
    return 1;
  v5 = 0;
  while ( byte_40A030[v5] == *(_BYTE *)(v5 + a1) )
  {
    v5 = ++v4;
    if ( v4 >= a2 )
      return 1;    // 要确保到这里,也就是说循环不能退出
  }
  return 0;
}

写脚本逆向,一定要记得运算符优先性,记得加括号,然后第一位可以猜到是f(没法逆向知道具体值)

b = [0x61, 0x6A, 0x79, 0x67, 0x6B, 0x46, 0x6D, 0x2E, 0x7F, 0x5F, 0x7E, 0x2D, 0x53, 0x56, 0x7B, 0x38, 0x6D, 0x4C, 0x6E]
a1 = ['' for i in range(19)]
for i in range(0, 19):
    if i == 18:
        a1[i] = chr(b[i] ^ 0x13)
    else:
        if i % 2:
            a1[i] = chr((b[i] ^ i) + i)
        else:
            a1[i + 2] = chr(b[i] ^ i)
flag = ''.join(a1)
print(flag)

gametime

运行发现是个游戏,首先ida静态分析下,主函数超长,经过分析发现只有game里的函数(两个,sub_401435和sub_401507,功能基本一样)是关键

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // edi
  unsigned int v4; // eax
  int v5; // ecx
  int v6; // ecx
  int v7; // ecx
  void (__stdcall *v8)(DWORD); // ebx
  int v9; // esi
  int v10; // esi
  int v11; // esi
  int v12; // esi
  int v13; // esi
  int i; // edi
  int v15; // esi
  unsigned __int8 *v16; // esi
  int v17; // ebx
  int v18; // esi
  int v19; // eax
  int v20; // esi
  int v21; // eax
  char v22; // cl
  int v23; // eax
  int v25; // [esp+10h] [ebp-20h]
  int v26; // [esp+14h] [ebp-1Ch] BYREF
  char v27; // [esp+1Bh] [ebp-15h]
  _WORD v28[3]; // [esp+1Ch] [ebp-14h] BYREF
  int v29; // [esp+22h] [ebp-Eh]
  int v30; // [esp+26h] [ebp-Ah]
  __int16 v31; // [esp+2Ah] [ebp-6h]

  strcpy((char *)v28, " ");
  v26 = 7630702;
  *(_DWORD *)&v28[1] = 0;
  v29 = 0;
  v30 = 0;
  v31 = 0;
  v3 = 0;
  v25 = 0;
  printf((int)"\r\tZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG\n");
  printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28);
  sub_401423();                                 // 相当于消除上一行,光标移动到开始了
  printf((int)"\r\tZOMGZOMG                                ZOMGZOMG\n");
  printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28);
  sub_401423();
  printf((int)"\r\tZOMGZOMG     TAP TAP REVOLUTION!!!!!!!  ZOMGZOMG\n");
  printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28);
  sub_401423();
  printf((int)"\r\tZOMGZOMG                                ZOMGZOMG\n");
  printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28);
  sub_401423();
  printf((int)"\r\tZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG\n\n\n");
  printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28);
  sub_401423();
  printf((int)"\r\t              R U READDY?!\n\n\n");
  printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28);
  sub_401423();
  printf((int)"\rThe game is starting in...\n");
  v4 = _time64(0);
  srand(v4);
  sub_4012B2();                                 // 10秒倒计时
  sub_4012D5(0xC8u);                            // 打印Get ready to play
  if ( !sub_401435(0x1F4u, 32, 10, v5, (const char *)v28, (const char *)&v26) )// sub_401435传入参数,第一个倒计时时间,第二个需要按下的字符,第三个点号出现次数(出现完要按下空格),后两个没用
    return 0;                                   // 必须三个game都成功,否则return
  if ( !sub_401435(0x12Cu, 120, 8, v6, (const char *)v28, (const char *)&v26) )
    return 0;
  if ( !sub_401435(0x12Cu, 109, 5, v7, (const char *)v28, (const char *)&v26) )
    return 0;
  printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28);
  printf((int)"\rTRAINING COMPLETE!                              \n");
  v8 = Sleep;
  v9 = 20;
  do
  {
    Sleep(0xC8u);
    printf((int)"\n");
    --v9;
  }
  while ( v9 );                                 // 换行20次
  printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28);
  printf((int)"\rNow you know everything you need to know");
  v10 = 4;
  do
  {
    printf((int)".");
    Sleep(0x3E8u);
    --v10;
  }
  while ( v10 );
  printf((int)"\n\n\nfor the rest of your life!\n");
  v11 = 20;
  do
  {
    Sleep(0xC8u);
    printf((int)"\n");
    --v11;
  }
  while ( v11 );
  printf((int)"LETS PLAY !\n");
  v12 = 20;
  do
  {
    Sleep(0xC8u);
    printf((int)"\n");
    --v12;
  }
  while ( v12 );
  sub_4012B2();
  sub_4012D5(0x64u);
  if ( !sub_401507(5, 32, 0xC8u, (const char *)v28, (const char *)&v26) )// 看着跟上面很像,传入参数第一个是点号打印次数,第二是按下的字符,第三个是必须按下字符的规定时间,后两个还是没用
    return 0;
  if ( !sub_401507(2, 120, 0xC8u, (const char *)v28, (const char *)v28) )
    return 0;
  if ( !sub_401507(1, 109, 0xC8u, (const char *)v28, (const char *)v28) )
    return 0;
  printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28);
  sub_401423();
  printf((int)"\rooooh, you fancy!!!\n");
  if ( !sub_401507(5, 109, 0xC8u, (const char *)v28, (const char *)&v26)// 再来一轮,必须都成功
    || !sub_401507(2, 120, 0xC8u, (const char *)v28, (const char *)&v26)
    || !sub_401507(1, 32, 0xC8u, (const char *)v28, (const char *)&v26) )
  {
    return 0;
  }
  printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28);
  printf((int)"\b\b");
  printf((int)"NIIICE JOB)!!!!\n");
  v13 = 20;
  do
  {
    Sleep(0x32u);
    printf((int)"\n");
    --v13;
  }
  while ( v13 );
  v27 = 1;
  do    // 从这里开始难以静态分析,因此直接让代码自己动态运行就好,关键是绕开game的判断
  {
    if ( v3 % 3 == 1 )
    {
      printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28);
      sub_401423();
      printf((int)"\rTURBO TIME!    \n");
      for ( i = 0; i < 20; ++i )
      {
        v8(0x32u);
        printf((int)"\n");                      // 打印换行符,直到最后一次
        if ( i == 19 )
        {
          v15 = sub_40141D();
          sub_401D02(v28, v15 - 5514);
          dword_41A1F8 = (int)v28;
          dword_41A1FC = v15 - 5498;
          sub_401AA5();
          sub_401CC9();
          printf((int)"key is %s (%s)", byte_417D02, (const char *)&v26);
          printf((int)"\b\b");
          v16 = (unsigned __int8 *)v28;
          v17 = 16;
          do
          {
            printf((int)"%02x", *v16++);
            --v17;
          }
          while ( v17 );
          printf((int)")\n\n");
          v8 = Sleep;
        }
      }
      v18 = 0;
      while ( 1 )
      {
        v19 = rand();
        if ( !sub_401507(1, byte_417B08[v19 % 3], 0x64u, (const char *)v28, (const char *)&v26) )
          break;
        if ( ++v18 >= 10 )
          goto LABEL_33;
      }
      v27 = 0;
LABEL_33:
      v3 = v25;
    }
    v20 = 0;
    while ( 1 )
    {
      v21 = rand();
      v22 = v27;
      v23 = v21 % 3;
      if ( v27 )
        break;
LABEL_38:
      if ( ++v20 >= 10 )
        goto LABEL_41;
    }
    if ( sub_401507(v23 + 3, byte_417B08[v23], 0x64u, (const char *)v28, (const char *)&v26) )
    {
      v22 = v27;
      goto LABEL_38;
    }
    v22 = 0;
    v27 = 0;
LABEL_41:
    if ( v3 == 1337 )
    {
      sub_4012F6();                             // 成功结算
      v22 = v27;
    }
    v25 = ++v3;
  }
  while ( v22 );
  return 0;
}

sub_401435和sub_401507中我们只看一个代码就好

char __usercall sub_401435@<al>(DWORD a1@<edx>, int a2@<ecx>, int a3, int a4, const char *a5, const char *a6)
{
  int v8; // edi

  printf((int)"key is %s (%s)", a6, a5);
  sub_401423();
  printf((int)"\rZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG\n");
  if ( a2 == 32 )
    printf((int)"\nWhen you see an 's', press the space bar\n\n");
  else
    printf((int)"\nWhen you see an '%c', press the '%c' key\n\n", a2, a2);
  printf((int)"key is %s (%s)", a6, a5);
  sub_401423();
  printf((int)"\rZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG\n");
  sub_4012D5(a1);
  v8 = a3;
  if ( a3 > 0 )
  {
    do
    {
      printf((int)".");
      Sleep(0xC8u);
      --v8;
    }
    while ( v8 );
  }
  if ( (unsigned __int8)sub_401260(a2, 100000) )// 判断是否规定时间内按下对应字符
    return 1;
  printf((int)"key is %s (%s)\r", a6, a5);
  sub_401423();
  printf((int)"\rUDDER FAILURE! http://imgur.com/4Ajx21P \n");// 失败
  return 0;
}

只需保证return为1即可游戏成功,因此ollydbg定位所有带“UDDER FAILURE! http://imgur.com/4Ajx21P”这句话,后面的return改成1

如图,原来是XOR AL, AL,表示清0返回0,现在改成和下面一样,return 1(注意改两处)

image-20240128100436735.png

F9让ollydbg自动跑,一段时间后就会发现打印了key

image-20240128100004858.png

notsequence

ida打开,定位主函数

int __cdecl main()
{
  _DWORD *v0; // eax
  int v2; // [esp+14h] [ebp-Ch]
  _DWORD *v3; // [esp+1Ch] [ebp-4h]

  memset(&unk_8049BE0, 0, 0x4000u);
  puts("input raw_flag please:");
  v3 = &unk_8049BE0;
  do
  {
    v0 = v3++;
    scanf("%d", v0);
  }
  while ( *(v3 - 1) );
  v2 = sub_80486CD((int)&unk_8049BE0);          // 第一个加密,返回不能为0
  if ( v2 == -1 )
  {
    printf("check1 not pass");
    system("pause");
  }
  if ( (unsigned __int8)sub_8048783((int)&unk_8049BE0, v2) != 1 )// 第二个加密,返回必须为1
  {
    printf("check2 not pass!");
    exit(0);
  }
  if ( v2 == 20 )                               // 这里可知v2=20
  {
    puts("Congratulations! fl4g is :\nRCTF{md5(/*what you input without space or \\n~*/)}");
    exit(0);
  }
  return 0;
}

分析两个加密函数

  1. sub_80486CD

    int __cdecl sub_80486CD(int a1)
    {
      int j; // [esp+0h] [ebp-14h]
      int v3; // [esp+4h] [ebp-10h]
      int i; // [esp+8h] [ebp-Ch]
      int v5; // [esp+Ch] [ebp-8h]
    
      v5 = 0;
      for ( i = 0; i <= 1024 && *(_DWORD *)(4 * i + a1); i = v5 * (v5 + 1) / 2 )// v5<=44
      {                                             // i=0、1、3、6 ... 19x(19+1)/2=190
        v3 = 0;
        for ( j = 0; j <= v5; ++j )
          v3 += *(_DWORD *)(4 * (j + i) + a1);      // int数组
        if ( 1 << v5 != v3 )                        // v3必须等于2^v5
          return -1;
        ++v5;
      }
      return v5;                                    // 由主函数知v5=20
    }

该代码逻辑是:逐个取1、2、3...n...19个数,每次取的数之和等于$2^{n-1}$

  1. sub_8048783

    int __cdecl sub_8048783(int a1, int a2)
    {
      int v3; // [esp+10h] [ebp-10h]
      int v4; // [esp+14h] [ebp-Ch]
      int i; // [esp+18h] [ebp-8h]
      int v6; // [esp+1Ch] [ebp-4h]
    
      v6 = 0;
      for ( i = 1; i < a2; ++i )                    // a2=20
      {
        v4 = 0;
        v3 = i - 1;
        if ( !*(_DWORD *)(4 * i + a1) )
          return 0;
        while ( a2 - 1 > v3 )
        {
          v4 += *(_DWORD *)(4 * (v3 * (v3 + 1) / 2 + v6) + a1);// V6比i小1
          ++v3;
        }
        if ( *(_DWORD *)(4 * (v3 * (v3 + 1) / 2 + i) + a1) != v4 )// 最后一行
          return 0;
        ++v6;
      }
      return 1;
    }

具体逻辑不好分析代码:大意是分为1、2、3...n...19个数,除了最后一组的其他每组数的第i个数之和=最后一组数第i+1数(至此可知是杨辉三角构造,最好别钻牛角尖想最后一行,直接类推分析,只有两行(1 11)、只有三行(1 11 121)、...就能看出来了)

至此代码分析完成,得知输入的是19行杨辉三角数,去除空格再md5

import hashlib


def get_row(n):
    row = [1] * (n + 1)
    for i in range(1, n):
        # 由于每一行都是上一行的基础上得到的,我们可以从第二项开始进行遍历
        for j in range(i, 0, -1):
            # 每一项都是它前面两项之和
            row[j] = row[j] + row[j - 1]
    row = [str(i) for i in row]
    return row


yanghui = []
for i in range(0, 20):
    yanghui.extend(get_row(i))
print("".join(yanghui))
md5 = hashlib.md5()
md5.update("".join(yanghui).encode('utf-8'))
print(md5.hexdigest())    # 37894beff1c632010dd6d524aa9604db

key

这道题主函数十分复杂,但是可以ida看流程

首先第一处分支:走错会打印what happen?(深入去读会发现是一个读取文件操作,不管直接看正确路线)

image-20240129103219165.png

读不懂,看了wp,结合代码前期逻辑知道代码会生成一段字符串和从flag文件中的内容比较

image-20240129105544163.png

int sub_401100()
{
  signed int v0; // esi
  signed int v1; // esi
  unsigned int v2; // edi
  void **v3; // ebx
  void **v4; // eax
  int v5; // ecx
  int v6; // ST04_4
  int v7; // ST08_4
  int v8; // ST0C_4
  int v9; // eax
  int v10; // ST0C_4
  char *v11; // esi
  int v12; // ecx
  void **v13; // eax
  int v14; // eax
  int v15; // ST0C_4
  int v16; // eax
  int v17; // ST0C_4
  int v18; // eax
  int v19; // ST0C_4
  int v20; // eax
  int v21; // ST0C_4
  int v22; // eax
  int v23; // ST0C_4
  int v24; // eax
  int v25; // ST0C_4
  int v26; // eax
  int v27; // ST0C_4
  int v28; // eax
  int result; // eax
  int v30; // [esp-4h] [ebp-13Ch]
  int Dst; // [esp+14h] [ebp-124h]
  char v32[4]; // [esp+20h] [ebp-118h]
  char v33; // [esp+24h] [ebp-114h]
  int v34; // [esp+5Ch] [ebp-DCh]
  char v35; // [esp+61h] [ebp-D7h]
  int v36; // [esp+64h] [ebp-D4h]
  int v37; // [esp+68h] [ebp-D0h]
  char v38; // [esp+6Ch] [ebp-CCh]
  FILE *File; // [esp+70h] [ebp-C8h]
  char v40; // [esp+84h] [ebp-B4h]
  void *v41; // [esp+CCh] [ebp-6Ch]
  int v42; // [esp+DCh] [ebp-5Ch]
  unsigned int v43; // [esp+E0h] [ebp-58h]
  void *v44; // [esp+E4h] [ebp-54h]
  int v45; // [esp+F4h] [ebp-44h]
  unsigned int v46; // [esp+F8h] [ebp-40h]
  void *Memory[4]; // [esp+FCh] [ebp-3Ch]
  int v48; // [esp+10Ch] [ebp-2Ch]
  unsigned int v49; // [esp+110h] [ebp-28h]
  __int128 v50; // [esp+114h] [ebp-24h]
  __int16 v51; // [esp+124h] [ebp-14h]
  char v52; // [esp+126h] [ebp-12h]
  int v53; // [esp+134h] [ebp-4h]

  v46 = 15;
  v45 = 0;
  LOBYTE(v44) = 0;
  v53 = 0;
  v43 = 15;
  v42 = 0;
  LOBYTE(v41) = 0;
  LOBYTE(v53) = 1;
  v0 = 0;
  v48 = 1684630885;
  LOWORD(v49) = 97;
  *(_OWORD *)Memory = xmmword_40528C;    // 小端'adimehtadimehtadimeht',正常大端是'themidathemidathemida'
  v51 = 11836;
  v52 = 0;
  v50 = xmmword_4052A4;    // 小端'.<<<<....++++---->',正常大端是'>----++++....<<<<.'
  do
  {
    sub_4021E0(1u, (*((_BYTE *)Memory + v0) ^ *((_BYTE *)&v50 + v0)) + 22);    // 做了异或再+22
    ++v0;
  }
  while ( v0 < 18 );
  v1 = 0;
  v49 = 15;
  v48 = 0;
  LOBYTE(Memory[0]) = 0;
  LOBYTE(v53) = 2;
  v2 = v43;
  v3 = (void **)v41;
  do
  {
    v4 = &v41;
    if ( v2 >= 0x10 )
      v4 = v3;
    sub_4021E0(1u, *((_BYTE *)v4 + v1++) + 9);    // 再+9
  }
  while ( v1 < 18 );
  memset(&Dst, 0, 0xB8u);
  sub_401620(v5, v6, v7, v8);
  LOBYTE(v53) = 3;
  if ( v32[*(_DWORD *)(Dst + 4)] & 6 )
  {
    v9 = sub_402A00(std::cerr, "?W?h?a?t h?a?p?p?e?n?", sub_402C50);
    std::basic_ostream<char,std::char_traits<char>>::operator<<(v9, v10);
    exit(-1);
  }
  sub_402E90(&Dst, &v44);
  v11 = &v33;
  if ( File )
  {
    if ( !(unsigned __int8)sub_4022F0(&v33) )
      v11 = 0;
    if ( fclose(File) )
      v11 = 0;
  }
  else
  {
    v11 = 0;
  }
  v38 = 0;
  v35 = 0;
  std::basic_streambuf<char,std::char_traits<char>>::_Init(&v33);
  v36 = dword_408590;
  File = 0;
  v37 = dword_408594;
  v34 = 0;
  if ( !v11 )
    std::basic_ios<char,std::char_traits<char>>::setstate((char *)&Dst + *(_DWORD *)(Dst + 4), 2, 0);
  v13 = Memory;
  if ( v49 >= 0x10 )
    v13 = (void **)Memory[0];
  if ( sub_4020C0(v12, v45, v13, v48) )
  {
    v28 = sub_402A00(std::cout, "=W=r=o=n=g=K=e=y=", sub_402C50);
  }
  else
  {
    v14 = sub_402A00(std::cout, "|------------------------------|", sub_402C50);
    std::basic_ostream<char,std::char_traits<char>>::operator<<(v14, v15);
    v16 = sub_402A00(std::cout, "|==============================|", sub_402C50);
    std::basic_ostream<char,std::char_traits<char>>::operator<<(v16, v17);
    v18 = sub_402A00(std::cout, "|==============================|", sub_402C50);
    std::basic_ostream<char,std::char_traits<char>>::operator<<(v18, v19);
    v20 = sub_402A00(std::cout, "|==============================|", sub_402C50);
    std::basic_ostream<char,std::char_traits<char>>::operator<<(v20, v21);
    v22 = sub_402A00(std::cout, "\\  /\\  /\\  /\\  /\\==============|", sub_402C50);
    std::basic_ostream<char,std::char_traits<char>>::operator<<(v22, v23);
    v24 = sub_402A00(std::cout, " \\/  \\/  \\/  \\/  \\=============|", sub_402C50);
    std::basic_ostream<char,std::char_traits<char>>::operator<<(v24, v25);
    v26 = sub_402A00(std::cout, "                 |-------------|", sub_402C50);
    std::basic_ostream<char,std::char_traits<char>>::operator<<(v26, v27);
    std::basic_ostream<char,std::char_traits<char>>::operator<<(std::cout, sub_402C50);
    v28 = sub_402A00(std::cout, "Congrats You got it!", sub_402C50);
  }
  std::basic_ostream<char,std::char_traits<char>>::operator<<(v28, v30);
  sub_401570(&v40);
  std::basic_ios<char,std::char_traits<char>>::~basic_ios<char,std::char_traits<char>>(&v40);
  if ( v49 >= 0x10 )
    sub_402630(Memory[0], v49 + 1);
  if ( v2 >= 0x10 )
    sub_402630(v3, v2 + 1);
  result = v46;
  if ( v46 >= 0x10 )
    result = sub_402630(v44, v46 + 1);
  return result;
}

python解密脚本

image-20240129110052537.png

Windows_Reverse1

upx脱壳,ida分析

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[1024]; // [esp+4h] [ebp-804h] BYREF
  char v5[1024]; // [esp+404h] [ebp-404h] BYREF

  memset(v5, 0, sizeof(v5));
  memset(v4, 0, sizeof(v4));
  printf("please input code:");
  scanf("%s", v5);
  sub_401000(v5);    //加密函数,对输入做处理
  if ( !strcmp(v4, "DDCTF{reverseME}") )    // 这里是用v4来比较,说明上面函数中对v4做了处理
    printf("You've got it!!%s\n", v4);
  else
    printf("Try again later.\n");
  return 0;
}

查看sub_401000函数,发现唯一可能赋值给主函数里v4的是v1

unsigned int __cdecl sub_401000(const char *a1)
{
  _BYTE *v1; // ecx
  unsigned int v2; // edi
  unsigned int result; // eax
  const char *v4; // ebx

  v2 = 0;
  result = strlen(a1);
  if ( result )
  {
    v4 = (const char *)(a1 - v1);
    do
    {
      *v1 = byte_402FF8[(char)v1[(_DWORD)v4]];
      ++v2;
      ++v1;
      result = strlen(a1);
    }
    while ( v2 < result );
  }
  return result;
}

为了分析v1到底是什么,需要去阅读汇编代码。回到主函数看v4,可以看到在调用加密函数前使用lea指令将v4地址传入了ecx

image-20240131135227471.png

再进加密函数看汇编代码,可以看到v1指向的正是ecx,同时后面还有位赋值操作

image-20240131140659267.png

接着就可以安心分析加密函数,首先byte_402FF8是一个字符数组,所以里面应该是数字型下标

image-20240131140927795.png

v4 = (const char *)(a1 - v1);这句代码中得到的是a1(输入的字符串地址)-v1(主函数v4的基地址),即地址差值

同时在循环中*v1 = byte_402FF8[(char)v1[(_DWORD)v4]];表示v1的基地址+(地址差值),取输入字符串一个字符(ascii值),从数组里获取对应值赋给v1(很绕)。总结来说思路很简单就是一个替换加密,由于没法直接找到这个字符数组值或者右键导出,因此使用ida自带python获取地址范围内的值

addr=0x00402FF8
arr=[]
for i in range(0x00403078-0x00402FF8):    #数组的个数
    arr.append(idc.get_wide_byte(addr+i))
print(arr)
enflag = 'DDCTF{reverseME}'
flag = ''
for s in enflag:
    flag += chr(arr[ord(s)])
print(flag)    # ZZ[JX#,9(9,+9QY!
最后修改:2024 年 02 月 05 日
如果觉得我的文章对你有用,请随意赞赏