물마중

[리버스_0x06] [번외] Kris의 Reverse Engineering 강의 되짚어 보기_1 본문

Reverse Engineering/Reversing 이론 설명

[리버스_0x06] [번외] Kris의 Reverse Engineering 강의 되짚어 보기_1

zweistar2 2011. 1. 18. 01:10
작성자: Dear. Tom
편집자: 엔시스
출   처: 보안인닷컴 팀 블로그[http://boanin.tistory.com]



지난 금요일부터 kris의 Reverse Engineering 강의를 듣고 있습니다.
예상대로, 고난이도의 내용과 언어의 어려움으로 모든 내용을 완벽이해하기란 정말 어려웠습니다.
그래서 그 중에서 제가 이해한 내용 위주로 간략히 정리해 보고자 합니다.
 
지금 정리한 내용은 첫날 수업의 일부분입니다. 이후의 내용도 정리되는대로 차근히 올려드리겠습니다.
 
 

1. 개요
 
- 주제: Reverse Engineering
- 강사: Kris Karspersky
- 일시: 2009년 4월 17, 18, 20, 21일
- 장소: SoftForum 빌딩 지하 1층 세미나실

* 작업환경
- System: Windows 2003
- Used Tool: Far, Hiew, Ollydbg, IDA, Soft-Ice, PETools, etc...
 
kris가 미리 준비해온 vmware 이미지를 이용하여 수업은 진행되었습니다. 특히, Linux 환경에 익숙한 탓인지 Far를 이용하여 모든 프로그램을 제어하였는데, 처음접한 이 프로그램을 통해 수업을 따라가기가 여간 어려운게 아니었습니다. kris 혼자서 온갖 단축키를 이용해서 사용하는데...정말 헷갈리더라구요..ㅠㅠ 그래도 몰라서 멍때리고 있으면, 일일이 다가와서 알려주는 친절한 크리스 덕에 그나마 따라잡을 수 있었던 것 같습니다;
그리고 정말 고맙게도 kris가 직접 쓴 책의 문서파일도 함께 첨부해주었습니다. <- 오픈 소스 정신?! ㅎㅎ
 
 
 

 
2. 강의 내용
 
* 정말 많은 내용을 배웠지만, 아래 내용은 강의의 극히 일부분만을 정리한 것입니다.
 
 
본격적인 리버싱 강의에 앞서, 간단히 Test형식으로 몇개의 문제를 풀어봤습니다.
풀이를 보시기 전에 먼저 한번 풀어보시는것을 권장합니다.
 
 
Test 1. 아래의 코드는 올바른가? 마지막 명령에서 Zero Flag는 set되는가?
 
XOR EAX, EAX
XOR EBX, EBX
DEC EBX
MOV EAX, SS
MOV SS, EAX
MOV BX, SS
MOV SS, BX
CMP EAX, EBX
 
풀이 1.
우선, 올바른 코드는 맞습니다.(단, MOV EAX, SS & MOV SS, EAX에서 레지스터 크기가 맞지 않으므로, EAX를 AX로 고쳐줍니다.) 그리고 Intel의 매뉴얼에 따르면, "when operating in 32-bit mode and moving data
between a segment register and a general-purpose register, the 32-bit IA-32 processors do not require
the use of the 16-bit operand-size prefix (a byte with the value 66H) with this instruction, but most
assemblers will insert it… when the processor executes the instruction with a 32-bit general-purpose
register, it assumes the 16 least-significant bits of the general-purpose register are the destination or
source operand the resulting value in 2 high-order bytes of the register is implementation dependent" 
이라고 합니다. 그래서 Zero Flag는 set 되지 않는다고 생각하지만, 대부분의 CPU에서는 Zero Flag가 1로 set 되어 진다고 합니다.
 
 
 
Test 2. 아래의 코드는 어떤 일을 하는가? 어디에서 쓰이며, 무엇을 위해서 쓰이는가?
 
foo()
{
static FILE *f = 0;
if (!f) f = fopen(FILE_NAME, "rw");

fclose(f);
return 0;
}
 
hint: 이것은 해킹을 어렵게 만든다.
 
 
풀이 2.
이것은 anti-uppacker 트릭입니다. 실행중인 어플리케이션의 이미지를 덤프해보면, 스태틱 변수 f가 초기화 되어있습니다. 변수 f는 0이 아닌 값으로 초기화 되어 있으므로, "f = fopen(FILE_NAME, "rw");" 부분을 그냥 넘어가 버리고, 프로그램 수행은 이전 파일 포인터값으로 이루어집니다. 윈도의 메모장과 같은 몇몇의 정당한 프로그램들 또한 같은 변수를 다시 쓰는 이러한 트릭을 쓴다고 합니다.  

anti-unpacker trick. guess, we dump an image of the running application. static variable f has been
initialized, so the value goes to the dump. ok, we try to run the dumped image. the variable f isn't zero,
thus "f = fopen(FILE_NAME, "rw");" is skipped and the program operates with the old file pointer,
which has no sense, so we get a crash. to fix the problem we have either dump the program _before_
foo() gets control (it might be almost impossible, if foo() is TLS callback), either kill "if (!f)" (it might
be almost impossible if the program has build-in integrity check mechanism). some fair programs, like
notapad.exe also use this trick to reuse the same variables (like Resource ID gets the actual handler).
 
전체 코드를 한번 살펴보면 다음과 같습니다.
 

#include <stdio.h>
#include <windows.h>

#define XXL 1024

foo()
{
 static char buf[XXL];
 static FILE *f = 0;
 if (!f) f = fopen("hello", "rw");
 if (f)
 {
  fread(buf, XXL, 1, f);
  MessageBox(0, buf, buf, 0); 
  fclose(f);
 }
  else
 {
  MessageBox(0, 0, "error", 0);
 }
 return 0;
}

main()
{
 foo();
}

 
 
 
Test 3. bar()는 cdecl 혹은 stdcall중에 어떤 방식을 쓰는가? 그리고 몇개의 인자가 필요한가? 
 
00401000 foo proc near
00401000 var_C = dword ptr -0Ch
00401000 var_8 = dword ptr -8
00401000 arg_0 = dword ptr 8
00401000 arg_4 = dword ptr 0Ch
00401000 push ebp
00401001 mov ebp, esp
00401003 sub esp, 0Ch
00401006 mov eax, [ebp+arg_4]
00401009 mov [ebp+var_8], eax
0040100C mov eax, [ebp+arg_0]
0040100F mov [ebp+var_C], eax
00401012 call bar
00401017 add eax, [ebp+var_8]
0040101A add eax, [ebp+var_C]
0040101D mov esp, ebp
0040101F pop ebp
00401020 retn
00401020 foo endp
 
hint: GCC와 친숙한가?
 
 
풀이 3.
여기에는 PUSH가 없습니다. 하지만, var_C와 var_8은 스택의 top에 위치해 있습니다.
그러므로, bar()에 의해 인자가 쓰일수도 있고, 아닐 수도 있습니다.
이것은 전형적인 GCC 스타일로 cdecl 함수에서 쓰이는 인자 전달 형식입니다.
 

@@ 여기서 잠깐! 그렇다면 cdecl, stdcall은 무엇이며, 어떻게 다른가요?

함수호출 방식에는 크게 3가지로 나뉩니다.
문제에서도 언급되었던 cdecl 및 stdcall 그리고 pascal 입니다.  

우선, 인수를 스택에 집어넣는 방향에 따라 다음과 같이 나뉩니다. 

    - pascal : 인수를 스택에 저장하는 순서를 왼쪽에서 오른쪽으로 한다.
    - cdecl : 인수를 스택에 저장하는 순서를 오른쪽에서 왼쪽으로 한다.
    - stdcall : 인수를 스택에 저장하는 순서를 오른쪽에서 왼쪽으로 한다.

 그리고 스택에 인수를 pop하는 주체에 따라 다음과 같이 나뉩니다.

    - pascal : 호출을 당하는 쪽이 스택공간을 삭제한다.
    - stdcall : 호출을 당하는 쪽이 스택공간을 삭제한다.
    - cdecl : 호출을 하는 쪽이 스택공간을 삭제한다. 
 
이렇게 stdcall은 pascal방식과 cdecl방식을 혼합한 형태를 띄웁니다.

 
 
 
Test 4. 아래의 코드는 무엇이 잘못 되었나? 이것은 exploitable 가능한가?
 
foo(char *p, int len)
{
char buf[XXL];
if (len > XXL) return -1;
memcpy(buf, p, len);

return 0;
}
 
hint: memcpy()의 프로토타입을 보자.
 

풀이 4.
 
void *memcpy(void *dest, const void *src, size_t n)
 
len은 signed int 변수로, memcpy()는 size_t 만큼 수행되는데, 이 변수는 unsigned int(적어도 x86에서는)형으로,
만약 len이 0보다 작다면, len > XXL은 거짓이 되고 memcpy()는 폭력적인 예외사항이나 DoS의 접근에 따라
80000000h..FFFFFFFFh bytes를 복사할 것입니다. 그러나, 만약 할당해지 페이지에 도달하기 전에 memcpy가 스택에 있는 SEH-record를 만나면, 이것은 SEH-record 및 SEH-handler를 덮어쓸 수 있을 것입니다. 그러므로, 이 코드는 exploitable하다고 말할 수 있습니다!
 
 
 
 
3. 후일담
 
개인적으로 kris와 식사할 기회가 있어서 여러가지 이야기를 주고 받을 수 있었는데...
한국이 굉장히 마음에 들었던지, 곧 다시 한국에 올거라고 합니다. 덧붙여, kris를 주축으로 맥아피에서 서울에 연구센터를 지을 계획도 있다는군요. 그리고 저에게 연구센터에서 일해볼 생각 없냐는 엄청난 제안을 해주시길래, 당연한 대답을 했더니, No Problem~ 문제 없다네요. ㅎㅎ 사실 이건 정말 꿈같은 이야기라 현실감이 없지만...; 세계적인 실력자에게 조금은 인정받은 것 같아 기분은 정말 좋았습니다. ^^;
 
개인적인 이야기는 각설하고,
첫날 수업은 정말 따라가기만 하는것도 벅찼습니다. T_T
처음 접하는 낯선 환경에서, 초스피드로 진행되는 정말 별천지에 다녀온 듯한 느낌이었습니다.
그래서 그랬을까요; 급기야 첫날 점심시간에 두분이 그냥 집에가시고...다음날이 되니 또 몇분이 안보이더군요. ^^;;
그래도 이런 저희의 어려움을 kris도 느꼈는지, 수업이 모두 끝나고 앞으로 어떻게 진행했으면 좋겠냐고 의견을 묻길래..준비해온 커리큘럼 그대로 따라가되, 중간중간에 휴식 및 정리할 수 있는 시간을 가졌으면 좋겠다고 얘기했습니다.
정말 kris는 초인적으로 화장실도 한번 안가고, 물도 한모금 안마시고 하루 종일 열강을 하는데...정말 신기했습니다...;
그래도 이런저런 우여곡절끝에 지금은 그나마 따라갈만 하네요. ㅎㅎ 그리고 내일도 저는 삽질하러 갑니다~! ^^