Bootstrap

[安网杯 2021] REV WP

hit_re

如果报dll缺失错误, 在https://www.dll-files.com/下载相应的 32 / 64 bits 的dll文件拷贝到C:\Windows\System32或者C:\Windows\SysWOW64文件夹下即可运行

在这里插入图片描述

无壳, 拖进IDA32, 直接逆main函数

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  int result; // eax
  int v4; // eax
  int v5; // [esp-4h] [ebp-414h]
  int v6; // [esp-4h] [ebp-414h]
  int (__cdecl *v7)(int); // [esp-4h] [ebp-414h]
  int v8; // [esp+0h] [ebp-410h]
  int v9; // [esp+0h] [ebp-410h]
  int v10; // [esp+0h] [ebp-410h]
  int v11; // [esp+0h] [ebp-410h]
  int v12; // [esp+0h] [ebp-410h]
  int v13; // [esp+0h] [ebp-410h]
  int v14; // [esp+0h] [ebp-410h]
  int v15; // [esp+0h] [ebp-410h]
  int v16; // [esp+0h] [ebp-410h]
  int v17; // [esp+4h] [ebp-40Ch]
  int v18; // [esp+4h] [ebp-40Ch]
  int v19; // [esp+4h] [ebp-40Ch]
  int v20; // [esp+4h] [ebp-40Ch]
  int v21; // [esp+4h] [ebp-40Ch]
  int v22; // [esp+4h] [ebp-40Ch]
  int v23; // [esp+10h] [ebp-400h]
  int v24; // [esp+18h] [ebp-3F8h]
  void *v25; // [esp+24h] [ebp-3ECh]
  int v26; // [esp+30h] [ebp-3E0h]
  int v27; // [esp+30h] [ebp-3E0h]
  int v28; // [esp+38h] [ebp-3D8h]
  int v29; // [esp+38h] [ebp-3D8h]
  int v30[9]; // [esp+4Ch] [ebp-3C4h] BYREF
  char v31[36]; // [esp+70h] [ebp-3A0h] BYREF
  int v32[9]; // [esp+94h] [ebp-37Ch] BYREF
  char v33[36]; // [esp+12Ch] [ebp-2E4h] BYREF
  char T_F; // [esp+1C7h] [ebp-249h]
  char act; // [esp+36Bh] [ebp-A5h]
  char *q; // [esp+374h] [ebp-9Ch]
  char *p; // [esp+380h] [ebp-90h]
  char *str_in; // [esp+38Ch] [ebp-84h]
  char v39; // [esp+39Bh] [ebp-75h]
  char *j; // [esp+3A4h] [ebp-6Ch]
  char *i; // [esp+3B0h] [ebp-60h]
  char *moves; // [esp+3BCh] [ebp-54h]
  int not_right_moves; // [esp+3C8h] [ebp-48h]
  int right_moves; // [esp+3D4h] [ebp-3Ch]
  char data_in[32]; // [esp+3E0h] [ebp-30h] BYREF
  int v46; // [esp+40Ch] [ebp-4h]

  __CheckForDebuggerJustMyCode(&unk_42E069);
  print(std::cout, "your solution: ");
  sub_41113B(v8, v17);
  v46 = 0;
  readin(std::cin, data_in);
  if ( moves_count(v9, v18) != 50 )             // input count 50
    goto fail;
  right_moves = 0;
  not_right_moves = 0;
  moves = data_in;
  i = (char *)sub_41105A(v10, v19);
  j = (char *)sub_411005(v11, v20);
  while ( i != j )
  {
    v39 = *i;
    if ( v39 == 'd' )
    {
      ++right_moves;
      if ( not_right_moves != 6 && not_right_moves != 2 )
        _exit(1);
      not_right_moves = 0;
    }
    else
    {
      ++not_right_moves;
    }
    ++i;
  }
  if ( right_moves == 10 )                      // right moves 10 times
  {
    str_in = data_in;
    p = (char *)sub_41105A(v10, v19);
    q = (char *)sub_411005(v12, v21);
    while ( p != q )
    {
      act = *p;
      switch ( act )
      {
        case 'a':
          --left_right;
          break;
        case 'd':
          ++left_right;
          break;
        case 's':
          ++up_down;
          break;
        case 'w':
          --up_down;
          break;
        default:
          _exit(-1);
      }
      if ( map[11 * up_down + left_right] != 1 )// == 1 can move in
        _exit(-2);
      ++p;
    }
    sub_4111C2((int)data_in);
    v26 = sub_4113E8((int)v33);
    LOBYTE(v46) = 1;
    T_F = sub_4114D3("dcc1df36a15968fb206fe52294bb4130", v26);
    LOBYTE(v46) = 0;
    sub_4114AB(v13, v22);
    if ( T_F )
    {
      v28 = print(std::cout, "flag is:  flag{");
      v27 = sub_41160E(data_in, (int)v32, 0, 0x10u);
      LOBYTE(v46) = 2;
      sub_4111C2(v27);
      v25 = (void *)sub_4113E8((int)v31);
      LOBYTE(v46) = 3;
      v24 = sub_41160E(v25, (int)v30, 0, 0x10u);
      LOBYTE(v46) = 4;
      v4 = sub_411451(v28, v24);
      v23 = print(v4, "}");
      std::ostream::operator<<(v23);
      LOBYTE(v46) = 3;
      sub_4114AB((int)sub_411564, v14);
      LOBYTE(v46) = 2;
      sub_4114AB(v5, v15);
      LOBYTE(v46) = 0;
      sub_4114AB(v6, v16);
    }
    else
    {
      v29 = print(std::cout, "a little deviation");
      v7 = sub_411564;
      std::ostream::operator<<(v29);
    }
    system("pause");
    v46 = -1;
    sub_4114AB((int)v7, v14);
    result = 0;
  }
  else
  {
fail:
    v46 = -1;
    sub_4114AB(v10, v19);
    result = -10;
  }
  return result;
}

主要逻辑是走出地图, 要求步数恰好为50步, 并且往右走10次, 而且往右走之前需要往其他方向走2或者6次, 提取出地图数据

import idaapi

addr = 0x0042B008
arr = []
for i in range(275):
    arr.append(idaapi.get_dword(addr + 4*i))
print(arr)

打印出地图

mapdata = [1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
maps = []
for i in range(25):
    maps.append(mapdata[i*11: i*11+11])

for _ in maps:
    print(_)

'''
[1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0]
[1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0]
[1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1]
[0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1]
[0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1]
[0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0]
[1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0]
[1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0]
[1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1]
[1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0]
[1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0]
[1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0]
[0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1]
[1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1]
[1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1]
[1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1]
[1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1]
[1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0]
[1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0]
[1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0]
[1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
'''

写一个算法搜索满足条件的路径, 并且找到一个路径需要和MD5值dcc1df36a15968fb206fe52294bb4130比较, 匹配hash值成功的50步路径即为解

这个路径还需要注意, 因为main函数流程中要求往右的次数为10, 显然不能通过往左走凑步数, 因为往左走1次要到达右下角至少需要11次往右走, 那就不符合判断条件, 所以为了凑步数只能上下往返, 因此可以判断路径中不包含’a’, 只包含’w’, ‘s’, ‘d’, 而且根据往右走之前经过距上次往右走的步数, 要么是2步, 要么是6步, 所以可以得到固定的往返模式来凑步数, 接下来就变成算法题

def DFS(step, hlevel, path):
    if step == 6: 
        if hlevel == 2:
            print(path)
        return
        
    if hlevel == 0:
        DFS(step + 1, hlevel + 1, path+'s')
    elif hlevel == 1:
        DFS(step + 1, hlevel + 1, path+'s')
        DFS(step + 1, hlevel - 1, path+'w')
    else: 
        DFS(step + 1, hlevel - 1, path+'w')

DFS(0, 0, '')

'''
sswsws
sswwss
swssws
swswss
'''

可见上下往返的6步模式有4种, 那么只需要用这4种模式凑步数即可, 最短路径是ssd * 10总共30步, 所以需要引入5次往返模式多凑20步, C 10 5 ∗ 4 5 = 258048 C_{10}^5 * 4^5 = 258048 C10545=258048种可能性进行回溯爆破, 其实只需要大概3s

from itertools import combinations
from hashlib import *

patterns = ['swswssd','swsswsd','sswwssd','sswswsd']
L = [_ for _ in range(10)]
coms = list(combinations(L, 5))
# print(coms)

# paths = ['ssd' for _ in range(10)]
# count = 0
# path = ''.join(paths)
# print(path, len(path))

def BackTrace(step, paths, comb):
    if step == 10:
        path = ''.join(paths)
        if md5(path.encode(encoding='UTF-8')).hexdigest() == 'dcc1df36a15968fb206fe52294bb4130':
            print('find the flag!!!')
            print(path)
            print()
        return    

    if step not in comb:
        BackTrace(step + 1, paths, comb)
    else:
        paths[step] = patterns[0]
        BackTrace(step + 1, paths, comb)
        paths[step] = patterns[1]
        BackTrace(step + 1, paths, comb)
        paths[step] = patterns[2]
        BackTrace(step + 1, paths, comb)
        paths[step] = patterns[3]
        BackTrace(step + 1, paths, comb)

# print(len(coms))
for comb in coms:
    paths = ['ssd' for _ in range(10)]
    BackTrace(0, paths, comb)

# path = 'ssdsswswsdssdswswssdssdswsswsdsswswsdssdssdswswssd'
# print(md5(path.encode(encoding='UTF-8')).hexdigest() == 'dcc1df36a15968fb206fe52294bb4130')

在这里插入图片描述

pypy

python文件打包的exe, 用pyinstxtractor解析
https://github.com/extremecoders-re/pyinstxtractor
pyinstxtractor.py解压出文件夹, 这里用python3.7才行, 需要和压缩成exe文件所用的python版本一致

falca@DESKTOP-GKDU8KD:/mnt/k/competition/安网杯/re/pypy$ python3.7 pyinstxtractor.py pypy.exe
[+] Processing pypy.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.7
[+] Length of package: 6230873 bytes
[+] Found 61 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pypy.pyc
[+] Found 133 files in PYZ archive
[+] Successfully extracted pyinstaller archive: pypy.exe

You can now use a python decompiler on the pyc files within the extracted directory

然后用uncompyle6pypy.pyc文件逆回py文件
uncompyle6 pypy.pyc > pypy.py
得到源码

# uncompyle6 version 3.8.0
# Python bytecode 3.7.0 (3394)
# Decompiled from: Python 3.8.10 (tags/v3.8.10:3d8993a, May  3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)]
# Embedded file name: pypy.py
import hashlib
if __name__ == '__main__':
    nums = []
    a, b, c = (3001, 2137, 4729)
    n = 100000
    num = 1
    while len(nums) != n:
        num_copy = num
        while num_copy != 1:
            if num_copy % a == 0:
                num_copy //= a
            elif num_copy % b == 0:
                num_copy //= b
            elif num_copy % c == 0:
                num_copy //= c
            else:
                break

        if num_copy == 1:
            nums.append(num)
        num += 1

    print(nums[(n - 1)])
    m = hashlib.md5()
    b = str(nums[(n - 1)]).encode(encoding='utf-8')
    m.update(b)
    result = m.hexdigest()
    print('flag{' + result + '}')
# okay decompiling pypy.pyc

简单来说就是求以3001, 2137,4729为因数组成的倍数, 从小到大排序的第100000个数的MD5值
算法题, 用优先队列heapq优化性能, 每次向优先队列里加入Min * 3001, Min * 2137, Min * 4729贪心取得当前最小的倍数, 当heapq弹出100000个Min, 则得到答案

from hashlib import *
from heapq import *

ps = [3001, 2137, 4729]
heap = []
heappush(heap, 1)
occupied = set()
occupied.add(1)

count = 0
Min = 0
while True:
    Min = heappop(heap)
    count += 1
    if count == 100000: break

    for i in range(3):
        tmp = Min * ps[i]
        if tmp not in occupied:
            heappush(heap, tmp)
            occupied.add(tmp)
    

print(Min)
print('flag{' + md5(str(Min).encode(encoding='UTF-8')).hexdigest() + '}')

'''
16405850262570740513897217717623720600448855987320514551726154873739883293720282851034424371051874484044854674904735438772866326784386465461577479345634270005309256879590279090175542403199472872896278587926077044001237403554885317937416574659330592944654425785521663598285833017718525414609
flag{ba64f885157a04966c5173fb24590032}
'''

总结

这是去年打的第一次线下赛, 时隔一年来补一下题, 当时因为禁止联网(虽然赛场全是热点, 我当时开热点还被捉了, xs), 环境配不起来REV这两题都没做出来, 后来各种事一直没补题, 现在感觉积累足够了, 差不多可以把近两年的赛题都做一做.
总体来说这两题逻辑不算很复杂, 有的函数调用链很深逻辑判断不了, 就直觉猜加动调验证, 软件加密也上了花指令反调试或者保护壳(虽然没啥干扰), 逆向出来就变成算法题了, 算法题多刷刷也不算特别难, 主要是能联网, 难度会低很多.

;