1. 리버싱 기초(2)

악성코드 분석

2018. 8. 25. 18:59

반응형

01 함수의 기본 구조

- 간단한 sum 함수의 구조
1
2
3
4
5
int sum(int a, int b)
{
    int c = a + b;
    return c;
}
cs

↓   ↓    ↓    



(retn이 이상한건 기분 탓)


push ebp를 해주면 main 을 가르키던 main ebp를 retn 위로 쌓아주고.

mov ebp를 해주면 그 위에 새로운 ebp가 생기면서 새로운 스택 프레임이 생깁니다.

(esp는 계속해서 따라 올라갑니다.)


[ebp+arg_0], [ebp+arg_4]는 하나의 심볼이다.

얘들의 의미는

[ebp+arg_0] = ebp로 부터 4바이트 떨어진 곳 (ebp + 8) (= a)

[ebp+arg_4] = ebp로 부터 8바이트 떨어진 곳 (ebp + 12) (= b)


a가 3, b가 4라고 하면

mov eax에서 eax가 3이 되고,

add eax에서 eax가 4와 더해져서 7이 된다.

mov [ebp+var_4], eax는 eax에 있던 7을 var_4에 옮겨준뒤,

var_4에 있는걸 다시 mov eax에서 eax로 7이라는 숫자를 저장해줍니다.


2번 연산을 하는 이유는 중간에 지역변수에 저장했기 때문입니다.


mov esp, ebp

pop ebp

retn


이 부분은 main 함수로 돌아오면 이때까지 쌓은 스택들은 필요가 없기 때문에

삭제를 해줘야 합니다. mov esp, ebp로 ebp와 esp를 돌려주고,

pop을 해주면 스택 데이터를 ebp에 넣어줍니다.


이 부분을 함수 호출 규약이라고 합니다.


02 함수 호출 규약 - 1) cdecl

- 항상 call 문의 다음 줄을 살펴서 스택을 정리하는 곳을 체크한다.
- cdecl 코드는 call 이후 add esp, 8과 같이 스택을 보정
- add esp, 8 그리고 push 문이 2개
- 4바이트 파라미터가 두 개임을 추측할 수 있음

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
31
// C 코드
int __cdecl sum(int a, int b)
{
    int c = a + b;
    return c;
}
 
int main(int argc, char* argv[])
{
    sum(1,2);
    return 0;
}
 
// 어셈블리 코드
sum:
push ebp
mov ebp, esp
push ecx
mov eax, [ebp+arg_0]
add eax, [ebp+arg_4]
mov [ebp+var_4], eax
mov eax, [ebp+var_4]
mov esp, ebp
pop ebp
retn
 
main:
push 2
push 1
call calling.00401000
add esp, 8
cs


28번째 코드 ~ 31번째 코드를 보시면

call 함수에서 두개의 파라미터( 28, 29번째 코드)을 가져다가 사용하며,

cdecl 규약을 사용해서 마지막에 스택을 정리한다. 라고 해석하면 된다.


- 02 함수의 호출 규약 - 2) stdcall [1/2]

- main 함수 내부에서 스택을 처리하지 않음
- stdcall 코드는 retn 8을 사용하여 스택을 처리
- Win32 API는 stdcall 방식을 사용


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// C 코드
int __stdcall sum(int a, int b)
{
    int c = a + b;
    return c;
}
 
// 어셈블리 코드
sum:
push ebp
mov ebp, esp
push ecx
mov eax, [ebp+arg_0]
add eax, [ebp+arg_4]
mov [ebp+var_4], eax
mov eax, [ebp+var_4]
mov esp, ebp
pop ebp
retn 8
 
main:
push 2
push 1
call calling.00401000
cs


19번째 코드의 retn 8은

"내가 파라미터를 총 8바이트 만큼 사용했어"라는 뜻입니다.

retn 8을 하는 순간에 밑에있는 스택 8바이트가 삭제됩니다.

그래서 call 밑에는 스택을 정리하는 코드가 없습니다.



- 02 함수 호출 규약 - 3) fastcall [2/2]

- 파라미터가 2개 이하일 경우, 인자를 push로 넣지 않고 ecx와 edx 레지스터를 이용
- 레지스터를 이용하기에 메모리를 이용하는 것보다 훨씬 빠름
- 함수 호출 전, edx와 ecx 레지스터에 값을 넣는 것을 보면 fastcall 규약 함수임을 짐작할 수 있음
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
31
// C 코드
int __fastcall sum(int a, int b)
{
    int c = a + b;
    return c;
}
 
// 어셈블리 코드
sum:
push ebp
mov ebp, esp
sub esp, 0Ch
mov [ebp+var_C], edx
mov [ebp+var_8], ecx
mov eax, [ebp+var_8]
add eax, [ebp+var_C]
mov [ebp+var_4], eax
mov eax, [ebp+var_4]
mov esp, ebp
pop ebp
retn
 
main:
push ebp
mov ebp, esp
mov edx, 2
mov ecx, 1
call calling.00401000
xor eax, eax
pop dbp
retn
cs


- 2. 함수의 호출 규약 - 4) thiscall [2/2]

- 주로 C++의 클래스에서 this 포인터로 이용
- 현재 객체의 포인터에 ecx에 전달하는 특징을 가짐
- 다음과 같이 ecx 포인터에 오프셋 번지를 더함

 

파라미터

주소

a

ecx+x

b

ecx+y

c

ecx+z


1
2
3
4
5
6
7
8
9
10
11
12
13
14
// C++ 코드
Class CTemp
{
public:
    int MemberFunc(int a, int b);
};
 
// 어셈블리 코드
mov eax, dword ptr [ebp-14h]
push eax
mov edx, dword ptr [ebp-10h]
push edx
lea ecx, [ebp-4]
call 402000
cs


5번째 코드가 ecx에 어떤 값을 전달하는 부분입니다.

하나의 변수만으로 다양한 데이터를 넘길 수 있습니다.



반응형