CTF逆向中的脚本
int 型 -> 十六进制 string 型
C++ 普通版
特点
- 以
'0x'
开头 - 转换后的字符串输出时可以保证长度都相同,位数不够的话高位补 0
- 结果以字符串形式输出
- 以
参数
参数 | 意义 |
---|---|
num | 待转换的 int 型数据 |
width | 指定转换后得到的十六进制 string 型的长度,不包括 '0x' ,位数不够的话高位补 0 |
- 代码
#include <sstream>
std::string int_to_string(int num, int width){
std::stringstream ioss; // 定义字符串流
std::string s_temp; // 存放转化后字符
ioss << std::hex << num; // 以十六制形式输出
ioss >> s_temp;
if(width > s_temp.size())
{
std::string s_0(width - s_temp.size(), '0'); // 位数不够则补0
s_temp = s_0 + s_temp; // 合并
}
std::string s = "0x" + s_temp.substr(s_temp.length() - width, s_temp.length()); // 取右width位
return s;
}
输入信息:int 型
num = 1125
,位数width = 6
调用:
int_to_string(1125, 6)
输出结果:
0x000465
C++ 递归版
特点
- 开头不包含
'0x'
- 不要求输出的长度都相同
- 结果以字符数组形式输出
- 开头不包含
代码
参数 | 意义 |
---|---|
num | 待转换的 int 型数据 |
buffer | 用来存储转换为 string 型结果的字符数组 |
- 代码
char *int_to_string(int num, char *buffer)
{
static int this_index = 0;
this_index = 0;
if (num < 16) //递归结束条件
{
if (num < 10) //当前数转换成字符放入字符串
buffer[this_index] = num + '0';
else buffer[this_index] = num - 10 + 'a';
buffer[this_index+1] = '\0'; //字符串结束标志
}
else
{
int_to_string(num / 16,buffer); //递归调用
this_index++; //字符串索引+1
num %= 16; //计算当前值
if (num < 10) //当前数转换成字符放入字符串
buffer[this_index] = num + '0';
else buffer[this_index] = num - 10 + 'a';
}
return buffer;
}
输入信息:int 型
num = 1125
,字符数组buffer[10] = {0}
调用:
int_to_string(1125, buffer)
输出信息:
465
Python 版
特点
- 以
'0x'
开头 - 转换后的字符串输出时可以保证长度都相同
- 结果以字符串形式输出
- 以
参数
参数 | 意义 |
---|---|
num | 待转换的 int 型数据 |
width | 指定转换后得到的十六进制 string 型的长度,不包括 '0x' ,位数不够的话高位补 0 |
- 代码
def int_to_string(num, width):
s = hex(num) # 将num转换为十六进制字符串(含0x)
length = width + 2 # 要求的长度加上0x后的长度
if len(s) < length: # 如果位数不合要求
s = '0x' + '0' * (length - len(s)) + s[2:] # 高位补0
return s
输入信息:int 型
num = 1125
,位数width = 6
调用:
int_to_string(1125, 6)
输出结果:
0x000465
十六进制 string 型 -> int 型
C++ 版
特点
- 将十六进制的 string 型数据转换为 int 型
- 也可以从某个长的 string 型数据中截取某一段短的 string 型字符串转换为 int 型
参数
参数 | 意义 |
---|---|
s | 十六进制数的 string 型字符串 |
index | 从下标为 index 的位置开始处理(包括 '0x' 的长度) |
length | 将从 index 开始,长度为 length 的字符串转换为 int 型 |
- 代码
#include <math.h>
int string_to_int(string s, int index, int length){
int sum = 0;
int tmp[length]; // 存放十六进制的每一位字符转换后对应的十进制数
for(int i = 0; i < length; i++){
if(s[index] >= 97 && s[index] <= 102) // 处理a-f
tmp[i] = 9 + s[index] - 96;
else if(s[index] >= 65 && s[index] <= 70) // 处理A-F
tmp[i] = 9 + s[index] - 64;
else // 处理0-9
tmp[i] = s[index] - 48;
sum = sum + tmp[i] * pow(16, length-1 -i);
index++;
}
return sum;
}
输入信息:字符串
s = "0x7D2E370A180F1604"
,索引index = 2
,长度length = 2
调用:
string_to_int(s, 2, 2)
输出结果:
125
(由 0x7D 得来)
Python 版
特点
- 将 string 类型 的十六进制数
s
,从下标index
的位置开始(包括'0x'
),长度为length
的部分,转换为十进制数sum
- 将 string 类型 的十六进制数
参数
参数 | 意义 |
---|---|
s | 十六进制数的 string 型字符串 |
index | 从下标为 index 的位置开始处理(包括 '0x' 的长度) |
length | 将从 index 开始,长度为 length 的字符串转换为 int 型 |
- 代码
def string_to_int(s, index, length):
num = int(s[index: index + length], 16)
return num
输入信息:字符串
s = "0x7D2E370A180F1604"
,索引index = 2
,长度length = 3
调用:
string_to_int(s, 2, 3)
输出结果:
2002
(由 0x7D2 得来)
十六进制 string 型 -> int & char 型数组
C++ 版
特点
- 将十六进制的 string 型数据转换为 int & char 型数组
- 也可以从某个长的 string 型数据中截取某一段短的 string 型字符串转换为 int & char 型数组
- 将
num
对应的 int 改为 char 即可得到 char 型数组
参数
参数 | 意义 |
---|---|
s | 十六进制数的 string 型字符串 |
index | 从下标为 index 的位置开始处理(包括 '0x' 的长度) |
length | 将从 index 开始,长度为 length 的字符串转换为 int & char 型数组 |
num | 用来存储转换为 int & char 型结果的数组 |
- 代码
void string_to_array(string s, int index, int length, int *num){
for (int k = 0; k < (s.length() - 2) / length; ++k) {
int sum = 0;
int tmp[length]; // 存放十六进制的每一位字符转换后对应的十进制数
for(int i = 0; i < length; i++){
if(s[index] >= 97 && s[index] <= 102) // 处理sum-f
tmp[i] = 9 + s[index] - 96;
else if(s[index] >= 65 && s[index] <= 70) // 处理sum-F
tmp[i] = 9 + s[index] - 64;
else // 处理0-9
tmp[i] = s[index] - 48;
sum = sum + tmp[i] * pow(16, length-1 -i);
index++;
}
num[k] = sum;
}
}
输入信息:字符串
s = "0x7D2E370A180F1604"
,索引index = 2
,长度length = 2
,int 型数组num[10] = {0}
调用:
string_to_array(s, 2, 2, num)
输出结果:
[125, 46, 55, 10, 24, 15, 22, 4, 0, 0]
(由 0x7D, 0x2E, 0x37, 0x0A, 0x18, 0x0F, 0x16, 0x04 得来)
Python 版
特点
- 将任意长度的 string 型数据转换为列表 list 形式
- 也可以从某个长的 string 型数据中截取某一段短的 string 型字符串转换为列表 list 形式
参数
参数 | 意义 |
---|---|
s | 十六进制数的 string 型字符串 |
index | 从下标为 index 的位置开始处理(包括 '0x' 的长度) |
length | 将从 index 开始,长度为 length 的字符串转换为 list 列表 |
- 代码
def string_to_array(s, index, length):
buffer = []
for i in range(index, len(s), length):
num = int(s[i: i + length], 16) # 将字符串按照步长,逐个转换为对应的十六进制数
buffer.append(hex(num))
return buffer
输入信息:字符串
s = "0x7D2E370A180F1604"
,索引index = 2
,长度length = 3
调用:
string_to_array(s, 2, 3)
输出结果:
['0x7d2', '0xe37', '0xa1', '0x80f', '0x160', '0x4']
十六进制小端序 int 型 -> 逆向字符串
C++ 版
特点
- 将小端序存放的 int型 数据转换为正序,并将结果存放到 string 字符串 buffer 中
- 若 num 位数超长,将类型改为 __int64
参数
参数 | 意义 |
---|---|
num | 小端序存放的 int 型数 |
width_num | 小端序存放的 int 型数据对应的 十六进制数 除去 '0x' 后的长度 |
buffer_length | 字符串 buffer 的长度,可根据 width_num / 2 得出 |
- 代码
#include <sstream>
#include <math.h>
std::string little_endian(int num, int width_num, int buffer_length){
std::stringstream ioss; // 定义字符串流
std::string s_temp; // 存放转化后字符
ioss << std::hex << num; // 以十六制形式输出
ioss >> s_temp;
if(width_num > s_temp.size())
{
std::string s_0(width_num - s_temp.size(), '0'); // 位数不够则补0
s_temp = s_0 + s_temp; // 合并
}
std::string s = "0x" + s_temp.substr(s_temp.length() - width_num, s_temp.length()); // 取右width位
std::string buffer = "";
for(int index = width_num; index >= 2; index -= 2){
char sum = 0;
int tmp[2]; // 存放十六进制的每一位字符转换后对应的十进制数
for(int i = 0; i < 2; i++){ // 将2长度的字符串转换为十进制数,存放到sum
if(s[index] >= 97 && s[index] <= 102) // 处理a-f
tmp[i] = 9 + s[index] - 96;
else if(s[index] >= 65 && s[index] <= 70) // 处理A-F
tmp[i] = 9 + s[index] - 64;
else // 处理0-9
tmp[i] = s[index] - 48;
sum = sum + tmp[i] * pow(16,2-1 -i);
index++;
}
index = index - 2; // 因为前面修改了index,后面还要用index控制循环,所以这里将其还原
buffer += sum; // 将字符sum逆向存储到 buffer字符串
buffer_length--;
}
return buffer;
}
输入信息:int 型
num = 0x65766F6C
,num 长度width_num = 8
,buffer 长度buffer_length = 4
调用:
little_endian(num, 8, 4)
输出结果:
love
(由 0x6C, 0x6F, 0x76, 0x65 得来)
Python 版
特点
- 将小端序存放的 int型 数据转换为正序,并将结果存放到 string字符串 buffer 中
- 如果 int 型数据的十六进制字符串位数不够,会自动进行补齐
参数:
参数 | 意义 |
---|---|
num | 小端序存放的 int 型数 |
width_num | 小端序存放的 int 型数据对应的 十六进制数 除去 '0x' 后的长度 |
- 代码
def little_endian(num, width_num):
hex_str = hex(num) # 将int数据转换为十六进制的字符串
while len(hex_str) != width_num + 2:
hex_str = "0x" + "0" * (width_num - len(hex_str[2:])) + hex_str[2:] # 位数不足width的用0凑齐
buffer = "" # 用于存放生成的字符串
index = width_num
while index >= 2:
tmp = int((hex_str[index: index+2]), 16) # 每两位string转换为十六进制int型数据
buffer += chr(tmp) # 将int型作为char存入buffer
index -= 2
return buffer
输入信息:int 型
num = 0x7265667463
,num 长度width_num = 10
调用:
little_endian(num, 10)
输出结果:
ctfer
(由 0x63, 0x74, 0x66, 0x65, 0x72 得来)
Python 版 Pwntools 工具
做过 PWN 的应该都比较熟悉了,直接利用 Pwntools 工具来实现,方便快捷
from pwn import *
str1 = 0x67616C66
print(p32(str1))
print(str(p32(str1).decode()))
print()
str2 = 0x7265667463
print(p64(str2))
print(str(p64(str2).decode()))
输出结果:
b'flag' flag b'ctfer\x00\x00\x00' ctfer
Python 版一次处理多数据
对前面几个版本的补充和强化,支持一次性处理多个十六进制小端序数据,并且数据由用户直接输入,对每一个十六进制小端序数据的长度没有要求
由于使用空格隔开,且支持一次性输入多个十六进制数据,非常适合用于堆,在 GDB 中也可以直接通过
x/s
来实现
def little_endian_to_big_endian(hex_data):
"""将小端序的十六进制数据转换为大端序"""
hex_data = hex_data.strip().split(':')[0].strip()
if hex_data.startswith('0x'):
hex_data = hex_data[2:]
# 确保长度是偶数
if len(hex_data) % 2 != 0:
raise ValueError("十六进制数据长度必须为偶数")
# 每两个字符分组为一个字节
bytes_list = [hex_data[i:i + 2] for i in range(0, len(hex_data), 2)]
# 反转字节顺序以转换为大端序
big_endian_bytes = ''.join(reversed(bytes_list))
# 返回大端序的十六进制数据
return '0x' + big_endian_bytes
def hex_to_string(hex_lines):
"""将十六进制数据转换为字符串"""
result = []
for line in hex_lines:
hex_data = line.strip().split(':')[0].strip()
if hex_data.startswith('0x'):
hex_data = hex_data[2:]
# 确保长度是偶数
if len(hex_data) % 2 != 0:
raise ValueError("十六进制数据长度必须为偶数")
# 转换为字节数组
byte_array = bytearray.fromhex(hex_data)
# 解码每个字节为字符并添加到结果列表中
result.append(byte_array.decode('ascii', errors='ignore'))
# 拼接所有字符串
return ''.join(result)
def main():
# 从用户输入获取十六进制数据(用空格分隔)
input_line = input("输入小端序的十六进制数据(用空格分隔):")
hex_lines = input_line.split() # 用空格分割输入
# 处理数据
try:
big_endian_lines = [little_endian_to_big_endian(line) for line in hex_lines]
result = hex_to_string(big_endian_lines)
print(f"转换后的字符串: {result}")
except ValueError as e:
print(f"错误: {e}")
if __name__ == "__main__":
main()
输入:
0x7265667463 0x7265
输出:
ctferer
十六进制小端序 int 型 -> 逆向数组
C++ 版
特点
- 将小端序存放的 int型 数据转换为正序,并将结果存放到 int型 的 buffer数组 中
- 若 num 位数超长,将类型改为 __int64
- 若要保存到 char 数组,直接将 int buffer 改为 char buffer 即可
参数
参数 | 意义 |
---|---|
num | 小端序存放的 int 型数据 |
width_num | 小端序存放的 int 型数据对应的 十六进制数 去掉 '0x' 后的长度 |
buffer | 用来保存处理结果的 int 型 数组 buffer[] ,buffer[] 是 num 的逆向顺序 |
buffer_length | buffer[] 的长度,可根据 width_num / 2 得出 |
- 代码
#include <sstream>
#include <math.h>
void little_endian(int num, int width_num, int *buffer, int buffer_length){
std::stringstream ioss; // 定义字符串流
std::string s_temp; // 存放转化后字符
ioss << std::hex << num; // 以十六制形式输出
ioss >> s_temp;
if(width_num > s_temp.size())
{
std::string s_0(width_num - s_temp.size(), '0'); // 位数不够则补0
s_temp = s_0 + s_temp; // 合并
}
std::string s = "0x" + s_temp.substr(s_temp.length() - width_num, s_temp.length()); // 取右width位
for(int index = 2; index + 2 - 1 < s.length(); index += 2){
int sum = 0;
int tmp[2]; // 存放十六进制的每一位字符转换后对应的十进制数
for(int i = 0; i < 2; i++){ // 将2长度的字符串转换为十进制数,存放到sum
if(s[index] >= 97 && s[index] <= 102) // 处理a-f
tmp[i] = 9 + s[index] - 96;
else if(s[index] >= 65 && s[index] <= 70) // 处理A-F
tmp[i] = 9 + s[index] - 64;
else // 处理0-9
tmp[i] = s[index] - 48;
sum = sum + tmp[i] * pow(16,2-1 -i);
index++;
}
index = index - 2; // 因为前面修改了index,后面还要用index控制循环,所以这里将其还原
buffer[buffer_length-1] = sum; // 将十进制数sum逆向存储到 buffer[]
buffer_length--;
}
}
输入信息:int 型
num = 0x65766F6C
,num 长度width_num = 8
,int 型数组buffer[10] = {0}
,buffer 长度buffer_length = 4
调用:
little_endian(num, 8, buffer, 4)
输出结果:
[108, 111, 118, 101, 0, 0, 0, 0, 0, 0]
(由 0x6C, 0x6F, 0x76, 0x65 得来)
Python 版
特点
- 将小端序存放的 int 型 数据转换为正序,并将结果存放到列表 buffer 中
- 如果 int 型数据的十六进制字符串位数不够,会自动进行补齐
参数:
参数 | 意义 |
---|---|
num | 小端序存放的 int 型数 |
width_num | 小端序存放的 int 型数据对应的 十六进制数 除去 '0x' 后的长度 |
- 代码
def little_endian(num, width_num):
buffer = [] # 存放结果的列表
hex_str = hex(num) # 将int数据转换为十六进制的字符串
while len(hex_str) != width_num + 2:
hex_str = "0x" + "0" * (width_num - len(hex_str[2:])) + hex_str[2:] # 位数不足width的用0凑齐
index = width_num
while index >= 2:
tmp = int((hex_str[index: index + 2]), 16) # 每两位string转换为十六进制int型数据
buffer.append(chr(tmp)) # 将int型作为char存入buffer
index -= 2
return buffer
输入信息:int 型
num = 0x7265667463
,num 长度width_num = 10
调用:
little_endian(num, 10)
输出结果:
['c', 't', 'f', 'e', 'r']
(由 0x63, 0x74, 0x66, 0x65, 0x72 得来)
Python 版 Pwntools 工具
做过 PWN 的应该都比较熟悉了,直接利用 Pwntools 工具来实现,方便快捷
from pwn import *
str1 = 0x67616C66
print(p32(str1))
print(list(str(p32(str1).decode())))
print()
str2 = 0x7265667463
print(p64(str2))
print(list(str(p64(str2).decode())))
输出结果:
b'flag' ['f', 'l', 'a', 'g'] b'ctfer\x00\x00\x00' ['c', 't', 'f', 'e', 'r', '\x00', '\x00', '\x00']
bytes 型 -> 十六进制字符串
Python 版
特点
- bytes 型中有些字符不可打印,会用
'\xf3'
之类的来表示,与可打印字符混在一起很不好看,可以将其全部转为 16 进制的字符串
- bytes 型中有些字符不可打印,会用
代码
import binascii
enc_byte = b'\x91E2\xf3\xc0g~D\xc5\x16\x9a\to\xfc\xcb\xd7'
enc_str = binascii.b2a_hex(enc_byte)
print(enc_str)
# b'914532f3c0677e44c5169a096ffccbd7'
走迷宫
Python 版
特点
- 给出迷宫,起始位置和终点位置,输出走迷宫的路径
- 迷宫中 0 代表路,1 代表墙壁
代码
maze = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1],
[1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1],
[1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1],
[1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
usedmap = [[0 for i in range(len(maze[0]))] for i in range(len(maze))]
# 生成与迷宫相同规格的全0列表,用来记录已经走过的位置
sti = 1 # 初始位置的横坐标
stj = 1 # 初始位置的纵坐标
edi = 5 # 终点位置的横坐标
edj = 5 # 终点位置的纵坐标
flag = ''
def dfs(x, y): # 走迷宫
global flag
if x == edi and y == edj:
print(flag)
return
if maze[x + 1][y] == 0 and usedmap[x + 1][y] == 0: # 可以往下走
usedmap[x][y] = 1 # 将现在所处的点标记,因为不能再走,否则会兜圈子
flag += 's' # 向下走,记录路径
dfs(x + 1, y) # 从下一个位置开始走
flag = flag[:-1]
usedmap[x][y] = 0 # 遇到死胡同,回退到标记的地方
if maze[x - 1][y] == 0 and usedmap[x - 1][y] == 0: # 可以往上走
usedmap[x][y] = 1 # 将现在所处的点标记,因为不能再走,否则会兜圈子
flag += 'w' # 向上走,记录路径
dfs(x - 1, y) # 从下一个位置开始走
flag = flag[:-1]
usedmap[x][y] = 0 # 遇到死胡同,回退到标记的地方
if maze[x][y + 1] == 0 and usedmap[x][y + 1] == 0: # 可以往右走
usedmap[x][y] = 1 # 将现在所处的点标记,因为不能再走,否则会兜圈子
flag += 'd' # 向右走,记录路径
dfs(x, y + 1) # 从下一个位置开始走
flag = flag[:-1]
usedmap[x][y] = 0 # 遇到死胡同,回退到标记的地方
if maze[x][y - 1] == 0 and usedmap[x][y - 1] == 0: # 可以往左走
usedmap[x][y] = 1 # 将现在所处的点标记,因为不能再走,否则会兜圈子
flag += 'a' # 向左走,记录路径
dfs(x, y - 1) # 从下一个位置开始走
flag = flag[:-1]
usedmap[x][y] = 0 # 遇到死胡同,回退到标记的地方
dfs(sti, stj)
生成迷宫
Python 版
特点
- 用于辅助走迷宫脚本,快速生成迷宫的二维列表形式的数据
代码
maze = [] # 存放生成的迷宫,是一个二维列表
maze_line = 16 # 迷宫的行数
maze_column = 16 # 迷宫的列数
# 迷宫数据
maze_str = "1111111111111111100000111111011110111011111101111011101100010111101110110101011110111000010101111011111101010111101111110001011110111111101101111011111110110111100001100001000111110111101101011111011110110101100001111011010010111111100001111011111111111111"
# maze_tmp 用来暂存每一行的迷宫数据,一维列表
maze_tmp = []
for i in range(len(maze_str)):
if i % maze_column == 0 and i > 0:
maze.append(maze_tmp) # 每 maze_column 个数据作为一组,加入 maze
maze_tmp = [] # 一组加入完后,需要将 maze_tmp 置空,用于存放下一组数据
maze_tmp += maze_str[i]
if i == len(maze_str) - 1: # 最后一行数据,直接加入maze即可
maze.append(maze_tmp)
for i in range(maze_line): # 输出迷宫
print(maze[i])
解方程
Python 版
特点
- 可求解 n 元 n 次方程组
- 无法判断无解的情况(会取近似值),但是可以从解出来的结果判断是否无解(无解的情况解出来的值是很长很长的不循环小数)
代码
from sympy import *
# 创建未知数
v, w, x, y, z = symbols('v w x y z')
# 定义方程组
eq1 = Eq(v * 23 + w * -32 + x * 98 + y * 55 + z * 90, 333322)
eq2 = Eq(v * 123 + w * -322 + x * 68 + y * 67 + z * 32, 707724)
eq3 = Eq(v * 266 + w * -34 + x * 43 + y * 8 + z * 32, 1272529)
eq4 = Eq(v * 343 + w * -352 + x * 58 + y * 65 + z * 5, 1672457)
eq5 = Eq(v * 231 + w * -321 + x * 938 + y * 555 + z * 970, 3372367)
# 解方程组
sol = solve((eq1, eq2, eq3, eq4, eq5), (v, w, x, y, z))
# 打印解
print(sol)