| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- CodeEngn
- __fastcall
- pe format
- RVA
- 코드엔진
- Reversing
- 프로그래머스
- 파이썬
- Programmers
- Dos Stub
- stack frame
- __stdcall
- Rich Header
- image section header
- 실행파일
- crackme
- Python
- ABI
- Calling Convention
- 리치헤더
- 함수 호출 규약
- 리버싱
- rev
- 크랙미
- 32bit
- __cdecl
- x32
- __vectorcall
- Image dos header
- x64
- Today
- Total
kj0on
[RCE] Basic L02 본문
0. 실행환경
0-1. 운영체제 (1)

0-2. 툴 (4)




1. 파일

password : codeengn

2. 프로그램 동작

실행 시 보이는 화면은 위와 같다. 해당 오류 메시지를 보여주면서 프로그램을 실행할 수 없다.
3. Detect It Easy

DIE에서 실행 파일을 읽을 수 없다. PE 구조가 훼손되서 제대로 읽지 못하는 듯 하다.
4. 분석

PE구조를 읽을 수 없기 때문에 Hex Editor로 값을 확인해 봐야한다. 먼저 나타나는 DOS Header 부분을 보면 e_magic의 시그니처 MZ (0x4D5A)가 나타나는 것을 확인할 수 있다. 그 다음 e_lfanew의 값을 참조해 0x00000200번지의 값을 확인한다.

NT Header 부분이 0x00으로 채워져 있다. Signature(0x50450000)값 또한 0x00이다. NT Header에 값이 없기 때문에 프로그램 실행이 안된 것이다.

다시 처음으로 돌아와서 이제 삭제된 부분을 파악해 준다. 현재 파악할 수 있는것은 "MZ" 시그니처와 ".text"가 나타나는 섹션헤더 부분이다. 따라서 DOS Header ~ Section Header 사이에서 값들이 지워졌다는 것을 알 수있다.

.text의 Raw Adress의 값이 0x400이다. 해당 값을 통해 .text section의 위치를 알 수 있다.

밑으로 내려서 .text section의 시작 주소를 확인해 보면 0x350이다. Raw Adress와 차이를 통해 얼만큼 삭제됐는지를 알아낼 수 있다. 원래 0x400위치에서 나타나야할 .text section이 0x350위치에서 나타났기 때문에 삭제된 데이터의 크기는 0xB0(176byte)이다.

Section Header에서 각 섹션의 Virtual Address를 파악한다. 각각 .text(0x1000) .rdata(0x2000) .data(0x3000) .rsrc(0x4000)으로 나타난다. 값이 0x1000의 배수로 나타나는 점을 고려해 Optional Header의 Section Alignment의 값은 0x1000인 것을 파악할 수 있다. 실행파일의 경우 ImageBase값이 0x00400000으로 나타난다는 것을 활용해 Optional Header에서 남아 있는 값이 있는지와 값의 위치를 알아낼 수 있다. ImageBase(0x00400000)와 SectionAlignment(0x00001000) 값이 붙어 있기 때문에 0x00004000 00100000의 값이 존재하는지 확인하면 된다(리틀엔디언).

Optional Header의 ImageBase, SectionAlignment 값이 존재한다. 해당 값과 위치를 파악했으니 이제 삭제된 부분을 복구해줘야 한다. 어떤 값이 삭제됐는지는 아직 불분명 하기 때문에 우선 크기와 위치만 맞춰준다. PE구조만 맞춰 준 다음 값들은 이후에 복구해야 작업이 편하다.

0xB0(176byte)만큼 크기를 맞춰줬다. 채워진 부분을 구분하기 쉽게 0xFF로 채워줬다. Optional Header의 Magic 값도 없어서 정확히 어디서 끊어진 건지는 알기 힘들었다.

크기를 맞춰 줬기 때문에 .text section 위치가 알맞게 조정됐다.

ImageBase와 NT Header의 Signature와의 오프셋은 0x34이다. ImageBase에서 0x34를 빼서 Signature의 주소를 찾아준다. 해당 위치에는 "PE"(0x50450000)가 나타나야 한다. NT Header의 오프셋인 e_lfanew의 값 또한 알맞게 수정해 줘야한다.

NT Header 복구가 완료 됐다. 저장 한 뒤 CFF Explorer로 열어준다.

PE구조를 볼 수 있다. 하지만 프로그램은 아직 실행이 안된다. 그 이유는 0xFF로 설정된 값들이 있기 때문이다. 해당 값들을 알맞게 수정해 줘야한다.

Dos Header 부분은 e_magic, e_lfanew를 제외하고 실행에 영향을 주지 않는다. 따라서 File Header 부분 부터 값들을 수정한다.

Machine(intel 386), NumberOfSections(섹션4개), Characteristics(excutable)으로 설정한다.

Section Header를 보면 값을 읽어오지 못한다는 것을 볼 수 있다. 그 이유는 SizeOptionalHeader 때문인데 값이 0xFFFF로 설정되어 있기 때문에 섹션을 읽지 못하는 것이다. Section Header는 SizeOptionalHeader의 오프셋으로 접근한다(File Header가 끄끝나는 지점 + SizeOptionalHeader). 따라서 값을 Optional Header의 크기만큼 설정해야 한다. (32bit의 경우 0xE0, 64bit의 경우 0xF0의 값을 가진다.)

SizeOptionalHeader의 값을 0xE0으로 수정했을 때 Section Header의 값이 제대로 들어왔다. 따라서 해당 파일은 32bit 실행파일 인 것을 알 수 있다.

File Header 수정 값은 위와 같다.

그 다음으로 Optional Header의 값을 수정한다. ImageBase 위의 값들(0xFF)을 수정해야 한다.

ImageBase 이후의 값은 제대로 잘 들어가 있는것으로 보였다. IAT도 확인해 본 결과 IDD, NAME 등 잘 나왔다. 이제 수정해야 할 값은 Magic과 AddressOfEntryPoint다. Magic은 32bit 실행파일인 것을 확인해 줬기 때문에 0x010B로 수정하면 된다. 문제는 AddressOfEntryPoint인데 .text의 RVA의 값이 EntryPoint를 보장하진 않는다. .text section의 RVA값을 넣고 EntryPoint가 아니면 디버거에서 OEP를 찾기로 했다. 따라서 AddressOfEntryPoint에는 일단 .text의 RVA값을 넣어줬다.

실행해 보면 위와 같은 오류가 나오는데 해당 오류는 프로그램이 계속 실행중이기 때문이다.

종료되지 않고 계속 실행된다.

작업끝내기도 거부되는데 이 경우 CFF Explorer에서 02.exe를 열고 있기 때문에 종료가 안되고 해당 오류가 발생한다. 간단하게 CFF Explorer를 끄면 다시 정상적으로 동작한다.

파일이 정상적으로 실행된다.

Check It! 버튼 클릭 시 메시지를 띄워준다. 값을 입력하지 않았을 때만 메시지가 출력된다.

EntryPoint 부터 간단하게 동작만 분석했다.

Check It! 버튼 클릭 시 GetDlgItemTextA로 TextBox에 입력한 문자열을 해당 인자값의 버퍼에 저장한다.

해당 버퍼에 사용자가 입력한 문자열이 저장된다.

이후 반복문을 통해 두 문자열을 비교한다. 총 7번 반복한다.

비교하는 값을 확인해 보면 전부 0x00이다. 입력한 문자열과 0x00을 비교하는 반복문이다(총 7번). 따라서 입력이 없을 때 MessageBoxA가 출력된다. 만약 입력값이 있으면 분기 하여 MessageBoxA를 호출하지 않고 다음 동작을 수행한다. 결과적으로 입력값이 있을 때는 아무런 동작을 하지 않는다.

SendMessageA는 종료할 때(Exit버튼) 사용되는 함수다.

의도는 파악하지 못했다. 프로그램의 전체적인 동작은 입력값이 없을 때만 성공 메시지를 띄워준다는 것이다.

문자열 중 해당 부분은 사용되지 않는다.

EntryPoint로 가면 GetmoduleHandleA 호출 후 mov해주는 코드가 있다.

해당 코드를 실행하기 전 상태다.

코드를 실행하면 해당 부분이 핸들 값으로 변한다. 그 결과 처음 상태였던 "JK3FJZh"의 일부 값이 덮어씌워진다. 이후 해당 "JK3FJZh" 값은 사용되지 않는다.
5. 풀이

HxD에서 문자열 위주로 살펴보면 패스워드로 추측되는 문자열을 찾을 수 있다.

프로그램 복구 후 EntryPoint에서 참조되는 메모리 덤프를 확인하면 문자열 "JK3FJZh"가 포함된 값을 확인할 수 있으며, 이를 통해 해당 값이 패스워드임을 추정할 수 있다. 그러나 분석 결과 이 문자열은 프로그램 실행 중 처음 덮어 써지는 부분 이후 별도로 참조되거나 비교되는 과정이 존재하지 않기 때문에 출력 결과에는 영향을 주지 않는다. 결과적으로 "JK3FJZh"가 패스워드라는 근거는 프로그램 내부에 존재하지 않기 때문에 직감으로 풀어야 하는 문제인듯 하다(Guessing).
복구파일
'Reversing > CodeEngn' 카테고리의 다른 글
| [RCE] Basic L06 (0) | 2025.06.29 |
|---|---|
| [RCE] Basic L05 (0) | 2025.06.29 |
| [RCE] Basic L04 (0) | 2025.06.29 |
| [RCE] Basic L03 (0) | 2025.06.28 |
| [RCE] Basic L01 (0) | 2025.06.28 |