Reverse & Pwn 常用工具和思路
IDA Pro
IDA Pro(Interactive Disassembler Professional)交互式反汇编器专业版,CTF比赛中Reverse & Pwn必备。
打开一个可执行文件前,应先用file命令或者DIE等工具,确定是32位还是64位,然后用相应的IDA工具打开可执行文件。
IDA界面默认有菜单栏、工具栏、导航带、函数窗口、IDA-View反汇编窗口、Hex-View十六进制显示、Import、Export窗口等部分。点击菜单Windows-Reset/Save/Load Desktop来重置、保存、加载IDA窗口布局,或者菜单View打开被关闭的窗口。
IDA-View反汇编视图
有文本视图(推荐)和图形视图两种,按空格键切换。文本视图有跳转逻辑关系。
中文字符串不显示
如果反汇编的shift+F12字符串不显示中文,那么Options-General-Strings,修改 Default 8bit 的编码选项改为 gb2312 即可。
IDA快捷键汇总
F5反汇编。
TAB汇编代码和伪代码之间切换。
空格键,在IDA-View反汇编视图,文本视图、图形视图切换。
Alt+T,搜索,就是search的搜索。
shift+F12,打开字符串视图。
ctrl+X,跳转到字符串被程序使用的位置。
X查看函数在哪被调用。
G在IDA-View的文本视图里跳转到指定地址位置。
ctrl+W,保存ida数据库,就是保存当前工作进度。
N变量、函数重命名。
Y变量、函数重新定义类型。
shift+F1,定义局部变量。结合Y键重新定义变量、函数的类型,在反汇编,辅助逆向分析。
Alt+Q,以结构体形式分析数据。
shift+E,按照不同要求显示字符串,可以避免大端序、小端序的困扰。
R和H,R将数字显示为字符,H将字符串显示为数字(可在十进制和十六进制切换)。
A将数据转换为字符串。
ACDUP对数据操作
- A以字符串形式显示数据。
- D以1、2、4、8个字节显示数据。x86为例db、dw、dd、dq(byte、word、double word、quadra word)。mips为例byte、halfword、word分别是1、2、4字节。
- U取消函数、代码、数据定义。
- P定义一个函数头,打patch很有用。
- C以代码显示。
“/”对C伪代码注释。
“;”分号汇编注释(伴随),所有call、jump到这里的位置都会显示注释。
“:”冒号汇编注释,普通的湖边注释。
未完待续……
gdb
布局(layout)
- 设置分屏布局: tui layout split
- 设置焦点为源代码窗口: tui layout focus src
- 设置当前焦点窗口为汇编窗口:tui layout asm
断点(breakpoint)
- 给函数下断点: b main
- 给地址下断点: b *0x0800401A
汇编指令(assemble instruction)
- 设置反汇编模式,这里设置为 Intel 格式: set disassembly-flavor intel
- 设置打印出下一条要执行的汇编代码: set disassemble-next-line on
- 单条汇编指令执行: si 或 ni
查看数据
查看0xffffcff0开始的,32×4字节的内存内容: x /32x 0xffffcff0
还可以以十进制d、指令i等形式查看,具体看 help x。
OllyDBG(OD)
OD是一种动态汇编调试工具。这里只讲一般用法,见下表。
搜索ASCII
拖入程序后,找目标字符串。在汇编指令的空白处右键-中文搜索引擎-搜索ASCII。搜索ASCII找到目标字符串,双击跳转到有该字符串的汇编指令地址。
编辑立即数
在有该字符串的汇编指令地址上右键-数据窗口中跟随-立即数,跳转到数据窗口中。数据窗口中选中要修改的字符串,按空格弹出窗口编辑数据。
重置调试
程序运行结束,状态为“已终止”,可以点击重置键,重新开始调试。
断点
按F2或者在汇编指令的地址处双击,可以下断点,断点处地址为红色背景色。
pyinstxtractor+uncompyle6
截止2023.06.22最新版本的uncompyle6支持python 3.11.x版本,uncompyle6 和 xdis都推荐编译安装,安装后即可使用。
git clone [email protected]:rocky/python-uncompyle6.git
cd python-uncompyle6
sudo python setup.py install
git clone [email protected]:rocky/python-xdis.git
cd python-xdis
sudo python setup.py install
jadx
jadx是java、安卓apk的反编译工具。
Reverse部分
easyre
64位exe,IDA64直接F5反汇编,看到逻辑是输入相等的两个整数打印flag,得到flag{this_Is_a_EaSyRe}
reverse1
64位exe,IDA64直接F5反汇编,未找到main,找到start,一层层进入直到sub_1400118C0()函数,发现字符串Str2内容是{hello_world},但是字母o都被改成数字0。输入字符串Str1与修改后的Str2比较。答案是flag{hell0_w0rld}
reverse2
64位elf,IDA64直接F5反汇编,直奔main函数,发现字符串flag为{hacking_for_fun},但是经过替换,字母i或r都替换为数字1,输入字符串s2与修改后的flag比较。答案是flag{hack1ng_fo1_fun}
内涵的软件
图片有内涵,exe也可以有内涵,也许你等不到答案,赶快行动起来吧!!! 注意:得到的 flag 请包上 flag{} 提交。32位exe,IDA直接F5反汇编,直奔main函数,逻辑上没有意义,直接提交flag{49d3c93df25caad81232130f3d2ebfad}
新年快乐
32位exe,upx加过壳。upx去壳后直接F5反汇编,直奔main函数,逻辑比较简单,答案是flag{HappyNewYear!}
xor
64位MacOS程序,IDA64直接F5反汇编,直奔main函数。flag字符串从第[1]位起逐位与前一位异或,结果与global比较。找到global对应的值,反向操作,写个简单脚本得到答案flag{QianQiuWanDai_YiTongJiangHu}
c = bytes.fromhex(
'660A6B0C77264F2E4011780D5A3B55117019461F76224D23440E6706680F47324F00')
print(c)
m = bytearray(len(c))
for i in range(len(c) - 1, 0, -1):
m[i] = c[i] ^ c[i - 1]
m[0]=c[0]
print(m)
helloword
安卓apk程序。使用jadx反编译,在MainActivity里面找到flag。答案是flag{7631a988259a00816deda84afb29430a}
reverse3
32位exe,IDA64直接F5反汇编,直奔main函数。输入的Str被base64编码,base64编码逐位加上index,结果应该和Str2内容一致。那么写个简单脚本就可以反向操作得到答案 flag{i_l0ve_you}
import base64
Str2 = "e3nifIH9b_C@n@dH"
Destination = ""
for i, c in enumerate(Str2):
Destination += chr(ord(Str2[i]) - i)
print(f'Destination=%s' % Destination)
flag = base64.b64decode(Destination.encode('utf-8')).decode('utf-8')
print(flag)
不一样的flag
32位exe,IDA直接F5反汇编,直奔main函数。发现是一个5×5的二维迷宫,起点是字符*,终点是字符#,通路是字符0,墙壁是字符1。答案flag{222441144222}。
用变量idx_column记录左右动作(按N重命名idx_column)。
用变量maze2d[25]记录上下动作,因为迷宫字符串是25个字符,maze2d[25]的值是'\0',初始值就是0。
最后判断用了idx_row(按N重命名idx_row),注意变量定义的顺序,char maze2d[29]; int idx_column; int step; int i; char idx_row[12]; 因此maze2d地址和idx_row地址差29+3*4=41,这就是为什么会有-41的原因。
SimpleRev
64位elf,IDA64直接F5反汇编,直奔main函数。虽然输入的是数字,但是按下R就可以看到相应字符串(大概),这里注意小端序的问题,用shift+E查看字符串内容。
key3和v9被join函数拼接成了text字符串'killshadow'。
key1和src拼接成了key字符串'ADSFKNDCLS',随后被涮换为小写的'adsfkndcls'。
写个简单的脚本爆破一下对应的输入即可,个人觉得答案不唯一,按下标位置自由组合即可。
text = 'killshadow'
key = 'adsfkndcls'
alpha=[]
alpha += [chr(i) for i in range(ord('A'), ord('A') + 26)]
alpha += [chr(i) for i in range(ord('a'), ord('a') + 26)]
for i, ct in enumerate(key):
print(i, end=':')
for cur_ch in alpha:
if text[i] == chr((ord(cur_ch) - 39 - ord(key[i]) + 97) % 26 + 97):
print(cur_ch, end=',')
print()
"""
0:K,e,
1:L,f,
2:D,x,
3:Q,k,
4:C,w,
5:U,o,
6:D,x,
7:F,z,
8:Z,t,
9:O,i,
"""
Java逆向解密
Reverse.class用jadx反编译即可得到源码。源码逻辑简单,对输入的字符串,逐个字符转换为int型,加上'@'符号,再和32抑或,再与KEY数组比较相等即可。写个简单脚本得到答案flag{This_is_the_flag_!}。
nums = [180, 136, 137, 147, 191, 137, 147, 191,
148, 136, 133, 191, 134, 140, 129, 135, 191, 65]
flag = ''
for i in nums:
flag += chr((i ^ 32) - ord('@'))
print(flag)
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!");
}
}
}
[GXYCTF2019]luck_guy
64位的elf,IDA64直接F5反汇编,直奔main函数。get_flag函数里f1字符串内容已知。s=0x7F666F6067756369LL 要注意小端序,char[] s={0x69, 0x63, 0x75, 0x67, 0x60, 0x6F, 0x66, 0x7F}。编写简单脚本得到答案flag{do_not_hate_me}。
f1 = 'GXY{do_not_'
f2 = ''
s = [0x69, 0x63, 0x75, 0x67, 0x60, 0x6F, 0x66, 0x7F]
for i, _ in enumerate(s):
if i % 2 == 1:
s[i] -= 2
else:
s[i] -= 1
for i in s:
f2 += chr(i)
flag = f1 + f2
print(flag)
[BJDCTF2020]JustRE
32位的windows程序,找start、main、WinMain函数意义不大,要看对话框的DialogFunc函数。IDA打开,shift+F12找字符串,发现有个BJD{%d%d2069a45792d233ac},ctrl+X交叉引用跳转到该函数地址,F5反汇编。发现是需要点击19999次才能得到flag(出题人真贱),直接把%d替换得到答案flag{1999902069a45792d233ac}
刮开有奖【子函数做了什么并未完全看懂,有猜测成分】
32位windows程序,点开后就……
F5反汇编,看DialogFunc函数。定义了一堆int变量实际是一个11长度的整形数组。数组被process_int_arr函数处理过。这个函数很复杂,我直接copy修改成.c文件,自己编译执行,搞出处理后的数组值,下面是代码。
#include "stdlib.h"
#include "string.h"
#include "stdio.h"
int process_int_arr(int *int_arr, int index_0, int size_10)
{
int result; // eax
int i; // esi
int cur_index; // ecx
int cur_int; // edx
result = size_10;
for ( i = index_0; i <= size_10; index_0 = i )
{
cur_index = i;
cur_int = int_arr[i];
if ( index_0 < result && i < result )
{
do
{
if ( cur_int > int_arr[result] )
{
if ( i >= result )
break;
++i;
int_arr[cur_index] = int_arr[result];
if ( i >= result )
break;
while ( int_arr[i] <= cur_int )
{
if ( ++i >= result )
goto LABEL_13;
}
if ( i >= result )
break;
cur_index = i;
int_arr[result] = int_arr[i];
}
--result;
}
while ( i < result );
}
LABEL_13:
int_arr[result] = cur_int;
process_int_arr(int_arr, index_0, i - 1);
result = size_10;
++i;
}
return result;
}
int main(int argc, char const *argv[])
{
char *v4; // esi
char *v5; // edi
int int_arr[2]; // [esp+8h] [ebp-20030h] BYREF
int int_arr_2; // [esp+10h] [ebp-20028h]
int int_arr_3; // [esp+14h] [ebp-20024h]
int int_arr_4; // [esp+18h] [ebp-20020h]
int int_arr_5; // [esp+1Ch] [ebp-2001Ch]
int int_arr_6; // [esp+20h] [ebp-20018h]
int int_arr_7; // [esp+24h] [ebp-20014h]
int int_arr_8; // [esp+28h] [ebp-20010h]
int int_arr_9; // [esp+2Ch] [ebp-2000Ch]
int int_arr_10; // [esp+30h] [ebp-20008h]
char String[65536]; // [esp+34h] [ebp-20004h] BYREF
char v18[65536]; // [esp+10034h] [ebp-10004h] BYREF
memset(String, 0, 0xFFFFu);
// GetDlgItemTextA(hDlg, 1000, String, 0xFFFF);
int_arr[0] = 90;
int_arr[1] = 74;
int_arr_2 = 83;
int_arr_3 = 69;
int_arr_4 = 67;
int_arr_5 = 97;
int_arr_6 = 78;
int_arr_7 = 72;
int_arr_8 = 51;
int_arr_9 = 110;
int_arr_10 = 103;
process_int_arr(int_arr, 0, 10);
for(int i=0;i<=10;++i){
printf("int_arr[%d]: %d\n", i, int_arr[i]);
}
return 0;
}
/*
int_arr[0]: 51
int_arr[1]: 67
int_arr[2]: 69
int_arr[3]: 72
int_arr[4]: 74
int_arr[5]: 78
int_arr[6]: 83
int_arr[7]: 90
int_arr[8]: 97
int_arr[9]: 103
int_arr[10]: 110
*/
接下来,对String[2-4]和String[5-7]分别base64加密得到ak1w和V1Ax。编写简单脚本得到答案flag{UJWP1jMp}。(You Jump,I jump!)
import base64
int_arr = [51, 67, 69, 72, 74, 78, 83, 90, 97, 103, 110, ]
flag = ''
flag += chr(int_arr[0] + 34)
flag += chr(int_arr[4])
# flag += chr(int((int_arr[2] * 3 + 141) / 4)) # String[2]已经被包含在后面了
# flag += chr(int(int_arr[7] / 9) * 2 * 4) # String[3]已经被包含在后面了
flag += base64.b64decode('V1Ax'.encode('utf-8')).decode('utf-8') # WP1
flag += base64.b64decode('ak1w'.encode('utf-8')).decode('utf-8') # jMp
print('flag{' + flag + '}')
# flag{UJWP1jMp}
简单注册器
简单注册器.apk,用jadx反编译可以看到源码。flag初始值为1,但是有个flag=(xxx)? 0:0,也就是说flag一定会变成0,永远验证不通过。直接看后面的代码,用python重新实现一下,打印flag得到答案flag{59acc538825054c7de4b26440c0999dd}
x = bytearray('dd2940c04462b4dd7c450528835cca15', 'utf-8')
x[2] = (x[2] + x[3]) - 50
x[4] = (x[2] + x[5]) - 48
x[30] = (x[31] + x[9]) - 48
x[14] = (x[27] + x[28]) - 97
for i in range(16):
a = x[31 - i]
x[31 - i] = x[i]
x[i] = a
bbb = x.decode('utf-8')
print('flag{' + bbb + '}')
[GWCTF 2019]pyre
.pyc文件,用uncompyle6进行反编译,uncompyle6 -o flag.py flag.pyc得到python源代码如下。
# uncompyle6 version 3.9.1.dev0
# Python bytecode version base 2.7 (62211)
# Decompiled from: Python 3.11.4 (main, Jun 7 2023, 10:13:09) [GCC 12.2.0]
# Embedded file name: encode.py
# Compiled at: 2019-08-19 21:01:57
print 'Welcome to Re World!'
print 'Your input1 is your flag~'
l = len(input1)
for i in range(l):
num = ((input1[i] + i) % 128 + 128) % 128
code += num
for i in range(l - 1):
code[i] = code[i] ^ code[i + 1]
print code
code = ['\x1f', '\x12', '\x1d', '(', '0', '4',
'\x01', '\x06', '\x14', '4', ',', '\x1b',
'U', '?', 'o', '6', '*', ':', '\x01',
'D', ';', '%', '\x13']
根据代码内容将算法反向进行即可。注意((input1[i]+i) % 128 +128) % 128 结果就是(input1[i]+i) % 128。逐个字符爆破一下。得到答案flag{Just_Re_1s_Ha66y!}
code = bytearray(b'\x1f\x12\x1d(04\x01\x06\x144,\x1bU?o6*:\x01D;%\x13')
l = len(code)
for i in range(l - 2, -1, -1):
code[i] = code[i] ^ code[i + 1]
input1 = ''
for i in range(l):
for j in range(10): # 爆破一下
temp = (j * 128 + code[i]) - i
if 32 <= temp < 127: # ascii字符32-126是可显示、可输入字符
input1 += chr(temp)
break
print(input1)
[ACTF新生赛2020]easyre
32位exe用upx加壳,脱壳后放入IDA反汇编。函数逻辑显示永远不可能通过输入获得flag。但是给出了v4可以反推出flag是什么,写个简单脚本得到答案flag{U9X_1S_W6@T?}
table = bytearray(
'~}|{zyxwvutsrqponmlkjihgfedcba`_^]\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(\'&%$# !"', 'utf-8')
v4 = bytearray("*F'\"N,\"(I?+@", 'utf-8')
v5 = bytearray(len(v4))
for i in range(12):
v5[i] = table.index(v4[i]) + 1
print('flag{' + v5.decode('utf-8') + '}')
findit
apk包放入jadx反编译,逻辑比较简单,输入字符串经处理后匹配,输出flag。编写简单脚本得到输入LzakAkLzwXdsyZgew和答案flag{c164675262033b4c49bdf7f9cda28a75}
a = b'ThisIsTheFlagHome'
b = b'pvkq{m164675262033l4m49lnp7p9mnk28k75}'
# 输入
la = len(a)
x = bytearray(la)
for i in range(la):
if ((a[i] < ord('I') and a[i] >= ord('A')) or (a[i] < ord('i') and a[i] >= ord('a'))):
x[i] = a[i] + 18
elif ((a[i] >= ord('A') and a[i] <= ord('Z')) or (a[i] >= ord('a') and a[i] <= ord('z'))):
x[i] = a[i] - ord('\b')
else:
x[i] = a[i]
print(x.decode('utf-8'))
# flag
lb = len(b)
y = bytearray(lb)
for i2 in range(lb):
if((b[i2] >= ord('A') and b[i2] <= ord('Z')) or (b[i2] >= ord('a') and b[i2] <= ord('z'))):
y[i2] = b[i2] + 16
if ((y[i2] > ord('Z') and y[i2] < ord('a')) or y[i2] >= ord('z')):
y[i2] = y[i2] - 26
else:
y[i2] = b[i2]
print(y.decode('utf-8'))
[ACTF新生赛2020]rome
32位exe,IDA反汇编。根据You are correct!字符串找到func函数。该函数对输入字符串ACTF{****xxxx****xxxx}中16个字符做处理,看结果是否与Qsw3sj_lz4_Ujw@l匹配。注意前面各个变量的定义,用4个int存储了中间的16个字符,用index[0]-index[15]存储处理后的结果,用index[16]存'\0',用index[17]字节开始的int存储数组下标。写脚本爆破一下得到答案flag{Cae3ar_th4_Gre@t}
result = b'Qsw3sj_lz4_Ujw@l'
inputs = bytearray(16)
for i in range(16):
for j in range(32, 127): # 遍历ascii可显示字符,经过rome的处理
inputs[i] = j
if inputs[i] > 64 and inputs[i] <= 90:
inputs[i] = (inputs[i] - 51) % 26 + 65
if inputs[i] > 96 and inputs[i] <= 122:
inputs[i] = (inputs[i] - 79) % 26 + 97
if inputs[i] == result[i]:
inputs[i] = j # 如果匹配说明j是正确的值
break
print('flag{' + inputs.decode('utf-8') + '}')
rsa
附件不是程序,二是pub.key公钥文件和flag.enc密文,更像是密码学。此题毫无思路,查看writeup之后记录于此。感谢buu Reverse学习记录(21) rsa_buu recursive-CSDN博客
在kali输入命令openssl rsa -pubin -text -modulus -in warmup -in pub.key就可以得到e和n
用yafu分解n得到p和q
再写脚本得到flag{decrypt_256}
import gmpy2
import rsa
e = 65537
n = 0xC0332C5C64AE47182F6C1C876D42336910545A58F7EEFEFC0BCAAF5AF341CCDD
p = 304008741604601924494328155975272418463
q = 285960468890451637935629440372639283459
phi_n = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi_n)
key = rsa.PrivateKey(n, e, int(d), p, q)
with open('flag.enc', "rb") as f:
c = f.read()
print(rsa.decrypt(c, key))
[FlareOn4]login
login.html内容比较简单,如下。对输入进行rot13加密,密文解密得到答案flag{[email protected]}
<!DOCTYPE Html />
<html>
<head>
<title>FLARE On 2017</title>
</head>
<body>
<input type="text" name="flag" id="flag" value="Enter the flag" />
<input type="button" id="prompt" value="Click to check the flag" />
<script type="text/javascript">
document.getElementById("prompt").onclick = function () {
var flag = document.getElementById("flag").value;
var rotFlag = flag.replace(/[a-zA-Z]/g, function(c){return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);});
if ("[email protected]" == rotFlag) {
alert("Correct flag!");
} else {
alert("Incorrect flag, rot again");
}
}
</script>
</body>
</html>
[WUSTCTF2020]level1
64位elf,IDA64反汇编,逻辑比较简单,注意下标是从1开始的。写个脚本搞出答案flag{d9-dE6-20c}
nums = [198, 232, 816, 200, 1536, 300, 6144, 984, 51200, 570, 92160,
1200, 565248, 756, 1474560, 800, 6291456, 1782, 65536000, ]
flag = ''
for i, n in enumerate(nums, start=1):
if i % 2 == 1:
flag += chr(n >> i)
else:
flag += chr(n // i)
print(flag)
CrackRTF
32位exe文件,反汇编后发现需要两次输入,每次输入6个字符。第一次输入后,追加字符串1@DBApp,再做MD5哈希,哈希匹配进入第二次输入。第二次输入后,追加第一次的字符串,再做MD5哈希,哈希匹配后生成一个dbapp.rtf文件。那么去MD5免费在线解密破解_MD5在线加密-SOMD5网碰撞得到两次的输入分别是123321和~!3a@0,然后生成dbapp.rtf文件即可得到答案flag{N0_M0re_Free_Bugs}
[GUET-CTF2019]re
64位elf程序,有壳。upx去壳后再IDA反汇编。没有main函数,只好shift+F12找字符串,根据运行提示“Wrong!”找到“Correct!”,进而找到主要的逻辑。挺坑的,只好写个脚本。注意,逻辑里是没有下标6的元素的,而且下标16,17的两个验证是反顺序写的。得到缺少下标6元素的答案,然后看writeup才知道要尝试提交,试出下标6元素。也有用Z3库求解的。
我运气不好,从a试到z,没试出来,试一下数字1就行,答案flag{e165421110ba03099a1c039337}
flag = ''
flag += chr(166163712 // 1629056)
flag += chr(731332800 // 6771600)
flag += chr(357245568 // 3682944)
flag += chr(1074393000 // 10431000)
flag += chr(489211344 // 3977328)
flag += chr(518971936 // 5138336)
flag += '<?>'
flag += chr(406741500 // 7532250)
flag += chr(294236496 // 5551632)
flag += chr(177305856 // 3409728)
flag += chr(650683500 // 13013670)
flag += chr(298351053 // 6088797)
flag += chr(386348487 // 7884663)
flag += chr(438258597 // 8944053)
flag += chr(249527520 // 5198490)
flag += chr(445362764 // 4544518)
flag += chr(981182160 // 10115280)
flag += chr(174988800 // 3645600)
flag += chr(493042704 // 9667504)
flag += chr(257493600 // 5364450)
flag += chr(767478780 // 13464540)
flag += chr(312840624 // 5488432)
flag += chr(1404511500 // 14479500)
flag += chr(316139670 // 6451830)
flag += chr(619005024 // 6252576)
flag += chr(372641472 // 7763364)
flag += chr(373693320 // 7327320)
flag += chr(498266640 // 8741520)
flag += chr(452465676 // 8871876)
flag += chr(208422720 // 4086720)
flag += chr(515592000 // 9374400)
flag += chr(719890500 // 5759124)
print(flag)
# flag{e<?>65421110ba03099a1c039337}
[2019红帽杯]easyRE
64位elf,逆向题做到这里开始有意思了。直接反汇编分析逻辑比较简单。先拼接字符串,逐个字符和下标亦或得到提示信息Info:The first four chars are `flag`,似乎没有用。又发现了一个10次base64加密信息,解密后得到地址https://bbs.pediy.com/thread-254172.htm,这地址毫无营养。
m = bytearray('Iodl>Qnb(ocy\x7Fy.i\x7Fd`3w}wek9{iy=~yL@EC', 'utf-8')
print(m)
lm = len(m)
for i in range(lm):
m[i] = m[i] ^ i
print(m.decode('utf-8'))
最后看了writeup才知道,start函数除了调用上面的main_logic,还有后面的两个函数。这两个函数都是用函数指针执行多个函数,其中sub_400D35函数对字符数组做异或操作得到flag。写个脚本,结合前面提示前4个字符是flag可以得到加密秘钥,然后再得到答案flag{Act1ve_Defen5e_Test}。
c = bytes.fromhex('403520565D182245172F246E623C2754486C246E723C32455B')
key = bytearray(4)
key[0] = c[0] ^ ord('f')
key[1] = c[1] ^ ord('l')
key[2] = c[2] ^ ord('a')
key[3] = c[3] ^ ord('g')
lc = len(c)
flag = bytearray(lc)
for i in range(lc):
flag[i] = c[i] ^ key[i % len(key)]
print(flag.decode('utf-8'))
[MRCTF2020]Transform
64位exe程序,IDA64反汇编。main的逻辑比较清晰。需要注意index_index的元素是下标,是用dd定义的数组,也就是说一个元素占4个字节,LOBYTE就是取最低字节,用shift+E选择initialized C variable可以看到数组元素LOBYTE的数值(否则4字节转换太麻烦)。写个简单脚本反推得到答案flag{Tr4nsp0sltiON_Clph3r_1s_3z}
index_index = [9, 10, 15, 23, 7, 24, 12, 6, 1, 16, 3,
17, 32, 29, 11, 30, 27, 22, 4, 13, 19, 20,
21, 2, 25, 5, 31, 8, 18, 26, 28, 14, 0, ]
DestStr = [103, 121, 123, 127, 117, 43, 60, 82, 83, 121, 87,
94, 93, 66, 123, 45, 42, 102, 66, 126, 76, 87,
121, 65, 107, 126, 101, 60, 92, 69, 111, 98, 77, ]
ld = len(DestStr)
InputStr = [0 for i in range(ld)]
for i in range(ld):
DestStr[i] = DestStr[i] ^ index_index[i]
InputStr[index_index[i]] = DestStr[i]
flag = ''
for i in range(ld):
flag += chr(InputStr[i])
print(flag)
[WUSTCTF2020]level2
32位elf程序,执行输出where is it?。upx脱壳后反汇编,程序main里只有一个puts,shift+F12后直接找到字符串 wctf2020{Just_upx_-d} 从而得到答案。但是这个怎么做到的呢?
[SUCTF2019]SignIn
64位elf程序,IDA反汇编,main函数主要逻辑是对输入字符串操作得到明文m,再用RSA算法加密得到密文c,对比密文c是否正确。因此,RSA解密,再反推输入即可。
使用yafu工具对n进行分解,得到p和q。第一次执行分解大概120秒。
再进行RSA解密,解密后对字符串逆向操作得到输入 suctf{Pwn_@_hundred_years}
import gmpy2
n = 103461035900816914121390101299049044413950405173712170434161686539878160984549
# yafu - factor(n)=p*q
p = 366669102002966856876605669837014229419
q = 282164587459512124844245113950593348271
phi_n = (p - 1) * (q - 1)
c = int('ad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35', 16)
e = 65537
d = gmpy2.invert(e, phi_n)
m = pow(c, d, n)
print(m)
# Reverse of operate_input_str
buf_str = hex(m)[2:]
lb = len(buf_str)
table = '0123456789abcdef'
input_str = ''
for i in range(0, lb, 2):
index = (table.index(buf_str[i]) << 4) + table.index(buf_str[i + 1])
input_str += chr(index)
print(input_str)
[ACTF新生赛2020]usualCrypt
32位exe程序,IDA反汇编。可以看到输入字符串input_str,经过base64加密后得到zMXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9。这个base64_encode函数是定制的码表ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/。最后swap_case函数还转换了字母的大小写。使用CyberChef工具得到答案 flag{bAse64_h2s_a_Surprise}
table = bytearray(
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 'utf-8')
offset = table.index(ord('K'))
for i in range(6, 15):
table[offset + i], table[i] = table[i], table[offset + i]
print(table.decode('utf-8')) # 自定义码表
[HDCTF2019]Maze【花指令】
此题是简单的10x7迷宫,蒙都能蒙出答案flag{ssaaasaassdddw}。
upx加壳的32位exe,脱壳后反汇编。发现没有main函数,只能才能从start进入,双击main发现反汇编失败。
看了别的writeup才知道花指令。本例的特征:地址40102C的指令要跳转到loc_40102E+1也就是地址40102F。注意地址40102E的指令0E8h是call指令,但并没有真的call什么。所以把地址40102C到地址40102E的指令都nop掉。
nop没发现快捷键,点击地址40102C,菜单 -> Edit -> Patch program -> Change program bytes,把指令75h、01h、E8h都改成90h,也就是nop。选中main函数的范围(地401000-401112),按P创建函数。再F5,成功反汇编。
[MRCTF2020]Xor
32位exe,反编译 main 函数报错,地址401095分析错误。那么,我们在汇编代码视图按G,输入401095,跳转到该地址。发现另一个调用sub_401020。我们先F5反编译sub_401020,再反编译main就不会报错了。题目逻辑比较简单,写个脚本得到答案
s = bytearray('MSAWB~FXZ:J:`tQJ"N@ bpdd}8g', 'utf-8')
for i in range(27):
s[i] ^= i
print(s.decode('utf-8'))
[MRCTF2020]hello_world_go【工具IDAGolangHelper 】
DIE查看发现是64位elf,无壳,用go语言开发。找到字符串flag{hello_world_gogogo}。据说 IDA Golang Helper工具可以反编译go程序,后续试试。
Youngter-drive
32位exe,upx壳。去壳后IDA反汇编。程序先检查了是否打开ollyice.exe、ollydbg.exe、peid.exe、ida.exe、idap.exe等逆向工具,如果打开了,只能得到warning退出。
IDA先捋清楚逻辑。main函数开启了两个线程,一个处理字符串,另一个让计数器递减1。操作字符串线程,每操作一次,sleep 100,计数器从29递减到0;可以看出要求输入都是字母,对大小写字母有不同操作。另一个线程只是对计数器减1,然后sleep 100。因为sleep时长一样,两个线程交替进行,相当于只对字符串的奇数位做了操作。
可以逆向出来ThisisthreadofwindowshahaIsES。但是注意,注意check flag的时候只check了0-28,没有校验下标29。所以最后还有一个字符。输入任何字符程序都能通过,但是buuctf上只能尝试,得到一个正确答案flag{ThisisthreadofwindowshahaIsESE}
"""
TOiZiZtOrYaToUwPnToBsOaOapsyS
"""
table = 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm'
Dest = 'TOiZiZtOrYaToUwPnToBsOaOapsyS'
ld = len(Dest)
Source = ''
for i in range(ld):
if i % 2 == 1: # 只有奇数位才变换
index = table.index(Dest[i])
if ord('a') <= index + 96 <= ord('z'):
Source += chr(index + 96)
elif ord('A') <= index + 38 <= ord('Z'):
Source += chr(index + 38)
else:
Source += '<?>'
else: # 偶数位是原值
Source += Dest[i]
print(Source)
[WUSTCTF2020]level3
64位elf,直接base64解码得到乱码,但base64_encode函数是正常的。看了writeup才知道,_libc_start_main函数有个入参init,这个函数调用O_OLookAtYou函数,自定义了码表。用自定义码表解码得到 wctf2020{Base64_is_the_start_of_reverse}
相册
题目描述:你好,这是上次聚会相片,你看看(病毒,不建议安装到手机,提取完整邮箱即为flag)。
把apk拖入jadx,没发现 MainActivity。去 AndroidManifest.xml 文件里找带有android.intent.action.MAIN 的action属性的类,这是安卓运行的第一个 Activity,是 cn.baidujiayuan.ver5304.C1 类(应该是做了代码混淆?)。在C1的onCreate方法里发现发送邮件的类 cn.baidujiayuan.ver5304.MailTask。MailTask 里面调用了A2的发送邮件方法,C2提供了邮件服务器MAILSERVER。A2 的 sendMailByJavaMail 方法并没多大用。C2在static初始化时用NativeMethod 类提供的密文设置了邮箱。注意C2的静态初始化(static{}内容),有加载loadLibrary("core")。用7z打开apk,解压缩找到 libcore.so,发现是32位elf,IDA反编译,找到libcore.so 里的 m() 方法有邮箱地址,base64解码即可,答案 flag{[email protected]}
[FlareOn4]IgniteMe
32位exe程序。读取字符串,从后向前逐个异或,再对比是否正确。注意异或字符的初始值,是0x80070000循环左移4位,再右移1位,复制给char类型,也就是4。写个脚本得到答案flag{[email protected]}
DestStr = b'\x0D&IE*\x17xD+l]^E\x12/\x17+DonV\x09_EGs&\x0A\x0D\x13\x17HB\x01@M\x0C\x02i'
input_str = bytearray(len(DestStr))
ld = len(DestStr)
last = 4 #
for i in range(ld - 1, -1, -1):
input_str[i] = DestStr[i] ^ last
last = input_str[i]
print(input_str.decode('utf-8'))
[WUSTCTF2020]Cr0ssfun
64位elf,逻辑比较简单,按下标填字符串得到 wctf2020{cpp_@nd_r3verse_@re_fun}
[GWCTF 2019]xxor
64位elf,反汇编发现逻辑是清晰的,但是怎么你向出异或算法没有想出来,看了writeup才知道算法。另外,就是要注意数据类型,int和unsigned int的区别,c语言数据溢出的问题。
先看main函数,顺序走下来,对6个输入的整数做operate,再检查结果。分析check函数就可以得到dest_arr的值是dest_arr[] = { -548868226, 550153460, 3774025685, 1548802262, 2652626477, -2064448480}。
from z3 import *
a0, a1, a2, a3, a4, a5 = Ints('a0 a1 a2 a3 a4 a5')
s = Solver()
s.add(a2 - a3 == 2225223423)
s.add(a3 + a4 == 4201428739)
s.add(a2 - a4 == 1121399208)
s.add(a0 == -548868226)
s.add(a5 == -2064448480)
s.add(a1 == 550153460)
print(s.check() == 'sat')
print(s.model())
再分析operate函数。注意:t0只是用上一步t1操作出来的,t1只用上一步t0操作出来的。因此把步骤反过来,可以逆向出答案。另一点,注意数据类型的大小,因为python的int不限大小不会溢出,所以逆向用c代码做会好一些(因为题目的逻辑int类型有溢出)。src_arr[] = {6712417, 6781810, 6643561, 7561063, 7497057, 7610749}
#include "stdio.h"
int main(int argc, char const *argv[])
{
int dest_arr[] = { -548868226, 550153460, 3774025685,
1548802262, 2652626477, -2064448480};
int src_arr[6] = {0, 0, 0, 0, 0, 0};
int ch_2234[] = {2, 2, 3, 4};
// 把算法步骤反过来
for (int j = 0; j <= 4; j += 2) {
int t = 1166789954 * 64;
unsigned int t0 = dest_arr[j];
unsigned int t1 = dest_arr[j + 1];
for (int i = 0; i <= 63; ++i) {
t1 -= (t0 + t + 20) ^ ((t0 << 6) + ch_2234[2]) ^ ((t0 >> 9) + ch_2234[3]) ^ 0x10;
t0 -= (t1 + t + 11) ^ ((t1 << 6) + *ch_2234) ^ ((t1 >> 9) + ch_2234[1]) ^ 0x20;
t -= 1166789954;
}
src_arr[j] = t0;
src_arr[j + 1] = t1;
}
for (int i = 0; i < 6; ++i) {
printf("src_arr[%d]=%d\n", i, src_arr[i]);
}
return 0;
}
最后把输入的6个数,转成16进制,连接起来转成字节流就是答案 flag{re_is_great!}
nums = [6712417, 6781810, 6643561, 7561063, 7497057, 7610749, ]
hexstr=''
for n in nums:
hexstr += hex(n)[2:]
print(bytes.fromhex(hexstr).decode('utf-8'))
[FlareOn6]Overlong
32位exe,反汇编发现一共3个函数。各个函数只让Text数组内容发生变化,内容是字符串"I never broke the encoding: "共28个字符(最后有个空格)。联想把入参28改成Text的长度128应该可以得到答案 flag{[email protected]}
#include "stdio.h"
int main_logic_sub(char *Text, char *ch_arr)
{
int step;
char dest_ch;
if ( (int)(unsigned char)*ch_arr >> 3 == 30 )
{
dest_ch = ch_arr[3] & 0x3F | ((ch_arr[2] & 0x3F) << 6);
step = 4;
}
else if ( (int)(unsigned char)*ch_arr >> 4 == 14 )
{
dest_ch = ch_arr[2] & 0x3F | ((ch_arr[1] & 0x3F) << 6);
step = 3;
}
else if ( (int)(unsigned char)*ch_arr >> 5 == 6 )
{
dest_ch = ch_arr[1] & 0x3F | ((*ch_arr & 0x1F) << 6);
step = 2;
}
else
{
dest_ch = *ch_arr;
step = 1;
}
*Text = dest_ch;
return step;
}
unsigned int main_logic(char *Text, char *ch_arr, unsigned int len_28) {
unsigned int i;
for (i = 0; i < len_28; ++i)
{
ch_arr += main_logic_sub(Text, ch_arr);
if ( !*Text++ )
break;
}
return i;
}
int main(int argc, char const *argv[])
{
char Text[128];
unsigned int end;
unsigned char ch_arr[] =
{
0xE0, 0x81, 0x89, 0xC0, 0xA0, 0xC1, 0xAE, 0xE0, 0x81, 0xA5,
0xC1, 0xB6, 0xF0, 0x80, 0x81, 0xA5, 0xE0, 0x81, 0xB2, 0xF0,
0x80, 0x80, 0xA0, 0xE0, 0x81, 0xA2, 0x72, 0x6F, 0xC1, 0xAB,
0x65, 0xE0, 0x80, 0xA0, 0xE0, 0x81, 0xB4, 0xE0, 0x81, 0xA8,
0xC1, 0xA5, 0x20, 0xC1, 0xA5, 0xE0, 0x81, 0xAE, 0x63, 0xC1,
0xAF, 0xE0, 0x81, 0xA4, 0xF0, 0x80, 0x81, 0xA9, 0x6E, 0xC1,
0xA7, 0xC0, 0xBA, 0x20, 0x49, 0xF0, 0x80, 0x81, 0x9F, 0xC1,
0xA1, 0xC1, 0x9F, 0xC1, 0x8D, 0xE0, 0x81, 0x9F, 0xC1, 0xB4,
0xF0, 0x80, 0x81, 0x9F, 0xF0, 0x80, 0x81, 0xA8, 0xC1, 0x9F,
0xF0, 0x80, 0x81, 0xA5, 0xE0, 0x81, 0x9F, 0xC1, 0xA5, 0xE0,
0x81, 0x9F, 0xF0, 0x80, 0x81, 0xAE, 0xC1, 0x9F, 0xF0, 0x80,
0x81, 0x83, 0xC1, 0x9F, 0xE0, 0x81, 0xAF, 0xE0, 0x81, 0x9F,
0xC1, 0x84, 0x5F, 0xE0, 0x81, 0xA9, 0xF0, 0x80, 0x81, 0x9F,
0x6E, 0xE0, 0x81, 0x9F, 0xE0, 0x81, 0xA7, 0xE0, 0x81, 0x80,
0xF0, 0x80, 0x81, 0xA6, 0xF0, 0x80, 0x81, 0xAC, 0xE0, 0x81,
0xA1, 0xC1, 0xB2, 0xC1, 0xA5, 0xF0, 0x80, 0x80, 0xAD, 0xF0,
0x80, 0x81, 0xAF, 0x6E, 0xC0, 0xAE, 0xF0, 0x80, 0x81, 0xA3,
0x6F, 0xF0, 0x80, 0x81, 0xAD, 0x00
};
end = main_logic(Text, ch_arr, 128); // 入参从28改成128
for (int i = 0; i < 128; ++i) {
printf("%c", Text[i]);
}
for (int i = 0; i < 128; ++i) {
if (i % 10 == 0) {
printf("\n");
}
printf("%x, ", Text[i]);
}
return 0;
}
[UTCTF2020]basic-re
看汇编代码,flag被load之后call的函数不是printf,而是一个错误的函数。直接提交flag{str1ngs_1s_y0ur_fr13nd}。
[FlareOn3]Challenge1
32位exe,定制码表的base64加密,flag{[email protected]}
特殊的 BASE64
64位exe,定制码表的base64加密,flag{Special_Base64_By_Lich}
[ACTF新生赛2020]Oruga
64位elf,反汇编发现是个16×16二维迷宫,通路是'\0',终点是'!',其他字符是障碍。W向上,M向下,J向左,E向右。不同点在于,每次方向确定之后,要一直沿着一个方向走,直到碰到障碍物才可以停下,且不能碰到四周边界。因此每步只能选择左右或者上下,再根据不能碰四周边界可以得到 actf{MEWEMEWJMEWJM} 。迷宫如下
0000#0000000####
000##000OO000000
00000000OO0PP000
000L0OO0OO0PP000
000L0OO0OO0P0000
00LL0OO0000P0000
00000OO0000P0000
#000000000000000
000000000000#000
000000MMM000#000
0000000MMM0000EE
00000M0M0M0000E0
00000000000000EE
TTTI0M0M0M0000E0
0T0I0M0M0M0000E0
0T0I0M0M0M!000EE
[ACTF新生赛2020]Universe_final_answer
64位elf程序,puzzle_function(key)函数内容是一个十元一次方程组,用z3求解器解方程组得到输入key,执行得到flag。
from z3 import *
keys = Ints('key_0 key_1 key_2 key_3 key_4 key_5 key_6 key_7 key_8 key_9')
key_0, key_1, key_2, key_3, key_4, key_5, key_6, key_7, key_8, key_9 = keys
s = Solver()
s.add(-85 * key_8 + 58 * key_7 + 97 * key_6 + key_5 + -45 * key_4 + 84 * key_3
+ 95 * key_0 - 20 * key_1 + 12 * key_2 == 12613)
s.add(30 * key_9 + -70 * key_8 + -122 * key_6 + -81 * key_5 + -66 * key_4 +
-115 * key_3 + -41 * key_2 + -86 * key_1 - 15 * key_0 - 30 * key_7 == -54400)
s.add(-103 * key_9 + 120 * key_7 + 108 * key_5 + 48 * key_3 + -89 * key_2 +
78 * key_1 - 41 * key_0 + 31 * key_4 - (key_6 * 64) - 120 * key_8 == -10283)
s.add(71 * key_6 + (key_5 * 128) + 99 * key_4 + -111 * key_2 + 85 * key_1 +
79 * key_0 - 30 * key_3 - 119 * key_7 + 48 * key_8 - 16 * key_9 == 22855)
s.add(5 * key_9 + 23 * key_8 + 122 * key_7 + -19 * key_6 + 99 * key_5 +
-117 * key_4 + -69 * key_2 + 22 * key_1 - 98 * key_0 + 10 * key_3 == -2944)
s.add(-54 * key_9 + -23 * key_7 + -82 * key_2 + -85 * key_0 + 124 * key_1 -
11 * key_3 - 8 * key_4 - 60 * key_5 + 95 * key_6 + 100 * key_8 == -2222)
s.add(-83 * key_9 + -111 * key_5 + -57 * key_0 + 41 * key_1 + 73 * key_2 -
18 * key_3 + 26 * key_4 + 16 * key_6 + 77 * key_7 - 63 * key_8 == -13258)
s.add(81 * key_9 + -48 * key_8 + 66 * key_7 + -104 * key_6 + -121 * key_5 +
95 * key_4 + 85 * key_3 + 60 * key_2 + -85 * key_0 + 80 * key_1 == -1559)
s.add(101 * key_9 + -85 * key_8 + 7 * key_6 + 117 * key_5 + -83 * key_4 +
-101 * key_3 + 90 * key_2 + -28 * key_1 + 18 * key_0 - key_7 == 6308)
s.add(99 * key_9 + -28 * key_8 + 5 * key_7 + 93 * key_6 + -18 * key_5 +
-127 * key_4 + 6 * key_3 + -9 * key_2 + -93 * key_1 + 58 * key_0 == -1697)
if s.check() == sat:
model = s.model()
print(model)
input_key = ''
for k in keys:
n = model[k].as_long() # 转换成int类型
input_key += chr(n)
print(input_key)
# F0uRTy_7w@
[Zer0pts2020]easy strcmp
64位elf,伪代码简单,但执行发现不对。从_libc_start_main函数的init函数看,init执行了ptr_func_0、ptr_func_1、ptr_func_2。ptr_func_1函数取了strcmp和cook_flag两个函数的地址。重点来了,main函数汇编代码,看似call了_strcmp函数,实际上_strcmp又跳转到了p_cook_flag,也就是cook_flag函数。cook_flag函数加密方式比较简单,看成qword整形,把key加回来就行了。注意bytes和int之间转换时的小端序问题。
crypt_str = b'zer0pts{********CENSORED********}'
key = bytes.fromhex('000000000000000042094A4935430A41F019E60BF5F20E0B2B28354A063A0A4F0000000000000000')
lc = len(crypt_str)
flag = ''
for i in range(0, lc, 8):
c = int.from_bytes(crypt_str[i:i + 8], 'little', signed=True) # 小端序
k = int.from_bytes(key[i:i + 8], 'little', signed=True) # 小端序
t = (c + k) & 0xFFFFFFFFFFFFFFFF
m = bytes.fromhex(hex(t)[2:])
flag += m.decode('utf-8')[::-1] # 小端序转成字符串后倒转一下
print(flag)
# zer0pts{l3ts_m4k3_4_DETOUR_t0d4y}
[WUSTCTF2020]level4
64位elf,根据提示,应该是有个type3(x[22])函数,需要自己找到才能出flag。
观察init函数和数据结构,x数组间隔0x18字节,推测该结构体有3个qword长。再根据输出提示,推断是个树状结构。init函数按照this-left-right方式初始化这棵树,也就是flag。
x = 'I{_}Af2700ih_secTS2Et_wr'
flag = x[22] + x[15] + x[20] + x[5] + x[6] + x[8] + x[18] + x[9] + \
x[1] + x[16] + x[11] + x[10] + x[13] + x[2] + x[0] + x[17] + \
x[21] + x[4] + x[12] + x[7] + x[23] + x[14] + x[19] + x[3]
print(flag)
# wctf2020{This_IS_A_7reE}
[网鼎杯 2020 青龙组]singal
32位exe,发现是模拟CPU操作。opcodes数组就是操作码和操作数。
- 10是读取输入。
- 2,3,4,5,11,12是运算。
- 8是暂存。
- 1是存最终结果。
- 6是空指令。
- 7是检查最终结果。
可以模拟出这个过程,然后逐位爆破输入。注意C语言的隐式类型转换,以及python的位操作和C的位操作不同,需要自己写转换。
答案flag{757515121f3d478}
def get_opcodes():
"""
初始化opcodes用的guide的内容
:return:
"""
guide = bytes.fromhex(
'0A0000000400000010000000080000000300000005000000010000000400000020000000080000000500000003000000010000000300000002000000080000000B000000010000000C000000080000000400000004000000010000000500000003000000080000000300000021000000010000000B000000080000000B000000010000000400000009000000080000000300000020000000010000000200000051000000080000000400000024000000010000000C000000080000000B000000010000000500000002000000080000000200000025000000010000000200000036000000080000000400000041000000010000000200000020000000080000000500000001000000010000000500000003000000080000000200000025000000010000000400000009000000080000000300000020000000010000000200000041000000080000000C000000010000000700000022000000070000003F0000000700000034000000070000003200000007000000720000000700000033000000070000001800000007000000A7FFFFFF070000003100000007000000F1FFFFFF07000000280000000700000084FFFFFF07000000C1FFFFFF070000001E000000070000007A000000')
lg = len(guide)
opcodes = []
for i in range(0, lg, 4):
op = int.from_bytes(guide[i:i + 4], 'little', signed=True)
opcodes.append(op)
return opcodes
def int_to_char(n):
"""
将算数运算结果转换到char类型(-128~127)
:param n:
:return:
"""
while n > 127:
n -= 0x100
while n < -128:
n += 0x100
return n
def char_to_logic(n):
"""
python只能对正整数位运算
将char类型(-128~127)转换到可以进行逻辑运算的形式
:param n:
:return:
"""
if n < 0:
return (0x100 + n) | 0xFFFFFF00
else:
return n
def logic_to_char(i):
"""
位运算结果转换为char类型(-128~127)
:param i:
:return:
"""
if (i & 0xFF) >= 0x80:
return (i & 0xFF) - 0x100
else:
return i
def vm_operad(str_buf, one_ops, dest, index):
fc = str_buf[0]
op_idx = 0 # 操作码操作数index
str_idx = 0 # str的index
str_idx_last = 0 # 加密结果暂存index
buf = 0x00
while op_idx < len(one_ops):
if one_ops[op_idx] == 1:
if buf == dest:
ch = chr(fc)
print(f'{index} {fc} {ch}')
return ch
op_idx += 1
elif one_ops[op_idx] == 2:
buf = one_ops[op_idx + 1] + str_buf[str_idx]
buf = int_to_char(buf) # C语言隐式类型转换
op_idx += 2
elif one_ops[op_idx] == 3:
buf = str_buf[str_idx] - one_ops[op_idx + 1]
buf = int_to_char(buf) # C语言隐式类型转换
op_idx += 2
elif one_ops[op_idx] == 4:
buf = one_ops[op_idx + 1] ^ \
char_to_logic(str_buf[str_idx]) # C语言和python位操作不同点
buf = logic_to_char(buf) # C语言隐式类型转换
op_idx += 2
elif one_ops[op_idx] == 5:
buf = one_ops[op_idx + 1] * str_buf[str_idx]
buf = int_to_char(buf) # C语言隐式类型转换
op_idx += 2
elif one_ops[op_idx] == 6:
op_idx += 1
elif one_ops[op_idx] == 8:
str_buf[str_idx_last] = buf
op_idx += 1
str_idx_last += 1
elif one_ops[op_idx] == 11:
buf = str_buf[str_idx] - 1
buf = int_to_char(buf) # C语言隐式类型转换
op_idx += 1
elif one_ops[op_idx] == 12:
buf = str_buf[str_idx] + 1
buf = int_to_char(buf) # C语言隐式类型转换
op_idx += 1
if __name__ == '__main__':
opcodes = get_opcodes()
print(opcodes) # 打印所有操作
str_buf = [0] * 200
enc = [34, 63, 52, 50, 114, 51, 24, -89,
49, -15, 40, -124, -63, 30, 122]
ops = [[4, 16, 8, 3, 5, 1],
[4, 32, 8, 5, 3, 1],
[3, 2, 8, 11, 1],
[12, 8, 4, 4, 1],
[5, 3, 8, 3, 33, 1],
[11, 8, 11, 1],
[4, 9, 8, 3, 32, 1],
[2, 81, 8, 4, 36, 1],
[12, 8, 11, 1],
[5, 2, 8, 2, 37, 1],
[2, 54, 8, 4, 65, 1],
[2, 32, 8, 5, 1, 1],
[5, 3, 8, 2, 37, 1],
[4, 9, 8, 3, 32, 1],
[2, 65, 8, 12, 1], ]
# 爆破输入
flag = ''
for index in range(15): # 逐个爆破
for i in range(0x80): # 爆破0x80以下的可输入字符
str_buf[0] = i
ch = vm_operad(str_buf, ops[index], enc[index], index)
if ch:
flag += ch
print(flag)
以下是opcodes的内容
读取15个字符
10,
对15个字符做操作
4, 16, 8, 3, 5, 1,
4, 32, 8, 5, 3, 1,
3, 2, 8, 11, 1,
12, 8, 4, 4, 1,
5, 3, 8, 3, 33, 1,
11, 8, 11, 1,
4, 9, 8, 3, 32, 1,
2, 81, 8, 4, 36, 1,
12, 8, 11, 1,
5, 2, 8, 2, 37, 1,
2, 54, 8, 4, 65, 1,
2, 32, 8, 5, 1, 1,
5, 3, 8, 2, 37, 1,
4, 9, 8, 3, 32, 1,
2, 65, 8, 12, 1,
检查最终结果
7, 34,
7, 63,
7, 52,
7, 50,
7, 114,
7, 51,
7, 24,
7, -89,
7, 49,
7, -15,
7, 40,
7, -124,
7, -63,
7, 30,
7, 122
[羊城杯 2020]easyre
三次加密,第一层base64,第二层等分成4段调换位置,第三层增强的凯撒密码(数字也变,偏移3)。
密文:EmBmP5Pmn7QcPU4gLYKv5QcMmB3PWHcP5YkPq3=cT6QckkPckoRG
第三次解密:BjYjM2Mjk4NzMR1dIVHs2NzJjY0MTEzM2VhMn0=zQ3NzhhMzhlOD
第二层解密:R1dIVHs2NzJjYzQ3NzhhMzhlODBjYjM2Mjk4NzM0MTEzM2VhMn0=
第一层解密:GWHT{672cc4778a38e80cb362987341133ea2}
next
Pwn部分
test_your_nc
靶机node4.buuoj.cn:29299,使用命令 nc node4.buuoj.cn 29299,ls可以看到flag文件,cat flag得到 flag{4ab7f725-20fc-4fe8-99d2-409a1a69f208}