1 분 소요

C 문법과 디스어셈블링

함수의 기본 구조

리버싱에서 가장 골치 아픈 것은 컴파일러가 자동으로 삽입하는 코드를 필터링하는 것이라고 한다. 따라서 이러한 코드를 걸러내는 능력이 필요하다. push ebp, pop ebp 이 이것이 함수의 처음과 끝이다.

push ebp

mov ebp, esp

ebp는 스택 베이스 포인터이다. 위의 코드를 보면 지금까지의 베이스 주소를 스택에 보관한 후 현재의 스택 포인터인 esp를 ebp로 바꾼다. 이는 기준이 될 스택 베이스 포인터를 백업하고 새로운 포인터를 잡는 것이다.

함수의 시작은 곧 새로운 스택을 사용한다고 생각할 수 있다.

종료 코드는 역으로 esp를 현재 베이스 주소를 넣어주고 ebp를 스택에서 복원한게 된다.

함수의 호출 규약

함수의 역할을 파악하는 것은 리버싱에서 상당히 중요하다. 이를 위한 선행 작업으로 함수가 어떻게 생겼고 인자는 몇 개인지 등의 정보를 파악해야 하는데 이는 함수 호출 규약을 아는 것이 중요하다. 함수의 호출 규약으로는 대표적으로 __cdecl, __stdcall, __fastcall, __thiscall 4가지가 있다. 여기서 확인할 것은 코드를 보고 어떤 calling convention에 해당하는지 파악하는 것이다.

call문을 보고 이 함수의 인자가 몇 개이고 어떤 용도로 쓰이는지를 분석하기 위함이다.

  1. __cdecl

    함수 밖에서 스택을 보정한다. 함수를 호출한 다음 줄에 add esp, 8과 같이 스택을 보정하는 코드가 등장한다. 그리고 호출 직전에 push 문을 통해 파라미터의 갯수를 확인할 수 있다. 그리고 함수 내부에서 마지막에 eax에 숫자가 들어가는지 주소가 들어가는 지에 따라 리턴 값을 알 수 있다.

  2. __stdcall

    함수 안에서 스택을 처리한다. 스택 보정과 파라미터 갯수 판단은 함수 내부에서 확인해야 한다. 리턴문에 그냥 retn이 아닌 retn 숫자를 확인할 수 있다. 이는 함수 종료시 스택을 보정한다는 뜻이다. 만약 add esp, 8과 같은 코드도 없고 retn에도 숫자가 나오지 않는다면 이는 파라미터가 없는 경우라고 볼 수 있다.

  3. __fastcall

    함수의 파라미터가 2개 이하일 경우 인자를 스택에 넣지 않고 ecx와 edx 레지스터를 이용한다. 함수 호출 전에 edx와 ecx 레지스터에 값을 넣는 것을 확인할 수 있다.

  4. __thiscall

    현재 객체의 포인터를 ecx에 전달한다. 인자 전달 방법이나 스택 처리 방법은 __stdcall과 동일하다.

조건문

메모리의 값을 직접 연산에 사용할 수 없기 때문에 레지스터로 옮겨온 후 cmp연산을 한 후 jnz와 같은 조건부 점프 명령어를 활용하는 것을 확인할 수 있다. 궁극적으로 jnz, jz 등을 처리하기 위한 코드가 대부분이며 변수의 처리를 위해 레지스터를 사용한다.

반복문

조건문과 마찬가지로 cmp, jg 등 조건부로 점프하는 연산이 등장하는 데 차이점은 특정 값을 지속적으로 더하거나 빼는 연산을 수행한다는 것이다. 이는 실제 Counter를 다루는 부분이 되겠다. 다시 위 코드로 올라가는 경우, 그리고 그 위치에 해당하는 코드가 적당한 값을 더하거나 빼면서 어떤 특정한 값과 cmp한다면 반복문이라 봐도 좋다.

결론

변환된 부분을 그냥 눈으로 훑지말고 레지스터 변수 하나라도 가벼이 여기지 않는다는 자세로 스택 처리도 바이트 단위까지 샅샅히 조사해야 한다.

댓글남기기