Reverse> [深思杯2019]re3

发布于 2022-12-14  16 次阅读


战队要搞什么周练,用的360平台里自带题库的题(pwn和re难得一匹,虽然大部分是因为我太菜)
pwn的python沙盒打了个非预期解(实在想不明白预期解是怎么搞出来的,也没搜到原题的wp),没事就跑去隔壁re玩玩

脱壳

原程序拖进IDA只有一个start,说明加了壳,需要我们脱壳
用工具分析出来是upx壳,但是用脱壳工具没办法脱,只能手动脱壳了,正好见识下简易的upx手动脱壳到底是个什么流程(这里用的是ESP定律,完整的原理就不太清楚了)

从EntryPoint开始单步,直到ESP发生变化,然后ESP处右键进去它的Dump里,然后在Dump起始处下个硬件断点

然后一路单步到一处跳转,可以发现此处有特征机器码55(听某re佬说的,我也不懂),所以从这里开始就是原程序了

然后点工具栏里的Scylla插件,转储这段程序,重建PE文件,然后搜索并获取IAT(导入地址表,大概是PE文件用于装载Windows的DLL用的),修复PE文件,最后修复刚刚转储的程序,脱壳就完成了

拖进IDA后就能看到反汇编出来的各种函数了

逻辑分析

main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp-4h] [ebp-3Ch]
  char v5; // [esp+4h] [ebp-34h] BYREF
  char v6[51]; // [esp+5h] [ebp-33h] BYREF

  v5 = 0;
  memset(v6, 0, 0x30u);
  v6[48] = 0;
  sub_401000();
  puts(aHiSdnisc2019);
  sub_40188D(aSerialnumber, v4);
  scanf("%s", &v5);
  if ( sub_401150(&v5) == 1 )
    puts(aCongratulation);
  else
    puts(aTryAgain);
  return sub_40180D(Buffer);
}

sub_401000():读取data.bin --> malloc出一个大堆块存放文件 --> 文件头部地址传入指针*Buffer

int sub_401000()
{
  FILE *v0; // eax
  FILE *v1; // esi
  void *v2; // edi
  int i; // eax

  v0 = fopen(FileName, Mode);
  v1 = v0;
  if ( !v0 )
    exit(1);
  fseek(v0, 0, 2);
  ElementSize = ftell(v1);
  rewind(v1);
  v2 = malloc(ElementSize + 10);
  Buffer = v2;
  if ( !v2 )
    exit(1);
  for ( i = 0; i < 32; ++i )
    byte_409064[i] ^= 0x66u;
  memset(v2, 0, ElementSize + 10);
  fread(Buffer, ElementSize, 1u, v1);
  return fclose(v1);
}

sub_40188D():不知道干嘛用的,事实证明也不用管

sub_401150():加密

int __cdecl sub_401150(const char *a1)
{
  unsigned int v1; // ebx
  int i; // ecx
  char *v3; // esi
  char *v4; // ecx
  char v5; // al
  bool v6; // zf
  signed int v7; // ecx
  char v8; // al
  char v9; // al
  unsigned int v10; // edx
  int result; // eax
  char v12; // [esp+Ch] [ebp-34h] BYREF
  char v13[51]; // [esp+Dh] [ebp-33h] BYREF

  v12 = 0;
  memset(v13, 0, 0x30u);
  v13[48] = 0;
  v1 = 0;
  if ( strlen(a1) )
  {
    for ( i = 1 - (_DWORD)&v12; ; i = 1 - (_DWORD)&v12 )
    {
      v3 = &v12 + v1;
      v4 = &v12 + v1 + i;
      v5 = a1[(_DWORD)v4 - 1];
      v7 = (unsigned int)v4 & 0x80000001;
      v6 = v7 == 0;
      *(&v12 + v1) = v5;
      if ( v7 < 0 )
        v6 = (((_BYTE)v7 - 1) | 0xFFFFFFFE) == -1;
      v8 = v6 ? v5 ^ 0x19 : v5 ^ 0x20;
      *v3 = v8;
      v9 = sub_4010C0(v8);
      *v3 = v9;
      byte_40B9C0[v1++] = v9;
      if ( v1 >= strlen(a1) )
        break;
    }
  }
  v10 = 0;
  if ( !strlen(a1) )
    goto LABEL_15;
  do
  {
    if ( *(&v12 + v10) != byte_409064[v10] )
      break;
    ++v10;
  }
  while ( v10 < strlen(a1) );
  if ( v10 == 32 )
    result = 1;
  else
LABEL_15:
    result = 0;
  return result;
}

流程比较复杂,一步步分析:

  • *Buffer指向存在堆里的data.bin
  • 对用户输入的单数双数位的数据分别异或
    • 光看反编译的代码看不懂,动态调试看v6的变化发现的
  • sub_4010C0(),遍历data.bin按照逻辑对每个字符进行一个流程的加密
  • 比对密文byte_409064
    • main函数里对这段密文进行了异或,所以在静态下是假密文,需要debug才能显现真密文,或者写EXP的时候异或一下
    • 注意小端序
  • 最后比对加密后的用户输入长度是不是32

sub_4010C0():根据data.bin(相当于一整个加密流程本),读取到特定值时就做特定加密

  • 不知道用什么指令欺骗了反编译器导致反编译会缺失一段(所以没找到要怎么patch),总之需要看汇编找加密逻辑,IDA的逻辑流程图帮了大忙
  • 具体逻辑太麻烦了这里就不写了,只用对着图里涉及到比较的数值一个个写function就行,不会很难

用密文直接解密很难(加密过程应该有信息丢失),采用爆破比较合适

EXP

字节处理u32这里搞不太懂python的类型转换,所以干脆上通用的移位处理算了

from pwn import *

c = [0x5D, 0x2E, 0x4C, 0x35, 0x49, 0x30, 0x06, 0x6E, 0x77, 0x73, 0x64, 0x30, 0x67, 0x72, 0x68, 0x3C, 0x76, 0x55, 0x5A, 0x52, 0x58, 0x2F, 0x7A, 0x3A, 0x53, 0x35, 0x5C, 0x79, 0x2A, 0x71, 0x09, 0x1C]
data = open("./data.bin","rb").read()

list = []
i = 0
while i < len(data):
    # tmp = u32(data[i] + data[i + 1] + data[i + 2] + data[i + 3])              # only for python2
    tmp = data[i + 3] << 24 | data[i + 2] << 16 | data[i + 1] << 8 | data[i]
    if tmp == 0xc8b5bdc6:
        list.append(1)
    elif tmp == 0xc5d0cfb3:
        list.append(2)
    elif tmp == 0xb5d2b4be:
        list.append(3)
    elif tmp == 0xbfc7bbb8:
        list.append(4)
    elif tmp == 0xc6c9d1d3:
        list.append(5)
    elif tmp == 0xc9d3d4d7:
        list.append(6)
    elif tmp == 0xced6a8b7:
        list.append(7)
    elif tmp == 0xfab9aeb0:
        list.append(8)
    i += 4

def f1(char):
    return((char + 1) ^ 0x19) & 0xff
def f2(char):
    return((char ^ 0x19) - 1) & 0xff
def f3(char):
    return(char >> 1) & 0xff
def f4(char):
    return(char ^ 0x66) & 0xff
def f5(char):
    return(char << 1) & 0xff
def f6(char):
    return(char - 1) & 0xff
def f7(char):
    return(char << 2) & 0xff
def f8(char):
    return(char + 1) & 0xff

flag = ""
count = 0
for t in c:
    count += 1
    for i in range(33,126):
        tmp = i
        if count % 2 == 0:
            code = (i ^ 0x19) 
        else:
            code = (i ^ 0x20) 
        for index in list:
            if index == 1:
                code = f1(code)
            elif index == 2:
                code = f2(code)
            elif index == 3:
                code = f3(code)
            elif index == 4:
                code = f4(code)
            elif index == 5:
                code = f5(code)
            elif index == 6:
                code = f6(code)
            elif index == 7:
                code = f7(code)
            elif index == 8:
                code = f8(code)
        if code == t:
            flag += chr(i)
            break

print(flag)
print("length:", count, count==32)
$ python3 exp.py
w5z6s7duQtR7AyFSTVhYv0H1m6JZxb33
length: 32 True

验证一下

> ./re_HeXie_dump_SCY.exe

      Hi,  SDNISC 2019 ~~~


serialNumber: w5z6s7duQtR7AyFSTVhYv0H1m6JZxb33

 -->   Congratulations ~~~   <--

flag{w5z6s7duQtR7AyFSTVhYv0H1m6JZxb33}

参考