Feb_Note&WP

RE做题过程零散笔记

工具使用相关

IDA

  • shift+E:提取数据

X64dbg

X64dbg使用用技巧与实用插件合集

markdown语法

  • 折叠代码块:code
    summary后写折叠后显示的标题,同时注意代码前后各空一行

Android逆向

APKIDE
Java环境搭建与配置

Python逆向

pyc

pyc文件就是由Python文件经过编译后所生成的文件,.py文件编译成pyc文件后加载速度更快而且提高了代码的安全性。pyc的内容与python的版本相关,不同版本编译的pyc文件不一样
直接对pyc文件进行逆向分析是很难的,所以针对这些脚本语言,通常方法为反编译为源码
在线pyc转py

#py to pyc
#py_compile 
pip install py_compile
python -m py_compile xxx.py
#uncompyle6
pip install uncompyle6
uncompyle6 -o xxx.py xxx.pyc

py to exe

py文件打包成exe文件:pyinstxtractor

#py to exe
#pyinstaller
pip install pyinstaller
pyinstaller -F xxx.py
#exe to pyc
#pyinstxtractor.py
python pyinstxtractor.py xxx.exe

需要注意的是转换出来的pyc文件不具有Magic Number即魔术头,需根据Python版本自行补全

Python版本对应Magic Number

  • 在Python3.7及以上版本的编译后二进制文件中,头部除了四字节Magic Number,还有四个字节的空位和八个字节的时间戳+大小信息,后者对文件反编译没有影响,全部填充0即可;
  • Python3.3 - Python3.7(包含3.3)版本中,只需要Magic Number和八位时间戳+大小信息
  • Python3.3 以下的版本中,只有Magic Number和四位时间戳
Magic Number对照表
Known values:
#  Python 1.5:   20121
#  Python 1.5.1: 20121
#     Python 1.5.2: 20121
#     Python 1.6:   50428
#     Python 2.0:   50823
#     Python 2.0.1: 50823
#     Python 2.1:   60202
#     Python 2.1.1: 60202
#     Python 2.1.2: 60202
#     Python 2.2:   60717
#     Python 2.3a0: 62011
#     Python 2.3a0: 62021
#     Python 2.3a0: 62011 (!)
#     Python 2.4a0: 62041
#     Python 2.4a3: 62051
#     Python 2.4b1: 62061
#     Python 2.5a0: 62071
#     Python 2.5a0: 62081 (ast-branch)
#     Python 2.5a0: 62091 (with)
#     Python 2.5a0: 62092 (changed WITH_CLEANUP opcode)
#     Python 2.5b3: 62101 (fix wrong code: for x, in ...)
#     Python 2.5b3: 62111 (fix wrong code: x += yield)
#     Python 2.5c1: 62121 (fix wrong lnotab with for loops and
#                          storing constants that should have been removed)
#     Python 2.5c2: 62131 (fix wrong code: for x, in ... in listcomp/genexp)
#     Python 2.6a0: 62151 (peephole optimizations and STORE_MAP opcode)
#     Python 2.6a1: 62161 (WITH_CLEANUP optimization)
#     Python 2.7a0: 62171 (optimize list comprehensions/change LIST_APPEND)
#     Python 2.7a0: 62181 (optimize conditional branches:
#                          introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE)
#     Python 2.7a0  62191 (introduce SETUP_WITH)
#     Python 2.7a0  62201 (introduce BUILD_SET)
#     Python 2.7a0  62211 (introduce MAP_ADD and SET_ADD)
#     Python 3000:   3000
#                    3010 (removed UNARY_CONVERT)
#                    3020 (added BUILD_SET)
#                    3030 (added keyword-only parameters)
#                    3040 (added signature annotations)
#                    3050 (print becomes a function)
#                    3060 (PEP 3115 metaclass syntax)
#                    3061 (string literals become unicode)
#                    3071 (PEP 3109 raise changes)
#                    3081 (PEP 3137 make __file__ and __name__ unicode)
#                    3091 (kill str8 interning)
#                    3101 (merge from 2.6a0, see 62151)
#                    3103 (__file__ points to source file)
#     Python 3.0a4: 3111 (WITH_CLEANUP optimization).
#     Python 3.0b1: 3131 (lexical exception stacking, including POP_EXCEPT
                          #3021)
#     Python 3.1a1: 3141 (optimize list, set and dict comprehensions:
#                         change LIST_APPEND and SET_ADD, add MAP_ADD #2183)
#     Python 3.1a1: 3151 (optimize conditional branches:
#                         introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE
                          #4715)
#     Python 3.2a1: 3160 (add SETUP_WITH #6101)
#                   tag: cpython-32
#     Python 3.2a2: 3170 (add DUP_TOP_TWO, remove DUP_TOPX and ROT_FOUR #9225)
#                   tag: cpython-32
#     Python 3.2a3  3180 (add DELETE_DEREF #4617)
#     Python 3.3a1  3190 (__class__ super closure changed)
#     Python 3.3a1  3200 (PEP 3155 __qualname__ added #13448)
#     Python 3.3a1  3210 (added size modulo 2**32 to the pyc header #13645)
#     Python 3.3a2  3220 (changed PEP 380 implementation #14230)
#     Python 3.3a4  3230 (revert changes to implicit __class__ closure #14857)
#     Python 3.4a1  3250 (evaluate positional default arguments before
#                        keyword-only defaults #16967)
#     Python 3.4a1  3260 (add LOAD_CLASSDEREF; allow locals of class to override
#                        free vars #17853)
#     Python 3.4a1  3270 (various tweaks to the __class__ closure #12370)
#     Python 3.4a1  3280 (remove implicit class argument)
#     Python 3.4a4  3290 (changes to __qualname__ computation #19301)
#     Python 3.4a4  3300 (more changes to __qualname__ computation #19301)
#     Python 3.4rc2 3310 (alter __qualname__ computation #20625)
#     Python 3.5a1  3320 (PEP 465: Matrix multiplication operator #21176)
#     Python 3.5b1  3330 (PEP 448: Additional Unpacking Generalizations #2292)
#     Python 3.5b2  3340 (fix dictionary display evaluation order #11205)
#     Python 3.5b3  3350 (add GET_YIELD_FROM_ITER opcode #24400)
#     Python 3.5.2  3351 (fix BUILD_MAP_UNPACK_WITH_CALL opcode #27286)
#     Python 3.6a0  3360 (add FORMAT_VALUE opcode #25483)
#     Python 3.6a1  3361 (lineno delta of code.co_lnotab becomes signed #26107)
#     Python 3.6a2  3370 (16 bit wordcode #26647)
#     Python 3.6a2  3371 (add BUILD_CONST_KEY_MAP opcode #27140)
#     Python 3.6a2  3372 (MAKE_FUNCTION simplification, remove MAKE_CLOSURE
#                         #27095)
#     Python 3.6b1  3373 (add BUILD_STRING opcode #27078)
#     Python 3.6b1  3375 (add SETUP_ANNOTATIONS and STORE_ANNOTATION opcodes
#                         #27985)
#     Python 3.6b1  3376 (simplify CALL_FUNCTIONs & BUILD_MAP_UNPACK_WITH_CALL
                          #27213)
#     Python 3.6b1  3377 (set __class__ cell from type.__new__ #23722)
#     Python 3.6b2  3378 (add BUILD_TUPLE_UNPACK_WITH_CALL #28257)
#     Python 3.6rc1 3379 (more thorough __class__ validation #23722)
#     Python 3.7a1  3390 (add LOAD_METHOD and CALL_METHOD opcodes #26110)
#     Python 3.7a2  3391 (update GET_AITER #31709)
#     Python 3.7a4  3392 (PEP 552: Deterministic pycs #31650)
#     Python 3.7b1  3393 (remove STORE_ANNOTATION opcode #32550)
#     Python 3.7b5  3394 (restored docstring as the first stmt in the body;
#                         this might affected the first line number #32911)
#     Python 3.8a1  3400 (move frame block handling to compiler #17611)
#     Python 3.8a1  3401 (add END_ASYNC_FOR #33041)
#     Python 3.8a1  3410 (PEP570 Python Positional-Only Parameters #36540)
#     Python 3.8b2  3411 (Reverse evaluation order of key: value in dict
#                         comprehensions #35224)
#     Python 3.8b2  3412 (Swap the position of positional args and positional
#                         only args in ast.arguments #37593)
#     Python 3.8b4  3413 (Fix "break" and "continue" in "finally" #37830)

enum PycMagic {
    MAGIC_1_0 = 0x00999902,
    MAGIC_1_1 = 0x00999903, /* Also covers 1.2 */
    MAGIC_1_3 = 0x0A0D2E89,
    MAGIC_1_4 = 0x0A0D1704,
    MAGIC_1_5 = 0x0A0D4E99,
    MAGIC_1_6 = 0x0A0DC4FC,

    MAGIC_2_0 = 0x0A0DC687,
    MAGIC_2_1 = 0x0A0DEB2A,
    MAGIC_2_2 = 0x0A0DED2D,
    MAGIC_2_3 = 0x0A0DF23B,
    MAGIC_2_4 = 0x0A0DF26D,
    MAGIC_2_5 = 0x0A0DF2B3,
    MAGIC_2_6 = 0x0A0DF2D1,
    MAGIC_2_7 = 0x0A0DF303,

    MAGIC_3_0 = 0x0A0D0C3A,
    MAGIC_3_1 = 0x0A0D0C4E,
    MAGIC_3_2 = 0x0A0D0C6C,
    MAGIC_3_3 = 0x0A0D0C9E,
    MAGIC_3_4 = 0x0A0D0CEE,
    MAGIC_3_5 = 0x0A0D0D16,
    MAGIC_3_5_3 = 0x0A0D0D17,
    MAGIC_3_6 = 0x0A0D0D33,
    MAGIC_3_7 = 0x0A0D0D42,
    MAGIC_3_8 = 0x0A0D0D55,
    MAGIC_3_9 = 0x0A0D0D61,
}

大小端序

  • 大端序: 高位存放在低地址,低位存放在高地址。数据字节位随着内存地址的增长而减小。正常的存储模式,便于数据类型的符号判断,因为最低地址位数据即为符号位,可以直接判断数据的正负号。
  • 小端序: 高位存放在高地址,低位存放在低地址。数据字节位随着内存地址的增长而增长。将数字逆序存储。强制转换数据不需要调整字节内容。做数值四则运算时从低位每次取出相应字节运算,最后直到高位,并且最终把符号位刷新,这样的运算方式会更高效。
  • 字符串数字等在x86内存中是反向存放的,如果用地址来取的话要反向,如果用数组下标来取的话才是正向。

可执行文件

PE文件

种类 主扩展名
可执行系列 EXE、XER
库系列 DLL、OCX、CPL、DRV
驱动程序系列 SYS、VXD
对象文件系列 OBJ

PE文件的全称是Portable Executable,意为可移植的可执行的文件
PE文件是指32位可执行文件,也称为PE32。64位的可执行文件称为PE+PE32+,是PE(PE32) 的一种扩展形式(请注意不是PE64)。
PE
PE官方文档

ELF文件

可执行与可链接格式,一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件,是UNIX系统实验室作为应用程序二进制接口(Application BinaryInterface,ABI)而开发和发布的,也是Linux的主要可执行文件格式。1999年,被86open项目选为x86架构上的类Unix操作系统的二进制文件标准格式,用来取代COFF。因其可扩展性与灵活性,也可应用在其它处理器、计算机系统架构的操作系统上。
ELF

ELF节的分类

  • .text节 是保存了程序代码指令的代码节。一段可执行程序,如果存在Phdr,则.text节就会存在于text段中。由于.text节保存了程序代码,所以节类型为SHT_PROGBITS
  • .rodata节 保存了只读的数据,如一行C语言代码中的字符串。由于.rodata节是只读的,所以只能存在于一个可执行文件的只读段中。因此,只能在text段(不是data段)中找到.rodata节。由于.rodata节是只读的,所以节类型为SHT_PROGBITS.
  • .plt节(过程链接表) 也称为过程链接表(Procedure Linkage Table),其包含了动态链接器调用从共享库导入的函数所必需的相关代码。由于.plt节保存了代码,所以节类型为SHT_PROGBITS
  • .data节 存在于data段中,其保存了初始化的全局变量等数据。由于.data节保存了程序的变量数据
    ,所以节类型为SHT_PROGBITS
  • ELF官方文档点击查看更多

一些常见汇编指令

  • cmp a,b 比较a与b
  • mov a,b 把b的值送给a
  • call与ret(调用一个子程序/返回主程序)
  • Inc和dec 自增/自减
  • and和xor
  • lea 用于将源操作数的实际地址加载到目的操作数中
  • Int 向处理器抛出一个系统终端信号,常用的中断信号是0x80,它用于向内核发送系统调用。
  • MOV AX,2000H; 将16位数据2000H传送到AX寄存器
  • MOV AL,20H; 将8位数据20H传送到AL寄存器
  • MOV AX,BX; 将BX寄存器的16位数据传送到AX寄存器
  • MOV AL,[2000H]; 将2000H单元的内容传送到AL寄存器
  • je或jz若相等则跳(机器码74或0F84)
  • jne或jnz若不相等则跳(机器码75或0F85)
  • jmp无条件跳(机器码EB)
  • jz相同则跳同je
  • jb若小于则跳
  • ja若大于则跳
  • jg若大于则跳
  • jge若大于等于则跳
  • jI若小于则跳
  • jIe若小于等于则跳
  • MOV EAX, 222; eax = 222
  • MOV EBX, 100; ebx = 100
  • ADD EBX EAX; ebx + = eax;
  • SUB EBX EAX; ebx - = eax;
  • push/pop 入栈和出栈
  • SHR(右移)指令使目的操作数逻辑右移一位,最高位用 0 填充。最低位复制到进位标志位,而进位标志位中原来的数值被丢弃;位元除法,数值进行右移(向 LSB 移动)即执行了位元除法(Bitwise Division)。将一个无符号数右移 n 位,即将该数除以 2n
  • SHL(左移)指令使目的操作数逻辑左移一位,最低位用 0 填充。最高位移入进位标志位,而进位标志位中原来的数值被丢弃:位元乘法,数值进行左移(向 MSB 移动)即执行了位元乘法(Bitwise Multiplication)。任何操作数左移 n 位,即将该数乘以 2n

WP

Helloworld

题目文件下载下来发现Apk文件
那就是Android逆向
学习一番,采用APKIDE
使用前需要Java环境搭建与配置
APKIDE打开题目的APK
直接搜索flag
搜索到在MainActivity.smali里面
img
flag{7631a988259a00816deda84afb29430a}

Java逆向解密

Java逆向,百度一下采用Jadx-gui
打开后得到如下代码

package defpackage;

import java.util.ArrayList;
import java.util.Scanner;

/* renamed from: Reverse  reason: default package */
/* loaded from: Reverse.class */
public class Reverse {
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        System.out.println("Please input the flag :");
        String str = s.next();
        System.out.println("Your input is :");
        System.out.println(str);
        char[] stringArr = str.toCharArray();
        Encrypt(stringArr);
    }

    public static void Encrypt(char[] arr) {
        ArrayList<Integer> Resultlist = new ArrayList<>();
        for (char c : arr) {
            int result = (c + '@') ^ 32;
            Resultlist.add(Integer.valueOf(result));
        }
        int[] KEY = {180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129, 135, 191, 65};
        ArrayList<Integer> KEYList = new ArrayList<>();
        for (int i : KEY) {
            KEYList.add(Integer.valueOf(i));
        }
        System.out.println("Result:");
        if (Resultlist.equals(KEYList)) {
            System.out.println("Congratulations!");
        } else {
            System.err.println("Error!");
        }
    }
}

核心代码就是int result = (c + '@') ^ 32;
对所输入字符串字符+64 ^ 32后若和KEY中的值相同即为正确的flag
脚本如下

点击查看
key = [180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129, 135, 191, 65]
flag =""
for i in range(len(key)):
  flag+=chr((key[i]^32)-64)
print(flag)

得到flagThis_is_the_flag_!

reverse3

查一下,无壳32位
img
IDA32打开
直接看main_0函数

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  size_t v3; // eax
  const char *v4; // eax
  size_t v5; // eax
  char v7; // [esp+0h] [ebp-188h]
  char v8; // [esp+0h] [ebp-188h]
  signed int j; // [esp+DCh] [ebp-ACh]
  int i; // [esp+E8h] [ebp-A0h]
  signed int v11; // [esp+E8h] [ebp-A0h]
  char Destination[108]; // [esp+F4h] [ebp-94h] BYREF
  char Str[28]; // [esp+160h] [ebp-28h] BYREF
  char v14[8]; // [esp+17Ch] [ebp-Ch] BYREF

  for ( i = 0; i < 100; ++i )
  {
    if ( (unsigned int)i >= 0x64 )
      j____report_rangecheckfailure();
    Destination[i] = 0;
  }
  sub_41132F("please enter the flag:", v7);
  sub_411375("%20s", (char)Str);
  v3 = j_strlen(Str);
  v4 = (const char *)sub_4110BE(Str, v3, v14);
  strncpy(Destination, v4, 0x28u);
  v11 = j_strlen(Destination);
  for ( j = 0; j < v11; ++j )
    Destination[j] += j;
  v5 = j_strlen(Destination);
  if ( !strncmp(Destination, Str2, v5) )
    sub_41132F("rigth flag!\n", v8);
  else
    sub_41132F("wrong flag!\n", v8);
  return 0;
}

分析可知逻辑如下

  • 先读入一串字符串str
  • 然后经过sub_4110BE这个函数加密后赋值给v4
  • v4复制给Destination,然后Destination再经过for循环的简单加密
  • 最后判断DestinationStr2是否相等

Str2即为flag经过变换后的字符串点进去可以得到Str2e3nifIH9b_C@n@dH
现在关键就是分析sub_4110BE
所以我们点进去看看

点开查看
void *__cdecl sub_411AB0(char *a1, unsigned int a2, int *a3)
{
  int v4; // [esp+D4h] [ebp-38h]
  int v5; // [esp+D4h] [ebp-38h]
  int v6; // [esp+D4h] [ebp-38h]
  int v7; // [esp+D4h] [ebp-38h]
  int i; // [esp+E0h] [ebp-2Ch]
  unsigned int v9; // [esp+ECh] [ebp-20h]
  int v10; // [esp+ECh] [ebp-20h]
  int v11; // [esp+ECh] [ebp-20h]
  void *v12; // [esp+F8h] [ebp-14h]
  char *v13; // [esp+104h] [ebp-8h]

  if ( !a1 || !a2 )
    return 0;
  v9 = a2 / 3;
  if ( (int)(a2 / 3) % 3 )
    ++v9;
  v10 = 4 * v9;
  *a3 = v10;
  v12 = malloc(v10 + 1);
  if ( !v12 )
    return 0;
  j_memset(v12, 0, v10 + 1);
  v13 = a1;
  v11 = a2;
  v4 = 0;
  while ( v11 > 0 )
  {
    byte_41A144[2] = 0;
    byte_41A144[1] = 0;
    byte_41A144[0] = 0;
    for ( i = 0; i < 3 && v11 >= 1; ++i )
    {
      byte_41A144[i] = *v13;
      --v11;
      ++v13;
    }
    if ( !i )
      break;
    switch ( i )
    {
      case 1:
        *((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
        v5 = v4 + 1;
        *((_BYTE *)v12 + v5) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
        *((_BYTE *)v12 + ++v5) = aAbcdefghijklmn[64];
        *((_BYTE *)v12 + ++v5) = aAbcdefghijklmn[64];
        v4 = v5 + 1;
        break;
      case 2:
        *((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
        v6 = v4 + 1;
        *((_BYTE *)v12 + v6) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
        *((_BYTE *)v12 + ++v6) = aAbcdefghijklmn[((byte_41A144[2] & 0xC0) >> 6) | (4 * (byte_41A144[1] & 0xF))];
        *((_BYTE *)v12 + ++v6) = aAbcdefghijklmn[64];
        v4 = v6 + 1;
        break;
      case 3:
        *((_BYTE *)v12 + v4) = aAbcdefghijklmn[(int)(unsigned __int8)byte_41A144[0] >> 2];
        v7 = v4 + 1;
        *((_BYTE *)v12 + v7) = aAbcdefghijklmn[((byte_41A144[1] & 0xF0) >> 4) | (16 * (byte_41A144[0] & 3))];
        *((_BYTE *)v12 + ++v7) = aAbcdefghijklmn[((byte_41A144[2] & 0xC0) >> 6) | (4 * (byte_41A144[1] & 0xF))];
        *((_BYTE *)v12 + ++v7) = aAbcdefghijklmn[byte_41A144[2] & 0x3F];
        v4 = v7 + 1;
        break;
    }
  }
  *((_BYTE *)v12 + v4) = 0;
  return v12;
}

难懂没关系,我们简单分析一下
我们点开aAbcdefghijklmn这个字符串看看

.rdata:00417B30 aAbcdefghijklmn db 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
.rdata:00417B30                                         ; DATA XREF: .text:004117E8↑o
.rdata:00417B30                                         ; .text:00411827↑o ...
.rdata:00417B30                 db 0

看到这个字符串应该敏锐地想到Base64加密
同时我们的猜测可以通过这个函数前面的/3 *4和字符串界面得到验证
img
所以可以确定就是Base64加密
当然更严谨可以通过动态调试验证。
利用python或者在线解密网站解密就可以得出flag{i_l0ve_you}

Python代码
from base64 import *
str2="e3nifIH9b_C@n@dH"
flag=""
for i in range(len(str2)):
    flag+=chr(ord(str2[i])-i)
print(b64decode(flag))

game

尝试打开
img
大意是说有八个灯,初始全为关闭,须将其全部开启才能得到flag
输入n(1-8)会改变序号为(n-1),n,(n+1)的灯的状态
试了一下,依次输入12345678就能得到flag
img
flagzsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}
好了这题解完了

法一 静态分析

32位无壳 IDA32 打开,搜索main函数

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  int i; // [esp+DCh] [ebp-20h]
  int v5; // [esp+F4h] [ebp-8h] BYREF

  sub_45A7BE(&unk_50B110);
  sub_45A7BE(&unk_50B158);
  sub_45A7BE(&unk_50B1A0);
  sub_45A7BE(&unk_50B1E8);
  sub_45A7BE(&unk_50B230);
  sub_45A7BE(&unk_50B278);
  sub_45A7BE(&unk_50B2C0);
  sub_45A7BE(&unk_50B308);
  sub_45A7BE("二                                                     |\n");
  sub_45A7BE("|              by 0x61                                 |\n");
  sub_45A7BE("|                                                      |\n");
  sub_45A7BE("|------------------------------------------------------|\n");
  sub_45A7BE(
    "Play a game\n"
    "The n is the serial number of the lamp,and m is the state of the lamp\n"
    "If m of the Nth lamp is 1,it's on ,if not it's off\n"
    "At first all the lights were closed\n");
  sub_45A7BE("Now you can input n to change its state\n");
  sub_45A7BE(
    "But you should pay attention to one thing,if you change the state of the Nth lamp,the state of (N-1)th and (N+1)th w"
    "ill be changed too\n");
  sub_45A7BE("When all lamps are on,flag will appear\n");
  sub_45A7BE("Now,input n \n");
  while ( 1 )
  {
    while ( 1 )
    {
      sub_45A7BE("input n,n(1-8)\n");
      sub_459418();
      sub_45A7BE("n=");
      sub_4596D4("%d", &v5);
      sub_45A7BE("\n");
      if ( v5 >= 0 && v5 <= 8 )
        break;
      sub_45A7BE("sorry,n error,try again\n");
    }
    if ( v5 )
    {
      sub_4576D6(v5 - 1);
    }
    else
    {
      for ( i = 0; i < 8; ++i )
      {
        if ( (unsigned int)i >= 9 )
          j____report_rangecheckfailure();
        byte_532E28[i] = 0;
      }
    }
    j__system("CLS");
    sub_458054();
    if ( byte_532E28[0] == 1
      && byte_532E28[1] == 1
      && byte_532E28[2] == 1
      && byte_532E28[3] == 1
      && byte_532E28[4] == 1
      && byte_532E28[5] == 1
      && byte_532E28[6] == 1
      && byte_532E28[7] == 1 )
    {
      sub_457AB4();
    }
  }
}

根据题目意思,很容易定位到最后一个if语句即为判断八个灯是否全开,
从之前调用了'CLS'指令清屏也能大致猜测出来。
那么flag应该就在sub_457AB4这个函数里面,跟进一下

int sub_45E940()
{
  int i; // [esp+D0h] [ebp-94h]
  char v2[22]; // [esp+DCh] [ebp-88h]
  char v3[32]; // [esp+F2h] [ebp-72h] BYREF
  char v4[4]; // [esp+112h] [ebp-52h] BYREF
  char v5[64]; // [esp+120h] [ebp-44h]

  sub_45A7BE("done!!! the flag is ");
  v5[0] = 18;
  v5[1] = 64;
  v5[2] = 98;
  v5[3] = 5;
  v5[4] = 2;
  v5[5] = 4;
  v5[6] = 6;
  v5[7] = 3;
  v5[8] = 6;
  v5[9] = 48;
  v5[10] = 49;
  v5[11] = 65;
  v5[12] = 32;
  v5[13] = 12;
  v5[14] = 48;
  v5[15] = 65;
  v5[16] = 31;
  v5[17] = 78;
  v5[18] = 62;
  v5[19] = 32;
  v5[20] = 49;
  v5[21] = 32;
  v5[22] = 1;
  v5[23] = 57;
  v5[24] = 96;
  v5[25] = 3;
  v5[26] = 21;
  v5[27] = 9;
  v5[28] = 4;
  v5[29] = 62;
  v5[30] = 3;
  v5[31] = 5;
  v5[32] = 4;
  v5[33] = 1;
  v5[34] = 2;
  v5[35] = 3;
  v5[36] = 44;
  v5[37] = 65;
  v5[38] = 78;
  v5[39] = 32;
  v5[40] = 16;
  v5[41] = 97;
  v5[42] = 54;
  v5[43] = 16;
  v5[44] = 44;
  v5[45] = 52;
  v5[46] = 32;
  v5[47] = 64;
  v5[48] = 89;
  v5[49] = 45;
  v5[50] = 32;
  v5[51] = 65;
  v5[52] = 15;
  v5[53] = 34;
  v5[54] = 18;
  v5[55] = 16;
  v5[56] = 0;
  v2[0] = 123;
  v2[1] = 32;
  v2[2] = 18;
  v2[3] = 98;
  v2[4] = 119;
  v2[5] = 108;
  v2[6] = 65;
  v2[7] = 41;
  v2[8] = 124;
  v2[9] = 80;
  v2[10] = 125;
  v2[11] = 38;
  v2[12] = 124;
  v2[13] = 111;
  v2[14] = 74;
  v2[15] = 49;
  v2[16] = 83;
  v2[17] = 108;
  v2[18] = 94;
  v2[19] = 108;
  v2[20] = 84;
  v2[21] = 6;
  qmemcpy(v3, "`S,yhn _uec{", 12);
  v3[12] = 127;
  v3[13] = 119;
  v3[14] = 96;
  v3[15] = 48;
  v3[16] = 107;
  v3[17] = 71;
  v3[18] = 92;
  v3[19] = 29;
  v3[20] = 81;
  v3[21] = 107;
  v3[22] = 90;
  v3[23] = 85;
  v3[24] = 64;
  v3[25] = 12;
  v3[26] = 43;
  v3[27] = 76;
  v3[28] = 86;
  v3[29] = 13;
  v3[30] = 114;
  v3[31] = 1;
  strcpy(v4, "u~");
  for ( i = 0; i < 56; ++i )
  {
    v2[i] ^= v5[i];
    v2[i] ^= 0x13u;
  }
  return sub_45A7BE("%s\n");
}

简单的v2和v5异或,再异或0x13
脚本如下

v5=[18,64,98,5,2,4,6,3,6,48,49,65,32,12,48,65,31,78,62,32,49,32,1,57,96,3,21,9,4,62,3,5,4,1,2,3,44,65,78,32,16,97,54,16,44,52,32,64,89,45,32,65,15,34,18,16,0]
v2=[123,32,18,98,119,108,65,41,124,80,125,38,124,111,74,49,83,108,94,108,84,6,96,83,44,121,104,110,32,95,117,101,99,123,127,119,96,48,107,71,92,29,81,107,90,85,64,12,43,76,86,13,114,1,117,126,0]
flag=""
for i in range(56):
    v2[i]^=v5[i]
    v2[i]^=0x13
    flag+=chr(v2[i])
print(flag)

运行结果zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}

法二 广度优先搜索BFS

题目可以转化成相当于一个初始全为0的长度为8的数组,每次操作选一个位置,将其与其相邻共3个数取反(首尾相邻,可看做一个环),一直操作到全部为1。求操作方案。可以采用BFS算法。求出操作方法,然后在程序中依次输入即可。

#include<bits/stdc++.h>
using namespace std;
struct node{
    string s;
    string step;
    node(string s1,string step1){
        s=s1; step=step1;
    }
};
string s="00000000";
map<string,int> f;
queue<node>a;
int main(){
    a.push(node(s,""));
    while(!a.empty()){
        node now=a.front(); a.pop();
        if(f[now.s]==1)continue;
        f[now.s]=1;
        if(now.s=="11111111"){
            cout<<now.step<<endl;
            break;
        }
        for(int i=0; i<8; i++){
            string lamp=now.s;
            if(i!=0) lamp[i-1]=lamp[i-1]=='1'?'0':'1';
            else lamp[7]=lamp[7]=='1'?'0':'1';
            if(i!=7) lamp[i+1]=lamp[i+1]=='1'?'0':'1';
            else lamp[0]=lamp[0]=='1'?'0':'1';
            lamp[i]=lamp[i]=='1'?'0':'1';
            a.push(node(lamp,now.step+(char)(i+49)));
        }
    }
    return 0;
}

最后求出结果是12345678

法三 动态调试

看了别人的wp才知道也可以动调解决,学了一下。
用X32dbg打开
img
右键-搜索-所有用户模块-字符串
img
定位到关键指令'CLS'(因为之前分析过'CLS'后有个关键判断),双击点进去
img
接下来由八个jne指令
这里讲一下jne,je指令

  • je或jz若相等则跳(机器码74或0F84)
  • jne或jnz若不相等则跳(机器码75或0F85)
    则这八个jne指令就是判断灯是否打开
    我们只需修改这里的指令即可
    img
    将jne指令改成jz或直接nop掉
    选中后空格修改两种选一种修改就行
    img
    img
    之后直接F9运行,随便输入数字,就会出现flag
    img

参考文献

posted @ 2023-02-28 23:41  Tree_24  阅读(53)  评论(0编辑  收藏  举报