SE壳破解已经有各种教程了,本文提供一个另类的思路来找公钥和随机key(下次发)。
公钥获取
可以确定的是SE壳的公钥和随机Key是直接在PE文件中的,并且找到了这两个就可以直接修改并通过修补最后两个字节来达到修改授权的目的,其他内容在网站都可以找到就不说了。
目的:存在a.exe或dll为目标PE,我们要修改公钥并解密a.key来达到修改成我们自己的授权。
步骤如下:
-
构建一个PE为b.exe,已知它的公钥和随机Key地址
-
a.key拷贝成b.key,也就是b.exe可以加载a文件的key
-
解析a.exe,得到第一个节点 .sedata ,找到非0连续地址,然后读取指定大小后启动b.exe
-
内存修改b.exe对应公钥地址为上面猜解值,启动b.exe并拦截弹框
-
循环执行4,直到弹框报错 '该授权文件与本程序不匹配。', 这样就得到公钥地址+公钥。
参考代码如下
其中,PE解析代码例子如下
// 读取输入文件
let mut input_file = File::open(&cli.input)?;
let mut buffer = Vec::new();
input_file.read_to_end(&mut buffer)?;
let pe = PE::parse(&buffer).map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "Invalid PE file"))?;
let (sedata_offset, sedata_size) = pe.sections.iter()
.find(|s| s.name().unwrap_or("") == ".sedata")
.map(|s| (s.pointer_to_raw_data, s.size_of_raw_data))
.unwrap_or((0, 0));
println!(".sedata Offset: 0x{:08X}, Size: 0x{:08X}", sedata_offset, sedata_size);
其中,拦截弹框并返回
// 添加新的窗口枚举回调函数
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
DWORD processId;
GetWindowThreadProcessId(hwnd, &processId);
if (processId == GetCurrentProcessId()) {
char title[256] = {0};
GetWindowTextA(hwnd, title, sizeof(title));
HWND child = GetWindow(hwnd, GW_CHILD);
int controlIndex = 0;
while (child && controlIndex < 2) {
child = GetWindow(child, GW_HWNDNEXT);
controlIndex++;
}
if (child) {
int textLength = GetWindowTextLengthW(child) + 1;
if (textLength > 1) {
wchar_t* wideText = new wchar_t[textLength];
GetWindowTextW(child, wideText, textLength);
int mbLength = WideCharToMultiByte(CP_UTF8, 0, wideText, -1, NULL, 0, NULL, NULL);
char* mbText = new char[mbLength];
WideCharToMultiByte(CP_UTF8, 0, wideText, -1, mbText, mbLength, NULL, NULL);
// 输出JSON格式
printf("{\"title\": \"%s\", \"text\": \"%s\"}\n", title, mbText);
delete[] wideText;
delete[] mbText;
// 退出进程
ExitProcess(-1);
}
}
}
return TRUE;
}
// 添加新的监控线程函数
void WindowMonitorThread() {
while (isMonitorRunning) {
EnumWindows(EnumWindowsProc, 0);
std::this_thread::sleep_for(std::chrono::milliseconds(30));
}
}
判断返回
fn parse_execution_result(output: std::process::Output) -> ExecutionResult {
match output.status.code().unwrap_or(-1) {
-1073741819 => ExecutionResult::DecryptionFailed,
-1 => {
if let Ok(json_output) = serde_json::from_slice::<serde_json::Value>(&output.stdout) {
if let Some(text) = json_output.get("text").and_then(|v| v.as_str()) {
match text {
"文件已损坏,无法运行。" => ExecutionResult::FileCorrupted,
"该授权文件与本程序不匹配。" => ExecutionResult::InvalidLicense,
_ => ExecutionResult::UnknownError(text.to_string()),
}
} else {
ExecutionResult::UnknownError("无法解析 JSON 输出".to_string())
}
} else {
ExecutionResult::UnknownError("标准输出无法解析为 JSON".to_string())
}
},
0 | 1 => ExecutionResult::Success(
String::from_utf8_lossy(&output.stdout).to_string(),
String::from_utf8_lossy(&output.stderr).to_string()
),
code => ExecutionResult::UnknownError(format!("未知错误码: {}", code)),
}
}
结果
通过测试,所有的SE2加壳均可以快速(1-5分钟)找到key,并且测试在linux上可用,不需要启动目标程序就可以得到公钥。至于随机数,看这篇有没有人关注,多的话会发,大概原理采用数学统计原理找到目标区域。
思路可以推广到其他强壳。