Feb_Note&WP
RE做题过程零散笔记
工具使用相关
IDA
- shift+E:提取数据
X64dbg
markdown语法
- 折叠代码块:
summary后写折叠后显示的标题,同时注意代码前后各空一行
Android逆向
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官方文档
ELF文件
可执行与可链接格式,一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件,是UNIX系统实验室作为应用程序二进制接口(Application BinaryInterface,ABI)而开发和发布的,也是Linux的主要可执行文件格式。1999年,被86open项目选为x86架构上的类Unix操作系统的二进制文件标准格式,用来取代COFF。因其可扩展性与灵活性,也可应用在其它处理器、计算机系统架构的操作系统上。
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里面
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位
用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循环的简单加密
- 最后判断Destination和Str2是否相等
Str2即为flag经过变换后的字符串点进去可以得到Str2为e3nifIH9b_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和字符串界面得到验证
所以可以确定就是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
尝试打开
大意是说有八个灯,初始全为关闭,须将其全部开启才能得到flag
输入n(1-8)会改变序号为(n-1),n,(n+1)的灯的状态
试了一下,依次输入12345678就能得到flag
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打开
右键-搜索-所有用户模块-字符串
定位到关键指令'CLS'(因为之前分析过'CLS'后有个关键判断),双击点进去
接下来由八个jne指令
这里讲一下jne,je指令
- je或jz若相等则跳(机器码74或0F84)
- jne或jnz若不相等则跳(机器码75或0F85)
则这八个jne指令就是判断灯是否打开
我们只需修改这里的指令即可
将jne指令改成jz或直接nop掉
选中后空格修改两种选一种修改就行
之后直接F9运行,随便输入数字,就会出现flag
参考文献
本文来自博客园,作者:{Tree_24},转载请注明原文链接:{https://www.cnblogs.com/Tree-24/}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南