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
비교 도중 실패하면(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
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 에 따라 다르게 나온다
- 64bit 환경에서 dnSpy로 분석했을 때 "CodeEngn"의 hashCode는 0x82AD30CB였지만
- 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 |