크랙미: do-you-even-keygen?
rust + tauri로 웹뷰를 띄워 실행된다.
tauri sig를 만들어서 할 생각까진 없어서 그냥 assets 덤프했다.
key는 js의 h 함수 내부에서 체크한다.
함수 안에서 key는 r 변수에 저장된다.
if (console.log("Frontend: Starting key verification for:", r), r.trim().length === 0) {
await On.error("Please enter a key");
return
}
if (r.length < 32 || r.length > 128) {
console.log("Frontend: Invalid key length:", r.length), await On.error("Invalid key length");
return
}
try {
const m = await G("verify_key", {
key: r
});
switch (console.log("Frontend: Received validation result:", m), m.debug_info && console.log("Backend debug info:", m.debug_info), m.status) {
case "Valid":
f(!0), await On.success("Impressive! You've generated a valid key.", {
duration: 5e3
});
break;
case "Expired":
f(!1), await On.error("This key has expired");
break;
case "InvalidHardware":
f(!1), await On.error("This key is not valid!");
break;
case "InvalidSignature":
f(!1), await On.error("Invalid key - Keep trying!");
break;
case "InvalidFormat":
case "InvalidChars":
f(!1), await On.error(m.message || "Invalid key format");
break
}
여기서 G는
function G(r, n = {}, o) {
return window.__TAURI_INTERNALS__.invoke(r, n, o)
}
인데
https://v1.tauri.app/v1/guides/features/command/
를 보면 js 내에서 러스트 함수를 호출하는 걸로 보인다.
러스트 내에선
sub_14011D951 함수에서 이를 받아 처리한다.
mov rdi,[rdx+000001B8] // name
mov r14,[rdx+000001C0] // size
이 함수 내부에서 처리하는name의 목록이다.
show_main_window
debug_generate_key
verify_key
.
js 코드에 안보이는 실패 message가 있는데 (Invalid Key)
이건 m.message 에 있다. (rust function에서 넣어줌.)
첫번째 조건은 "-" 를 기준으로 나눠진 데이터 길이가 4인지를 비교한다.
{
v23 = *(sub_140122F04(*(&v63 + 1), 4ui64, 0i64, &off_140229D08) + 8);// src\verifier.rs
// its data[0]
v21 = *(sub_140122F04(*(&v63 + 1), v64, 1ui64, &off_140229D20) + 8);// aSrcVerifierRs
// data[1]
v24 = *(sub_140122F04(*(&v63 + 1), v64, 2ui64, &off_140229D38) + 8);// aSrcVerifierRs
// data[2]
v25 = sub_140122F04(*(&v63 + 1), v64, 3ui64, &off_140229D50);// aSrcVerifierRs
// data[3]
if ( v23 == 16 ) // data[0].size == 16
두번째 조건은 data[0].size가 16, 나머지는 8이여야 한다.
v27 = *(v25 + 8);
if ( v21 == 8 && v24 == 8 && v27 == 8 )
그 후 시간을 구하고
*&v95[0] = get_time(0x1Eui64);
시간 값과 문자열로 변환된 데이터를 가지고 어떤 연산을 한다.
이 과정에 HWID를 구해 데이터를 만드는 과정도 포함된다.
(함수 내부 확인 X)
<[u8]_as_core::cmp::PartialEq>::eq(data[1], data[1].size, 최종연산결과, 길이)
그리고 data[1]과 비교 (첫번째 입력 데이터 비교)
<[u8]_as_core::cmp::PartialEq>::eq(data[3], data[3].size, HWID, 길이)
data[3]은 HWID로 만든 값과 비교한다. (두번째 입력 데이터 비교)
그 다음 또 뭔가를 해서 또 다른 데이터를 만드는데 ( 내부 확인 X )
이 값의 앞 일부분을 data[0]과 비교한다. (세번째 입력 데이터 비교)
<[u8]_as_core::cmp::PartialEq>::eq(data[0], data[0].size, 최종연산결과, 길이)
마지막으로 비교 결과를 검사한다.
v20 = &v67.m256i_i8[1];
if ( v67.m256i_i8[0] ) // 1
{
if ( v67.m256i_i8[1] ) // 2
{
v21 = v67.m256i_i64 + 2;
if ( v67.m256i_i8[2] ) // 3
{
입력을 정리하면
data[0]은 time 관련 값으로 만들어진 데이터
data[1]은 time, hwid로 만들어진 데이터 (Signature)
data[2]는 아무 입력이나 상관 없고
data[3]은 hwid 관련 값으로 고정적이다.
data[0]과 data[1]을 만드는 방법을 알려면 로직을 모두 분석해야 하겠지만 크기가 상당하고 어려움 보단 시간이 많이 드는 과정이라 그렇게까지 하고 싶지 않아 리턴되는 데이터를 받아서 입력값을 만들어 출력하는 식으로 풀었다.

이 CrackMe는 Patch가 금지되어 있는데 푼 방법이 Patch와 비슷하다면
연산 함수 내부 루틴 까진 아니더라도 함수만 복사해서 데이터 생성 로직을 따라 하면 Keygen을 만들 수 있다.
연산 = sha256
data[1] = sha256 결과
참고
https://github.com/tauri-apps/tauri
https://infosecwriteups.com/reverse-engineering-a-native-desktop-application-tauri-app-5a2d92772da5