security/리버싱 - CodeEngn.com

Advance RCE L01 ~ L07

민사민서 2023. 4. 20. 23:39

L01. Basic L19랑 동일, 프로그램 종료 시간 구하기

- UPX로 패킹되어있고
- 오버레이 존재한다, Autoit compiled script가 들어있음
- "CodeEngn.com by Lee Kang-Seok" 메시지박스 출력되고 대략 12초 후 창 꺼진다

역시나 Ollydbg로 분석해보니 안티 디버깅 기법 적용되어있었음
KERNEL32!IsDebuggerPresent API 호출을 통한 디버거 탐지 기법 적용되어있음
JNZ 004338DE 를 JMP 0040E96F로 패치하여 다른 파일로 저장한다 (의미없는 점프문)

Search for string 해도 의미있는 문자열 안 나오고 (동적으로 복호화되는듯)
Search for intermodular calls 에서 MessageBoxW API 호출부에 전부 bp 건다

444D88에서 bp 걸림
마찬가지로 _beginthreadex로 메인 스레드 외에 새 스레드를 생성하고
메시지박스 호출하고(00444D88에서)
메시지박스 창 닫히면 새로 생성된 스레드가 종료될 때까지 기다렸다가 CloseHandle 한다

_beginthreadex
3rd parameter (startaddress) : 00444C3A
4th parameter (argList) : ESP+0x10 주소, 008AF878

00444C3A(새 스레드의 실행이 시작되는 함수 주소)를 IDA로 분석해보자
- 배열 인자의 두번째 원소값만큼 시간이 흐를때까지 while문 반복 (각 반복에서 sleep(10ms) 진행)
- 충분한 시간이 지나면 메인 스레드의 MessageBox 창을 찾아 OK button을 누른다

뭐야 똑같은 문제네. 444C3A에 bp 걸고 새로운 스레드가 생성될 때 전달된 인자 값을 확인하면 된다
인자로 전달된 값 008AF878, Hex Dump로 확인해보니 두번째 원소 값은 0x337B = 13179

=> 정답은 13179의 md5 해쉬값 (sleep(10ms) 때문에 13180일 줄 알았는데 아니네)

L02. Password 구하기

따로 packing/protecting 되어있지는 않음. 근데 실행하면 바로 꺼지네?

EP 실행하다보면 00401000 메인함수를 호출한다 (인자 3개: 1, 840C38, 840CB0)

402CE0 함수 내에서 호출하는 402C80 함수에서 access violation exception 발생 (0x00000000을 읽으려했다) 
why?

* wcsrchr(dllName_str, '\') 에서 dllName_str에 0x0 값이 들어가서
how to solve?
* 반복문 4번째 돌 때 wcsrchr에서 문제가 되는 것.

반복문 한 번만 돌고 탈출하도록 JNE SHORT 부분을 그냥 JMP SHORT (다음라인)으로 패치.
(DLL 한 개만 확인한다는 문제가 있지만 이후 코드들 문제없이 잘 실행된다)


00401D80 함수를 호출하여 문자열들 출력
00402010 함수를 호출하여 user input 받아온다(엔터까지). ECX로 건네준 주소(0019F550)에 저장한다

쭉 진행하다가 CALL EDX에서 step over시 바로 프로그램 종료되길래 해당 함수 내로 step in 해보았다

 

0019F744부터 비교함수, EAX로 user input 주소 전달

(실행 중에 동적으로 변한 코드. 프로그램 시작할 땐 이 위치에 코드 없었음)

비교 구문

- EAX에 user input 문자열 옮겨와서
- 한 바이트씩 ECX에 옮겨와서 (MOVSX, 부호 확장 MOV, 43 => 00000043) 정답이랑 비교
- 순서대로 0x43, 0x52, 0x41, 0x41, 0x41, 0x43, 0x4B, 0x45, 0x44, 0x21, 0x0 => CRAAACKED!\x00

실패 시 ExitProcess
실패 시 2

비교 도중 실패하면(CMP ECX 혹은 TEST ECX,ECX 구문에서) ExitProcess 호출부로 JMP

비교 전부 성공하면 "WELL DONE!" 문자열 스택에 넣고 MessageBox 호출~

L03. Name = "CodeEngn"일 때 Serial 값

패킹 / 프로텍팅 되어있지 않음
Name / Serial 입력하고 check. 실패 시 "No, that is not the right answer :)" 메시지박스 뜬다
메인 함수에서 DialogBoxParamA를 이용해 DialogBox를 생성한다. 이 때 DialogProc은 40102C

Search for text로 찾아보니
실패 MessageBox 호출부는 DialogProc에 위치하는데
성공 MessageBox 호출부는 SEH=SE handling routine에 위치 (00401392), DialogProc에서 SEH에 401392 함수 등록

DialogProc 함수는 툭하면 호출되므로 시작 부분에 BP 걸면 분석이 힘들고
uMsg = WM_COMMAND인 case에서 (check 버튼 눌렀을 때의 코드에) 적절히 bp 세팅

GetDlgItemTextA 호출을 통해 403238에 name 저장, 리턴값(길이)>=3 아니면 fail
GetDlgItemTextA 호출을 통해 403264에 serial 저장
wsprintfA(00403284, "%u", DWORD PTR:[403000]) 통해 403284에 정수 문자열이 저장된다

00403284와 user input serial을 lstrcmp 한다
두 개가 같다면 EAX=0으로 세팅될 것이고, 그렇다면 IDIV EAX에서 Integer division by zero Exception 생긴다

=> SEH에 의해 처리. 성공 메시지박스 출력
exception 발생 안 하면 그 아래 코드 실행되고, 실패 메시지박스 출력
=> 이름 "CodeEngn"일 때 403000에는 0xC2A776FA 저장되고, 즉 serial은 3265754874


cf) IDIV = signed integer division

IDIV [divisor, 나누는 수]
// 이 때 dividend는 자연스레 EDX:EAX (sign-extended 64bit)가 된다
// 즉 IDIV 전에 CDQ instruction을 통해 dividend를 EAX에 담고 sign-extend 해야된다
// 몫은 EAX에 담기고, 나머지는 EDX에 담긴다

L04. Name = "CodeEngn"일 때 Serial 값

패킹도 안되어있고, 잘못된 serial 입력 시 "Invalid serial!" 메시지박스 출력

EP에서 바로 디코딩 루틴으로 점프. 00401006~004011A7의 각 바이트를 0x25 xor 완료한 후 0x401006으로 진입

DialogBoxParamA 호출을 통해 DialogProc=00401036으로 하는 DialogBox를 생성한다


코드가 깨져있어서 Remove Analysis 했더니 잘 나온다~
GetDlgItemTextA API를 두 번 호출해 004030C0에는 Name, 004030E0에는 Serial을 받아온다

CALL 00401114에서 serial 검증 한다
wsprintfA(00403104, "LOD-%lu-%lX", ESI, EDI)를 통해 00403104에 serial을 생성하여 저장하고
00403104(생성된 serial)과 004030E0(user input serial)을 lstrcmp 해서 성공/실패 결정

=> 이름 CodeEngn일 때 serial = LOD-59919-A0024900

L05. Serial 구하기

packing / protecting 없음
EP부터 바로 Jump to MSVBVM60.ThunRTMain 코드가 있네 ㅋㅋㅋ

Visual Basic으로 제작되어있음. MSVBVM60.dll 이라는 전용 엔진(dll) 사용함
=> VB 프로그램은 Windows 운영체제의 Event Driven 방식으로 동작하기 때문에 main 함수에 사용자 코드가 들어가는 형식이 아니다
=> Check 버튼 / OK 버튼 등 Event Handler에 사용자 코드가 존재한다

Search for - intermodular calls : 의심스러운 함수들 __vbaStrCmp
Search for - strings : 의심스러운 문자열 딱히 없음 (버튼 눌렀을 때 뜨는 문자 / Dialog에 뜨는 문자들만 발견됨)

MSVBVM60.__vbaStrCmp 호출부에 bp 걸고 달리자
arg2에 User input 주솟값(EAX) 들어가고, arg1에 실제 serial 주솟값(ECX) 들어간다
=> UNICODE "677345"

L06. 메시지박스 호출 횟수 구하기 (Conditional Log Breakpoint)

누가 이딴 프로그램을 ㅋㅋㅋㅋㅋ
확인 버튼 누르거나 닫기 버튼 누를때마다 계속 카운트 증가하며 새로운 메시지박스 뜸

UPX 패킹, autoit compiled script가 overlay로 존재

=> 언패킹안티디버깅 기법 우회하여(IsDebuggerPresent 호출부 근처) 패치함

 

cf) Autoit 실행파일은 자체적으로 안티 디버깅 기능을 가진다 (생성 시 안티 디버깅 루틴 추가)


MessageBoxW API 호출에 모두 bp

여기서 BP 걸린다. 이 부분이 우리에게 보이는 메시지박스

0x45DF07 함수 아래의 CALL USER32!MessageBoxW에서 bp가 걸린다

콜스택을 통해 거꾸로 타고 올라가니 0040BC70 함수 존재
매 카운트마다 0040BC70 함수 -> 0045DF07 함수 -> MessageBoxW 순서로 호출 이루어짐
EBP에 저장된 문자열을 메시지박스로 출력한다

 

TRY1) MessageBoxW 호출 직전의 맥락 파악

- 0040BC70 함수가 어느 조건에서 호출되는지 분석해보자(콜스택 보니 호출은 0040B21A에서 이루어짐)
- CALL 0040BC70 함수 직전의 분기들을 집중해서 살펴봅시다... 그래도 감이 안 오네

TRY2) Conditional breakpoint를 이용해 EBP 값 Logging

MessageBox API 호출부에 Conditional Log Breakpoint를 걸어둔다
- Expressions: [EBP], [EBP+4] 

// EBP에 담긴 출력 유니코드 문자열을 가져와서 Log에 남긴다

// 두 자리 넘어갈 것이므로 8바이트 정도는 가져와야 함 ([EBP], [EBP+4])
- Log value of expressions - On Condition (혹은 Always)
- Pause program - Never , Conditions - (EMPTY)

// 프로그램 멈추기 않고 로깅만

 

=> conditional bp 제외 모든 bp 제거하고 F9한다음 엔터버튼 꾹 눌러서 계속 OK 누르다보면 언젠가 멈춘다
=> 그 때의 로그값 로그창 들어가서 확인하면 [EBP] = 39003700 , [EBP+4] = 30000000
=> Little Endian 고려하면 \x00\x37\x00\x39\x00\x30 = L"790", 즉 790에서 카운트 멈춤
// conditional log bp를 이용한 노가다..

L07. Name="CodeEngn"일 때 Serial=28BF522F-A5BE61D1-XXXXXXXX 구하기

.NET 프레임워크로 개발된 프로그램 (Basic RCE L13과 동일)
=> Ollydbg나 IDA로 분석하지 못하고 전용 디컴파일러 툴(dnSpy) 이용하자

WindowsFormsApplication2 namespace 아래에 "Form1" public 클래스와 "Program" internal static 클래스 존재
Form1 클래스의 함수들을 살펴보면 button1_Click(), button2_Click() 함수들 있다

버튼 클릭 시 Name과 Serial 비교하는 루틴을 진행할 것이므로 Click action 관련 함수들을 집중적으로 살펴보자.

// WindowsFormsApplication2.Form1
// Token: 0x0600000A RID: 10 RVA: 0x0000257C File Offset: 0x0000077C
private void button1_Click(object sender, EventArgs e)
{
	string text = "";
	string text2 = "";
	string text3 = "";
	ytrewq ytrewq = new ytrewq();
	if (this.textBox1.Text.Length >= 5 && this.textBox1.Text.Length <= 27 && this.textBox2.Text.Length == 26 && this.textBox2.Text[8] == '-' && this.textBox2.Text[17] == '-')
	{
		for (int i = 0; i < 8; i++)
		{
			text += this.textBox2.Text[i];
		}
		uint num = Convert.ToUInt32(text, 16);
		for (int j = 9; j < 17; j++)
		{
			text2 += this.textBox2.Text[j];
		}
		uint num2 = Convert.ToUInt32(text2, 16);
		for (int k = 18; k < 26; k++)
		{
			text3 += this.textBox2.Text[k];
		}
		uint num3 = Convert.ToUInt32(text3, 16);
		uint num4 = ytrewq.qwerty(Form1.dfgsf(this.textBox1.Text));
		uint hashCode = (uint)this.textBox1.Text.GetHashCode();
		num3 ^= hashCode;
		this.yreee[0] = num;
		this.yreee[1] = num2;
		this.yreee[2] = num;
		this.yreee[3] = num2;
		bool flag = this.vxzzz(this.yreee, this.ewrrr, 2415796773U, num3);
		if (flag && this.yreee[2] == hashCode && this.yreee[3] == num4)
		{
			MessageBox.Show("Congratulations, mate!", "Fine!", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
		}
	}
}

button1_Click() 함수 맨 마지막에 if문 참/거짓 비교 후 MessageBox.Show(성공 문자열~) 코드 보인다

첫번째 if문 검사

- Name 길이는 5 이상 27 이하
- Serial 길이는 26, (8자리) - (8자리) - (8자리) 형태이다.

 

중간 조작

8자리 문자열을 16진수 4바이트로 해석해 num/num2/num3에 각각 저장
Name 문자열을 ytrewq.qwerty()에 넣어 조작한 값을 num4에 저장 ("CodeEngn" => 0xB4E4E0D5 [in 64bit])
Name 문자열로부터 해시코드 받아와서 hashCode에 저장 ("CodeEngn" => 0x82AD30CB)
num3 을 0x82AD30CB와 xor 해서 다시 num3에 저장

 

두번째 if문 검사

- yreee[0] = yreee[2] = num , yreee[1] = yreee[3] = num2 으로 초기화한다
- uint ewrrr[6] 의 경우 Form1 클래스가 로드될 때 특정 값으로 초기화된다 (고정값)

vxzzz(yreee, ewrrr, 0x8FFE2225, num3) 호출 결과
- boolean 값 리턴되고
- yreee의 4개의 원소 값이 조작된다

Serial = 28BF522F-A5BE61D1-XXXXXXXX 이므로 num, num2 값은 이미 결정된 상태.

 

vxzzz 내부적으로 너무 복잡.. 0x00000000 ~ 0xFFFFFFFF 브루트포스 하자
dnSpy 에서 File - Export to Project 한 후 해당 솔루션을 Visual Studio에서 열어 button1_Click 함수의 코드 수정

 

- text, text2 하드코딩, text3은 제거.

- 0x0~0xFFFFFFFF까지 반복문 돌면서 num3에 대입 후 vxzzz() 호출
- System.Diagnostics.Debug.WriteLine(x); 추가하여 brute force 도중 디버깅 로그 확인
- [디버그] - [창] - [출력] 클릭하여 디버깅 진행상황 확인
- hashCode 값 하드코딩. // 32bit일 때의 값으로

 

why consider 32bit??

- String.GetHashCode의 리턴값은 .NET Framework의 버전 또는 32bit / 64bit 에 따라 다르게 나온다

dnSpy 분석 in 64bit Windows 10

- 64bit 환경에서 dnSpy로 분석했을 때 "CodeEngn"의 hashCode는 0x82AD30CB였지만

dnSpy 분석 in 32bit Windows 7

- 32bit 환경에서 dnSpy로 분석했을 때는 0x9E1E73D5로 나온다

=> 따라서 32bit와 64bit 어디서 실행하냐에 따라 "CodeEngn"에 대응되는 시리얼 값도 달라질 것.

=> 07.exe는 x86 프로그램으로 애초에 32bit 환경에서 돌아가는 것을 가정하고 정답 정해져있음.


6분 지났는데 로그 보니까 x=500 ㅋㅋ 
택도 없어보이니까 아예 brute-force의 x 시작을 0x11E05100으로 해서 잘 되는지만 테스트하자

정답은 299913681 => 0x11E051D1

 

 

'security > 리버싱 - CodeEngn.com' 카테고리의 다른 글

Basic RCE L16~L20  (0) 2023.04.16
Basic RCE L11~L15  (0) 2023.04.13
BASIC RCE L06~L10  (0) 2023.04.11
BASIC RCE L01~L05  (0) 2023.04.09