본문 바로가기
SSM

Apr 24, 2011 - Windows System Programming Expert Seminar(1)

by nickeys 2011. 4. 25.
1. exe format
exe file(PE file format을 따르는 파일)은 header와 section들로 구성이 된다. 그리고 그 구조는 다음과 같다.



모든 exe file의 header에는 자신이 올라갈 주소(Image Base, 주로 400,000번지로 지정이 된다.)와 Entry(맨 처음 기계어 코드가 시작되는 offset 즉, .text 섹션 까지의 offset)가 있다. 또한 .text 섹션은 보통 200byte 단위로 정렬 되므로 그 보다 적은 용량이 필요할 경우에도 200byte 단위로 메모리가 잡힌다. 따라서 그 남는 공간에 필요한 코드를 심는 해킹 기법이 존재한다.

2. The mechanism of calling function
# C, C++은 함수의 인자 전달 시 마지막 인자부터 스택(stack)으로 전달 된다. 왜냐면, 레지스터를 쓰게 되면 인자 개수의 제한이 있기 때문.
# 리턴 값의 경우는 단 1개 뿐이므로 레지스터(EAX)를 이용한다.
# C, C++에서는 직접 레지스터에 접근이 안된다. 따라서 inline assembly를 사용한다.

# cl.exe 는 ms c, c++ compiler다. e.g> cl main.c /c(링크 제외 컴파일만) e.g2> link main.obj 1.obj
# NASM은 오픈소스 어셈블러. e.g> nasm -f win32 -o 1.obj 1.asm(-o옵션은 output 관련, 1.asm을 1.obj로 출력. -f 옵션은 타겟 시스템의 포맷)
# VC는 컴파일 할 때 함수 명 앞에 '_'를 추가함. e.g> foo => _foo
# cl sample.c /FAs : 이 c file의 어셈 파일을 만들게 함.

# .data 세그먼트에 변수 선언 시, '변수명 사이즈 초기값' 형태로 선언 한다. 사이즈는 DD, DW, DB(double word, word, byte)와 같이 선언 가능하다.
# global keyword는 다른 파일에 해당 함수를 노출 시킨다.
# 어셈의 모든 포인터는 void 포인터이다. 또한, C, C++의 역참조 연산자는 '변수크기[변수명]' 형태로 가능하다. e.g> dword[L1] e.g2> word[L2]

# 함수 호출
a. jmp를 사용해서 호출 -> 돌아올 주소를 알려 주어야 한다(레지스터를 이용하거나, 스택에 넣어서 알려준다. 후자가 C 표준 방법이다).
e.g > _asm_main:
push next
jump foo
next:
ret
foo:
move eax, 200
pop ebx
jmp ebx
b. call / ret 사용 -> jmp의 stack 버전을 명령어로 만든 버전이다.
e.g> _asm_main:
call foo
foo:
move eax, 200
ret

# 함수 인자 전달
a. 레지스터로 전달 : 빠르지만, 인자 갯수의 제한이 있다.
b. 스택을 통한 전달 : 인자 전달용 스택을 파괴해야 함.
ㄱ. 호출자가 파괴 : 표준 C 함수의 방식
ㄴ. 호출 당한 함수가 파괴 : 코드가 줄어 듦으로 메모리 사용이 준다. 모든 WIn32 API는 이 방식이다. __stdcall, WINAPI등의 키워드가 필수적임.
예를 들어 foo(1, 2)와 같이 함수를 호출 했다면 다음과 같은 모양으로 스택이 구성된다.

어셈블리어로 표현 하면 다음과 같다.
_asm_main:
push 2
push 1
call foo
add esp, 8 ; 인자가 모두 정수이므로 각각 4bytes씩 총 8bytes를 먹으므로
ret
foo:
move eax, dword[esp+8] ; 복귀주소의 다음이 2 번째 인자 이므로, 첫 번째 인자를 나타낸다.
add eax, dword[esp+4] ; 두 번째 인자를 더해서 반환.
ret
위의 코드에서는 호출한 측에서 스택의 인자 전달을 위한 부분을 파괴하였다. esp는 현재 스택의 top을 가리키고 있다.

#지역 변수의 사용
_asm_main:
push 2
push 1
call foo
add esp, 8 ; 인자가 모두 정수이므로 각각 4bytes씩 총 8bytes를 먹으므로
ret
foo:
push ebp ; 함수 호출 직후 ebp는 esp와 같은 값을 가지지만 이로써 esp는 ebp보다 4bytes 아래를 가리킨다.
mov ebp, esp ; 스택을 쓸 때 계속해서 변하는 esp와 달리 ebp는 고정된 값이므로 이를 이용해 함수 내 변수를 가리킨다.
sub esp, 8; 총 8bytes의 공간을 추가로 할당(지역변수로 씀)
mov dword[ebp-4], 0 ; x = 0
mov dword[ebp-8], 10 ; y = 10

mov eax, dword[ebp+8] ; 첫 번째 인자
add eax, dword[ebp+12] ; 첫 번째 인자 + 두 번째 인자 값을 반환

mov esp, ebp ; 지역 변수 해제
pop ebp ; ebp의 예전값 복구
ret

위 코드에서 지역변수를 해제 하기 전까지의 상황은 다음과 같다.


# 문자열 상수 표현
sement .data
L1 DB "Hello" 10 0 ; "Hello" + '\n' + '\0'

# Calling convention

C 표준은 __cdecl이고 WinAPI 함수들은 __stdcall 방식이다. 또한, 외부 함수를 어셈 코드 상에서 호출 하기 위해서는
'extern 함수명'으로 외부 함수를 명시적으로 선언 해줘야 한다.
e.g> extern _printf // printf는 c기본 함수이므로 __cdecl
extern _MessageBoxA@16 // Win32API의 함수들은 __stdcall

+@ C 코드를 컴파일 해보기
                               //함수의 프롤로그(prolog)
int Add(int a, int  b)   //push  ebp
{                                //mov ebp, esp
                                //push ecx   //sub esp,4
   
    int c = 0;                //mov dword[ebp-4], 0
   
                                //mov 며영은 양쪽 오퍼랜드가 메모리일수 없다. 레지스터 사용.   
    c = a + b;                //mov eax, dword[ebp+8]    //mov dword[ebp-4], dword[ebp+8]       
                                //add eax, dword[ebp+12]   //add dword[ebp-4], dword[ebp+12]
                                //mov dword[ebp-4],eax

    return c;                    // mov eax, dword[ebp-4]

                                //mov esp, ebp -> 함수의 에필로그 라고 합니다.

                                //pop ebp
                                //ret
}

int main() {
    int n = Add(1,2);  // push 2
                            // push 1
                            // call _Add
                            //add  esp,8
                            //mov    dword[ebp-4], eax
}


'SSM' 카테고리의 다른 글

Apr 24, 2011 - Windows System Programming Expert Seminar(2)  (0) 2011.04.25
Sep 8, 2010 Naver DeView  (0) 2010.09.12
My name tags~  (0) 2010.08.19
2달 반 삽질의 결과 ㅋ  (0) 2010.07.29