Reversing/CodeEngn

[CodeEngn] RCE basic L20 풀이

chan2s 2025. 9. 6. 00:39

Q20. 이 프로그램은 Key파일을 필요로 하는 프로그램이다. 'Cracked by: CodeEngn!' 문구가 출력되도록 하려면 crackme3.key 파일 안의 데이터는 무엇이 되어야 하는가 Ex) 41424344454647 (정답이 여러 개 있는 문제로 인증 시 맞지 않다고 나올 경우 Contact로 연락 주시면 확인해 드리겠습니다)

[그림 1] 20.exe 최초 실행

바로 20.exe를 실행해 보면 아무 화면도 출력되지 않는다.

[그림 2] OllyDbg Text 분석

텍스트를 확인해 보면 Cracked by: 이후에 빈칸은 보이지만 아무 내용도 출력되지 않는다는 것을 확인했다.

[그림 3] CreateFileA

401016 ~ 401002D 주소를 보면 CreateFileA함수를 볼 수 있다.

이때 함수의 FileName의 값으로 CRACKME3.KEY를 인자값으로 받는 것을 확인했다.

문제에서 key 파일이 필요하다고 했었다.
메모장을 통해 새로운 key파일을 생성해야 한다. 

CRACKME3.KEY 파일 생성 방법
: 메모장을 통해 빈 .txt파일을 만든 후 이름과 확장자를 바꿔주면 된다.
  이때 이름을 CRACKME3.KEY로 저장하면 된다.
+) 추가 사항
CreateFileA이라는 함수를 실행하는데 받는 인자 값들 중 “Mode” 인자 값을 주목해야 한다.
 Mode값에 따라 CreateFileA의 동작이 조금 달라지는데,
이 프로그램처럼 Mode 3(OPEN_EXISTING)이 입력될 경우에는 CreateFile함수의 조건을 만족하는 파일이 있을 경우에만 파일이 실행되도록 동작하기 때문이다.

[그림 4] 401388 크랙 성공 문구 확인
[그림 5] 401061 ReadFile 함수

이 ReadFile함수를 통해 18byte를 읽는 것을 알 수 있었다.
추가적으로 만약 파일이 18byte를 갖지 못한다면 반응하지 않는다고 한다.

따라서 생성해 둔 CRACKME3.KEY 파일에 123456789123456789를 입력 후 저장하여 다시 실행한다.

[그림 6] CRACKME3.KEY 파일 Read

위 과정을 수행하면 다음과 같이 CRACKME3.KEY 파일에 존재하는 18byte의 데이터를 디버거에서 확인이 가능했다.

[그림 7] 401311 주소 호출

401074주소에서 [그림 6]에서 확인할 수 있는 18Byte에 대해 401311주소를 호출해서 다음 명령어들이 수행되므로
해당 401311주소 알고리즘을 분석해야 했다.

401311 분석


[그림 8] 401311 상세 분석

401315  ESP+4에 ESI에는 입력받은 문자열의 전체가 들어간다.
401319  BL의 초기값으로 0x41이 들어가는 것을 확인한다.
40131D  AL과 BL을 XOR연산 진행한다. 이 결과는 EAX에 저장된다.
40131F  입력받은 전체 문자열에서 AL에는 한 글자씩 들어간다.
401324  EAX와 더한 값을 4020F9에 저장한다.
40132A  AL=0이면 반복 루틴 탈출한다.
401330  BL=4F면  반복 루틴 탈출한다.

[그림 9] 401324 EAX 값 70(p)확인

401324주소를 거치게 되면 CRACKME3.KEY에 저장되었던 첫 글자 31(1)70(p)으로 변경됨을 확인해 볼 수 있었다.

따라서 401311 분석한 내용을 정리해 보면 다음과 같다.

  • BL은 **41(’A’)~ 4F(’O’)**까지 변하는데 마지막의 4F의 값이면 루프를 빠져나오게 되므로 총 14번(0x41~ 0x4E까지)의 변화가 이루어진다.
  • 덤프창에서 전체 KEY파일의 내용 중 앞 14바이트까지 변경됨을 볼 수 있었다.
  • 40133B 주소의 RETN을 만나면 4020F9 주소에 루틴을 거쳐 저장한 값을 반환한다.

[그림 10] 401079 XOR 연산

401311에서 빠져나온 후 더 분석을 진행해 보면
401079 주소에서 앞서 루틴을 거쳐 연산결과의 합이 저장된 값[4020F9]과 0x12345678을 XOR연산 진행한다는 것을 알 수 있었다.
이 XOR연산 값을 다시 [4020F9]에 저장한다.
이 다음 40108B에서 40133C 를 호출하는 것을 볼 수 있었다. 따라서 40133C도 분석을 해야 한다.

40133C 분석


[그림 11] 40133C 분석

40133C에서 402008을 ESI에 저장한다. (Stack창의 ESP+4값 확인)
401340에서는 ESI와 0E를 더한다.
여기서 0E는 0xE(’15’)를 의미하며, 최종적으로 반환되는 값은 EAX에 저장되는 [402008 + 0E]이다.
ReadFile에서 읽어온 바이트 중 15번째 바이트부터 시작한다는 의미이다.


[그림 12] 40133C 이후 분석

다시 40133C를 빠져나와서 더 분석해 보면,
401093에서 EAX값과 4020F9의 값을 비교한다.
동일하지 않다면 40109F의 분기점을 만나 401037로 넘어가게 된다.

401311에서 연산을 진행할 때 앞의 14개의 바이트만 연산하고 나머지 4개의 바이트는 연산하지 않았는데,
나머지 4개의 HEX값이 40133C를 지나면 EAX로 들어오고 4020F9와 비교하고 분기한다는 의미라고 한다.

[그림 13] "-CRACKED!!"문구 생성 알고리즘

4010A1에서 40210E를 인자로, 401346함수를 실행하고,
13번째 글자 지점에 특정 문구(”-Cracked!!”) 를 저장하는 동작을 수행한다.

[그림 14] 401188 성공 분기점

이후에 집중해서 볼 구간은 401188의 CMP AL, 1의 결과에 따라 성공 분기점이 갈리게 된다.
성공 - 40118C , 실패 - 4011A3

따라서 다시 전체적으로 정리한다면

정리


  1. 18바이트 크기의 “CRACKME3.KEY”라는 파일명을 가진 파일이 필요로 한다.
  2. “CRACKME3.KEY”파일의 0x0 ~ 0xD의 바이트가 401311 함수를 거쳤을 때 “CodeEngn”라는 문자열을 출력할 수 있는 값이어야 한다.
  3. 401311 함수 이후에 저장된 [4020F9] 값과 “0x12345678”와 XOR 한 값이 “CRACKME3.KEY”의 0xE ~ 0x12바이트와 같아야 한다.

알아두면 좋을 점
1. A XOR B = C 일 때, C XOR B = A 가 성립된다.
2. 해당 알고리즘을 분석해보면 AL XOR BL = 0 일때 반복문이 종료된다.

이 정리된 식에 따라 코드를 작성한다면 다음과 같다.

xor_data = "A"  # 0x41
input_data = []  # 저장해야 할 data 값
last_char = 

# ASCII 변환을 위한 함수 정의
def hex_to_ascii(hex_string):
    return bytes.fromhex(hex_string).decode('ascii')

# 1. 고정된 출력 문자열 사용
text = "CodeEngn"
print(f"Using output string: {text}")

# 2. 입력한 글자들 XOR한 값 저장
for i in range(len(text)):
    trans_data = ord(text[i]) ^ (ord(xor_data) + i)
    input_data.append(hex(trans_data))
    last_char += ord(text[i])  # 모든 문자 아스키 코드 합

# 3. 14문자 중 부족한 나머지 채우기
i = len(text)
while i < 14:
    input_data.append(hex(ord(xor_data) + i))
    i += 1

# 4. 0x12345678과 XOR 연산
last_char ^= int('0x12345678', 16)

# 5. 마지막 자리(15~18) 연산: last_char를 1바이트씩 분할
for _ in range(4):
    input_data.append(hex(last_char % 0x100))
    last_char = last_char // 0x100

# 6. 결과 출력
print("Data must be...")
print(input_data)

# 7. 0x 접두사 제거하고 대문자로 변환하여 출력
result = []
for hex_val in input_data:
    # 0x 제거하고 대문자로 변환, 2자리로 맞춤
    clean_hex = hex_val[2:].upper().zfill(2)
    result.append(clean_hex)

print("Final result:")
print(' '.join(result))

# 8. ascii 코드 변환
hex_string = (' '.join(result))
ascii_result = hex_to_ascii(hex_string)
print(f"Hex to ASCII is : ")
print(ascii_result)

결과

Using output string: CodeEngn
Data must be...
['0x2', '0x2d', '0x27', '0x21', '0x0', '0x28', '0x20', '0x26', '0x49', '0x4a', '0x4b', '0x4c', '0x4d', '0x4e', '0x7b', '0x55', '0x34', '0x12']
Final result:
02 2D 27 21 00 28 20 26 49 4A 4B 4C 4D 4E 7B 55 34 12
Hex to ASCII is : 
-'!( &IJKLMN{U4


** Process exited - Return Code: 0 **
Press Enter to exit terminal

[그림 15] CRACKME3.KEY 패치

복호화 코드 결과로 나온 -'!( &IJKLMN{U4 를 HxD프로그램을 통해 초반에 생성해 둔 CRACKME3.KEY 파일을 패치하면 된다.

[그림 16] Crack 성공

성공적으로 패치를 진행한 후 실행해 보면 다음과 같은 창이 출력된다.

더보기

정답 : 022D272100282026494A4B4C4D4E7B553412

이번 문제는 처음 접해보는 형식의 문제여서 꽤나 헤맸었다.
최대한 쉽게 이해할 수 있도록 값의 출처들을 수집하고 의미를 파악해 가며 작성했다.
출처 1 :   [CodeEngn] Basic RCE L20 문제 풀이

 

[CodeEngn] Basic RCE L20 문제 풀이

문제 이 프로그램은 Key파일을 필요로 하는 프로그램이다. 'Cracked by: CodeEngn!' 문구가 출력 되도록 하려면 crackme3.key 파일안의 데이터는 무엇이 되어야 하는가 Ex) 41424344454647 (정답이 여러개 있는

sec-kero.tistory.com

출처 2 : codeengn-challenges-writeups/RCE_Basic/L20/RCE Basic L20 Report [areiong].pdf at master · codeengn/codeengn-challenges-writeups

 

codeengn-challenges-writeups/RCE_Basic/L20/RCE Basic L20 Report [areiong].pdf at master · codeengn/codeengn-challenges-writeups

Top Writeups for CodeEngn Challenges. Contribute to codeengn/codeengn-challenges-writeups development by creating an account on GitHub.

github.com

 

'Reversing > CodeEngn' 카테고리의 다른 글

[CodeEngn] RCE mobile L01 풀이  (0) 2025.09.06
[CodeEngn] RCE advance L01 풀이  (0) 2025.09.06
[CodeEngn] RCE basic L19 풀이  (0) 2025.09.05
[CodeEngn] RCE basic L18 풀이  (0) 2025.09.05
[CodeEngn] RCE basic L17 풀이  (0) 2025.09.04