[crackme]018-crackme_0006
在 0x004011D0 下个断(程序能运行起来,输入信息后check后断下)
这段程序在获取磁盘的序列号,然后进行了逻辑左移,循环左移的操作。但是可以发现左移的结果并没有被使用,通过静态分析可以更明显的观察出来:
- IDA静态分析结果:
直接使用的是磁盘序列号。
- 在求斜边长使用的浮点寄存器
push ebp 保存旧栈帧
mov ebp, esp 建立新栈帧
add esp, -0x4 分配 4 字节局部变量([local.1])
push ebx/esi/edi 保存寄存器
wait 等待 FPU 就绪
finit 初始化 FPU
fild [arg.1] 将第一个整数参数(a)加载到 FPU 栈顶
fld st 复制栈顶(a)
fmulp st(1), st 计算 a²,结果存入 st(1),弹出 st
fild [arg.2] 将第二个整数参数(b)加载到 FPU 栈顶
fld st 复制栈顶(b)
fmulp st(1), st 计算 b²,结果存入 st(1),弹出 st
faddp st(1), st 计算 a² + b²,结果存入 st(0)
fsqrt 计算 √(a² + b²)
fistp [local.1] 将浮点结果四舍五入为整数,存入局部变量
注意:最后求的结果是四舍五入的
随后使用系统 API GetDlgItemTextA 获取输入的用户名,
- 将用户名累乘,再和斜边,再进行循环左移或的操作,再进行与的操作,赋值给 v11
- 对上面的结果对16取余,0到15的数字作为模板字符串的下标,取出字符,赋值给 v10,这个v10就保存计算出的序列号
- 每次整除4,更新 v11,并将 v13 更新
- 最后做是否相等的判断
值得注意的是这个将用户名累乘的操作也不是完全这样的,看汇编代码:
mul ecx 在累乘之后,高位存放在 edx,低位在eax,如果高位 edx 有值,会将edx 加到eax的低位上,所以输入的用户名累乘相加只要在4字节以内就没问题,如果溢出 4 字节就是另外的情况。
破解算法:
import ctypes
from ctypes import wintypesdef arithmetic_left_shift(x: int, n: int, width: int = 32) -> int:"""固定位宽算术左移(低位补 0,高位超出 width 的部分丢弃)width 可设为 8/16/32/64 …"""mask = (1 << width) - 1 # 例如 32 位: 0xFFFF_FFFFreturn (x << n) & maskdef rotate_left(x: int, n: int, width: int = 32) -> int:"""固定位宽循环左移"""n %= width # 允许 n >= widthmask = (1 << width) - 1x &= mask # 先截断到固定位宽return ((x << n) | (x >> (width - n))) & mask
# 定义常量
MAX_PATH = 260# 加载 kernel32.dll
kernel32 = ctypes.windll.kernel32# 定义函数原型
GetVolumeInformationA = kernel32.GetVolumeInformationA
GetVolumeInformationA.argtypes = [wintypes.LPCSTR, # lpRootPathNamewintypes.LPSTR, # lpVolumeNameBufferwintypes.DWORD, # nVolumeNameSizewintypes.LPDWORD, # lpVolumeSerialNumberwintypes.LPDWORD, # lpMaximumComponentLengthwintypes.LPDWORD, # lpFileSystemFlagswintypes.LPSTR, # lpFileSystemNameBufferwintypes.DWORD # nFileSystemNameSize
]
GetVolumeInformationA.restype = wintypes.BOOL# 准备缓冲区
volume_name = ctypes.create_string_buffer(MAX_PATH)
serial_number = wintypes.DWORD()
max_component_length = wintypes.DWORD()
file_system_flags = wintypes.DWORD()
file_system_name = ctypes.create_string_buffer(MAX_PATH)# 调用函数(例如获取 C: 盘信息)
root_path = b"C:\\"
success = GetVolumeInformationA(root_path,volume_name,MAX_PATH,ctypes.byref(serial_number),ctypes.byref(max_component_length),ctypes.byref(file_system_flags),file_system_name,MAX_PATH
)c_serial = serial_number.valueroot_path = b"D:\\"
success = GetVolumeInformationA(root_path,volume_name,MAX_PATH,ctypes.byref(serial_number),ctypes.byref(max_component_length),ctypes.byref(file_system_flags),file_system_name,MAX_PATH
)d_serial = serial_number.value
# 原始序列号 使用勾股定理
c = round((c_serial ** 2 + d_serial ** 2) ** 0.5)
# c = int((c_serial ** 2 + d_serial ** 2) ** 0.5 + 1)
#print(c)def hash_username(name: str) -> int:"""将用户名字符串按 ASCII 码做“累乘+高位回加”,结果始终保持在 32 位无符号整数范围内。"""value = 0for ch in name:byte = ord(ch) # 当前字符 ASCIIvalue = value * 0x100 + byte # 累乘:左移 8 位 + 追加新字节# 处理超 32 位部分while value > 0xFFFFFFFF:high = value >> 32 # 超出 32 位的高位low = value & 0xFFFFFFFF # 低 32 位value = high + low # 高位加回低 32 位return value & 0xFFFFFFFF # 确保返回 32 位无符号username = "easyaaaaaa"
# user_unmber = hash_username(username)
user_unmber = 1
for i in username:user_unmber_gw = 0user_unmber *= ord(i)user_unmber_gw = user_unmber >> 32user_unmber = user_unmber & 0xFFFFFFFFprint(hex(user_unmber),user_unmber_gw)user_unmber = user_unmber + user_unmber_gwbase = (c | rotate_left(user_unmber, 1)) & 0xFFFFFFFmb = '071362de9f8ab45c'
flag = ''
while True:base2 = baseflag += mb[base % 16]base = base2 // 4base2 = baseif(base == 0):breakprint(flag)
62de9f8ab45c’
flag = ‘’
while True:
base2 = base
flag += mb[base % 16]
base = base2 // 4
base2 = base
if(base == 0):
break
print(flag)