ReadMe에 나와있듯이 Serial이 5B134977135E7D13일 때의 Name 값을 찾아야 한다.
ReversingKr KeygenMe
Find the Name when the Serial is 5B134977135E7D13
IDA로 열어본 Easy Keygen의 main 함수이다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
signed int v3; // ebp
int i; // esi
char v6; // [esp+Ch] [ebp-130h]
char v7[2]; // [esp+Dh] [ebp-12Fh] BYREF
char v8[100]; // [esp+10h] [ebp-12Ch] BYREF
char Buffer[197]; // [esp+74h] [ebp-C8h] BYREF
__int16 v10; // [esp+139h] [ebp-3h]
char v11; // [esp+13Bh] [ebp-1h]
memset(v8, 0, sizeof(v8));
memset(Buffer, 0, sizeof(Buffer));
v10 = 0;
v11 = 0;
v6 = 16;
qmemcpy(v7, " 0", sizeof(v7));
sub_4011B9(aInputName);
scanf("%s", v8);
v3 = 0;
for ( i = 0; v3 < (int)strlen(v8); ++i )
{
if ( i >= 3 )
i = 0;
sprintf(Buffer, "%s%02X", Buffer, v8[v3++] ^ v7[i - 1]);
}
memset(v8, 0, sizeof(v8));
sub_4011B9(aInputSerial);
scanf("%s", v8);
if ( !strcmp(v8, Buffer) )
sub_4011B9(aCorrect);
else
sub_4011B9(aWrong);
return 0;
}
사용자가 입력한 name의 값을 특정 방식으로 변환하여 Buffer에 담고 있다.
이후, Buffer의 값은 사용자가 입력한 serial과 문자열 비교가 이루어지므로 ReadMe의 serial을 역연산하여 name을 찾아야 한다.
scanf("%s", v8);
v3 = 0;
for ( i = 0; v3 < (int)strlen(v8); ++i )
{
if ( i >= 3 )
i = 0;
sprintf(Buffer, "%s%02X", Buffer, v8[v3++] ^ v7[i - 1]);
}
어떻게 XOR 연산이 수행되는지 알아보기 위해 실행 파일을 OllyDbg로 분석하였다.
스택의 특정 위치에 값 10, 20, 30을 설정하고 있다.
이후, XOR 연산에 사용될 것으로 보인다.
00401038 |. C64424 10 10 MOV BYTE PTR SS:[ESP+10],10
0040103D |. C64424 11 20 MOV BYTE PTR SS:[ESP+11],20
00401042 |. C64424 12 30 MOV BYTE PTR SS:[ESP+12],30
name을 입력받고 문자열의 길이를 계산하는 부분이다.
0040106E |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
00401070 |. F7D1 NOT ECX
00401072 |. 49 DEC ECX
REPNE SCAS BYTE PTR ES:[EDI]는 ECX 레지스터가 0일 될 때까지 반복 실행한다.
* 이는 REPNE뿐만 아니라 REP, REPE(Repeat Equal), REPZ(Repeat Zero), REPNE(Repeat Not Equal), REPNZ(Repeat Not Zero) 등 REP 접두어가 붙은 명령어들 모두에서 동일하게 작동한다.
입력한 name이 'hello'라면 EDI는 문자열 "hello"의 시작 주소를 가르키게 되고 EDI가 각 문자를 순서대로 검사할 때 ECX는 1씩 감소한다.
- 첫 번째 반복: EDI가 'h'를 가리키고 ECX는 0xFFFFFFFE가 된다.
- 두 번째 반복: EDI가 'e'를 가리키고 ECX는 0xFFFFFFFD가 된다.
- 세 번째 반복: EDI가 'l'를 가리키고 ECX는 0xFFFFFFFC가 된다.
- 네 번째 반복: EDI가 'l'을 가리키고 ECX는 0xFFFFFFFB가 된다.
- 다섯 번째 반복: EDI가 'o'를 가리키고 ECX는 0xFFFFFFFA가 된다.
- 여섯 번째 반복: EDI가 널 바이트(0x00)를 가리키고 ECX는 0xFFFFFFF9가 된다.
- 널 바이트를 만나면, **Zero Flag (ZF)**가 설정되고, REPNE SCASB 반복이 중단된다.
현재 ECX는 0xFFFFFFF9이며 NOT ECX 명령어로 모든 비트가 반전되면 0x00000006이 된다.
DEC ECX로 ECX를 1 감소시키면 ECX는 5가 되며 문자열 "hello"의 길이와 같다.
주어진 입력값에 대해 XOR 연산을 하는 부분이다.
스택에 저장된 입력값과 사전 설정된 값 10,20,30을 반복적으로 XOR 연산하여 ECX에 저장한다.
00401077 |> 83FE 03 /CMP ESI,3
0040107A |. 7C 02 |JL SHORT Easy_Key.0040107E
0040107C |. 33F6 |XOR ESI,ESI
0040107E |> 0FBE4C34 0C |MOVSX ECX,BYTE PTR SS:[ESP+ESI+C]
00401083 |. 0FBE542C 10 |MOVSX EDX,BYTE PTR SS:[ESP+EBP+10]
00401088 |. 33CA |XOR ECX,ED
다음과 같이 readme의 serial에 대해 역연산을 하면 답을 찾을 수 있다.
serial = "5B134977135E7D13"
key_pattern = [0x10, 0x20, 0x30]
serial_bytes = [int(serial[i:i+2], 16) for i in range(0, len(serial), 2)]
xor_result = []
for i, byte in enumerate(serial_bytes):
xor_byte = byte ^ key_pattern[i % len(key_pattern)]
xor_result.append(xor_byte)
print(''.join([chr(x) for x in xor_result]))
# K3yg3nm3
'워게임 > 리버싱' 카테고리의 다른 글
[Dreamhack] Small Counter (0) | 2024.08.30 |
---|---|
[Dreamhack] Simple Crack Me (0) | 2024.08.29 |
[Dreamhack] rev-basic-2 (0) | 2024.08.17 |
[Reversing.kr] Easy ELF (0) | 2024.08.13 |
[Dreamhack] rev-basic-4 (0) | 2024.08.11 |