[安全] BUUOJ 逆向题目WP (一)

xor 1

核心逻辑

for ( i = 1; i < 33; ++i )
    __b[i] ^= __b[i - 1]; // 这块是输入
if ( !strncmp(
        __b,
        global,                               // "f\nk\fw&O.@\x11x\rZ;U\x11p\x19F\x1Fv\"M#D\x0Eg\x06h\x0FG2O"
        0x21u) )
    printf("Success");

异或的逆运算还是异或,反着异或一遍即可

exp.c

unsigned char s[] = {
    0x66, 0x0A, 0x6B, 0x0C, 0x77, 0x26, 0x4F, 0x2E, 0x40, 0x11, 
    0x78, 0x0D, 0x5A, 0x3B, 0x55, 0x11, 0x70, 0x19, 0x46, 0x1F, 
    0x76, 0x22, 0x4D, 0x23, 0x44, 0x0E, 0x67, 0x06, 0x68, 0x0F, 
    0x47, 0x32, 0x4F, 0x00
};
 
#include <stdio.h>
int main() {
    for (int i = 0x21u - 1; i >= 0; --i) {
        s[i] ^= s[i - 1];
    }
    puts(s);
}

flag{QianQiuWanDai_YiTongJiangHu}

一些知识的补充

使用 IDA,框选一段数据之后可以通过快捷键 Shift + E 快速复制想要的格式 (不过只有 C 语言版本的)
如果需要 Python 版本的数据可以使用 LazyIDA 插件, 安装之后右键 Dump 即可

reverse 3

main 函数中主要逻辑, 其中 Str 是输入

Source = sub_4110BE(Str, v3, &v14);
strncpy(Destination, Source, 40u);
j_1 = j_strlen(Destination);
for ( j = 0; j < j_1; ++j )
Destination[j] += j;
MaxCount = j_strlen(Destination);
if ( !strncmp(
    Destination,
    Str2,                                 // "e3nifIH9b_C@n@dH"
    MaxCount) )
sub_41132F("rigth flag!\n", v8);

密文 是将 明文 经过一次加密函数之后再加上当前字符的 offset 得到的

接着查看加密函数
看到一个标准 base64 码表, 且往入参处的最后一个参数那里传了一个 len / 3 * 4, 大概率是 base64 编码算法
试了一下, 确实是

更改变量的名字, 类型 都对代码审计很有帮助

( 遇到使用地址作为名字的数组且本应连续却被切分成不同名字的变量的时候可以将包含 align 的那一行一起 U 一下 (应该是去除定义的意思) )

exp.py

s = [
    0x65, 0x33, 0x6E, 0x69, 0x66, 0x49, 
    0x48, 0x39, 0x62, 0x5F, 0x43, 0x40, 
    0x6E, 0x40, 0x64, 0x48,
]
 
for i in range(len(s)):
    s[i] -= i
 
import base64
s = base64.b64decode(bytes(s))
print(s)

flag{i_l0ve_you}

一些知识的补充

  1. 实际上 base64 是 64 进制的意思, 我们可以将信息看成是一个大数字, 那么 8bits 一个位置的 unsigned char [] 其实就是 256 进制的大整数. 由于 base64 一个位置只有 6bits, 故编码后长度是 string 长度的 4/3 左右 (base64 * 6 == string * 8).

  2. 使用 struct 可以方便地转换数组表示的大整数 和 字节数组 (bytes). 格式化字符串 fmt 由 字节序格式符 组成. 字节序: < 小端序; > 大端序 格式符:

    • x 填充字节 (用于跳过数据);
    • c / b / B (char / signed char / unsigned char);
    • h / H (short / unsigned short)
    • I / Q (unsigned int / unsigned long long 小写的被我省略了)
    • f / d (float / double)
    • s ( xs表示长度为 x 的字符串 )

    struct.pack(fmt, v1, v2, ...) -> bytes 可以将 vx 所代表的数字按字节序组成一个 bytes
    常用示例: data_list = [0x11223344, 0xAABBCCDD];bytes_data = struct.pack(f'<{len(data_list)}I', *data_list)

    struct.unpack(fmt, buf) -> tuple 将 bytes 对象按照 fmt 要求返回一个 tuple
    struct.calcsize(fmt) -> int 返回 fmt 要求的字节大小

helloword

估计是 java 逆向基础

jadx 打开, 查看 AndroidManifest, 查看 MainActivity, flag 就躺在里面

不一样的 flag

迷宫题
简单梳理一下源码可以建立等价程序

#include <bits/stdc++.h>
 
int main() {
    char map[26] = \
    "*1111"
    "01000"
    "01010"
    "00010"
    "1111#";
    // 222441144222
    int x = 0, y = 0;
    int input;
 
    while (true) {
        std::cin >> input;
 
        switch (input) {
            case 1: --y; break;
            case 2: ++y; break;
            case 3: --x; break;
            case 4: ++x; break;
            default: break;
        }
 
        // std::cout << x << " " << y << "\n";
        // std::cout << map[5 * y + x] << "\n";
 
        if (x > 4 || y > 4) exit(1);
        if (map[5 * y + x] == '1') exit(1);
        if (map[5 * y + x] == '#') break;
    }
 
    std::cout << "yes\n";
}

flag{222441144222}

SimpleRev

主要逻辑是这段

while ( 1 )
  {
    char = getchar();
    if ( char == '\n' )
      break;
    if ( char == ' ' )
    {
      ++v4;
    }
    else
    {
      if ( char <= 96 || char > 122 )
      {
        if ( char > 64 && char <= 90 )
        {
          str2[v4] = (char - 39 - key[v5 % keylen] + 'a') % 26 + 'a';
          ++v5;
        }
      }
      else
      {
        str2[v4] = (char - 39 - key[v5 % keylen] + 'a') % 26 + 'a';
        ++v5;
      }
      if ( !(v5 % keylen) )
        putchar(' ');
      ++v4;
    }
  }

由于 %26 运算的不可逆, 可以建立反映射表来求得可能的原值

exp.py

import struct 
from collections import defaultdict
 
key1 = "ADSFK".encode()
key2 = struct.pack("<Q", 0x534C43444E)
key  = key1 + key2
 
tex1 = "kills".encode()
tex2 = struct.pack("<Q", 0x776F646168)
text = tex1 + tex2
 
key  = list(key)
text = list(text)
LEN  = 10 # from reverse
 
for i in range(LEN):
    key[i] += 32 * (key[i] > 64 and key[i] <= 90)
 
dist: list[defaultdict[int, list[int]]] \
    = [defaultdict(list) for _ in range(10)]
 
for i in range(LEN):
    for c in range(256):
        f = lambda x: \
            (x - 39 - key[i] + ord('a')) % 26 + ord('a')
        dist[i][f(c)].append(c)
 
 
for i in range(LEN):
    c = text[i]
    for v in dist[i][c]:
        if (65 <= v and v <= 90) or (97 <= v and v <= 122):
            print(chr(v), end=" ")
    print()

输出

K e 
L f 
D x
Q k
C w
U o
D x
F z
Z t
O i

实测选大写的为 flag

注意

遇到类似 *(_QWORD *)src = 0x534C43444ELL; 这种赋值的时候要注意大小端序, 一般是小端序, 不要反了

[GXYCTF2019]luck_guy

主要逻辑如下

unsigned __int64 get_flag()
{
  unsigned int seed; // eax
  int i; // [rsp+4h] [rbp-3Ch]
  int j; // [rsp+8h] [rbp-38h]
  __int64 s; // [rsp+10h] [rbp-30h] BYREF
  char v5; // [rsp+18h] [rbp-28h]
  unsigned __int64 v6; // [rsp+38h] [rbp-8h]
 
  v6 = __readfsqword(0x28u);
  seed = time(0);
  srand(seed);
  for ( i = 0; i <= 4; ++i )
  {
    switch ( rand() % 200 )
    {
      case 1:
        puts("OK, it's flag:");
        memset(&s, 0, 40u);
        strcat((char *)&s, f1);                 // "GXY{do_not_"
        strcat((char *)&s, &f2);
        printf("%s", (const char *)&s);
        break;
      case 2:
        printf("Solar not like you");
        break;
      case 3:
        printf("Solar want a girlfriend");
        break;
      case 4:
        s = 0x7F666F6067756369LL;
        v5 = 0;
        strcat(&f2, (const char *)&s);
        break;
      case 5:
        for ( j = 0; j <= 7; ++j )
        {
          if ( j % 2 == 1 )
            *(&f2 + j) -= 2;
          else
            --*(&f2 + j);
        }
        break;
      default:
        puts("emmm,you can't find flag 23333");
        break;
    }
  }
  return __readfsqword(0x28u) ^ v6;
}

虽然是用随机数来决定运行顺序的, 依然可以捋一下这几个块之间的关系
case 4 明显是 f2 的初始化, 然后 case 5 是 f2 的变换处理, 最后 case 1 是结合 flag 前半部分进行输出

那么写个脚本模拟这个过程即可, 不需要 patch

import struct
 
s = list(struct.pack("<Q", 0x7F666F6067756369))
 
for j in range(8):
    if j % 2 == 1:
        s[j] -= 2
    else:
        s[j] -= 1
 
print("GXY{do_not_".encode() + bytes(s))

GXY{do_not_hate_me}

Java逆向解密

jadx, 没什么好说的

flg = [180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129, 135, 191, 65]
flg = bytes([(c ^ 32) - ord('@') for c in flg]).decode()
print(flg)

This_is_the_flag_!

[BJDCTF2020]JustRE

大概是 Windows API 的图形界面程序, 本来是想 Strings View 找组件名字来一直 xrefs 的, 没想到直接看到了神秘字符串 .data:00407030 aBjdDD2069a4579 db ' BJD{%d%d2069a45792d233ac}',0

sprintf(Buffer, " BJD{%d%d2069a45792d233ac}", 19999, 0);

BJD{1999902069a45792d233ac}

一些知识的补充

善用字符串, 虽然真正复杂的题目都会剥离符号以及字符串混淆