캡스톤 디자인 #5 - 기드라를 이용한 리버스 엔지니어링 책 따라가기 (Intro)

Dreamer_01·2022년 5월 16일
0

분석툴로 Ghidra를 사용하기로 했다.

그래서 '기드라를 이용한 리버스 엔지니어링'이라는 책을 천천히 따라가면서 공부해보기로 했다.

도서의 초반 부분은 컴퓨터 구조를 설명하는 내용으로, 이미 공부해본 내용이라 넘어가기로 했다.

책의 기본적인 환경은 윈도우 기반이고, vm을 통해 리눅스를 활용하는 부분도 존재한다.

하지만 기본적으로 윈도우 기반으로 내용이 진행되므로, mac이용자들은 window 환경을 마련하기를 바란다.(윈도우 없이 리버싱 하기에는 너무 힘들다. 따로 마련하는 것을 추천한다. bootcamp든 따로 desktop을 마련하는 것이든)

C언어와 어셈블리 언어

함수 호출

인수

#include <stdio.h>
#include <Windows.h>
void func(LPCSTR lpText, LPCSTR lpCaption) {
	MessageBoxA(NULL, lpText, lpCaption, MB_OK);
}

int WINAPI main(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow) {
	func("Hello", "infected!");
	return 0;
}

위의 프로그램이 책의 예제이다.
이 프로그램의 결과는 아래와 같다.

우리는 이것을 디스어셈블 해야 하는데, 일단 책의 내용을 따라가보겠다.

이 프로그램에 쓰이는 어셈블리 명령어들

데이터 전송 명령어들

  1. MOV: 명령값 복사 ex> MOV destination, source
  2. PUSH: 명령 스택에 값을 저장 ex> PUSH value
  3. POP: 명령 스택에서 값을 가져오기 ex> POP destination

기본적인 산술 연산을 하는 산술 명령

  1. ADD: 더하기 ex> ADD destination, source

논리 연산을 수행하는 논리 명령

  1. XOR: 비트별 배타적 논리합 연산 ex> XOR destination, source

지정된 메모리나 레지스터에 특정 데이터를 전송하는 명령어인 제어 전송 명령어

  1. CALL: 함수 호출 ex> CALL function
  2. RET: 함수에서 호출원으로 되돌리기 ex> RET

main함수의 디스어셈블 결과


주소 0x0040100d 에서 CALL 명령어를 사용해서 FUN_00401020을 불러온다.
이전에는 0x00401003 에서 제 1인수인 lpText, 0x00301008에서는 제 2인수인 lpCaption이 PUSH된다.
이러한 과정이 구성된 이유는 이 함수의 호출규약이 cdecl이기 때문인데, cdecl은 x86의 c/c++에서 가장 일반적인 호출규약이다. cdecl에서는 함수의 인수는 역순으로 스택이 쌓이고, 반환값은 EAX 레지스터에 저장되면서 함수 호출원을 통해 스택의 값을 POP한다.
즉, PUSH 명령어를 통해 함수의 인수를 스택에 쌓는 모습을 볼 수 있고, 그 인수들은 역순으로 스택에 쌓이는 것을 예상할 수 있다.

func함수의 디스어셈블 결과


이 함수에서는 각 인수를 일시적으로 레지스터에 대입하고(MOV 명령어 부분), PUSH 명령어를 통해서 스택에 인수를 보관한다 그후에 CALL 명령어를 통해 스택의 값을 인수로 취해서 윈도우 API인 MessageBoxA를 불러낸다.

분기

if

앞으로 쓰일 어셈블리 명령어
논리 연산 명령

  1. shl: 왼쪽으로 논리 시프트 진행 ex> SHL destination, source

산술 명령

  1. cmp: source1을 source2와 비교하여 결과에 따라서 EFLAGS 레지스터에 결과값을 세팅한다.

값을 뛰어넘어 분기를 나누는 분기명령

  1. jnz: ZF의 값이 제로가 아닌 경우에 점프 ex> JNL address
  2. jl: sf=0인 경우에 점프 ex> JL address
  3. jmp: 조건을 따지지 않고 무조건 점프를 진행한다. ex> JMP address
int main(int argc, char* argv[]) {
	int arg = atoi(argv[1]);
	if (arg == 0) {
		puts("if");

	}
	else if (arg == 1) {
		puts("elseif");
	}
	else {
		puts("else");
	}
}



첫째 if문에서는 0040101e~00401031까지이다 MOV dword ptr [EBP + local_8],EAX 에서 이전에 호출된 atoi라는 함수를 이용해서 수치로 변환된 값이 local_8에 저장이 되고, 그 값을 그 밑에 있는 cmp 명령어를 통해서 값을 비교한다.
비교후, JNZ명령어를 통해서 값이 일치하면 그대로, 다른 경우에는 LAB_00401033으로 제어가 이동한다.
제어가 이동하지 않고 유지될 경우에는 puts 함수 ㅎ출하고, jmp명령으로 함수 에필로그로 제어가 넘어가게 된다.

기드라에서는 이와 같은 분기 파일을 더 쉽게 분석하기 위해서 그래프뷰를 사용하는데, 이를 통해서 제어흐름을 쉽게 알 수 있다.

추가 어셈블리 명령어

산술 명령

sub: 빼기 ex> SUB dest, src

분기 명령

jz: ZF=1인 경우 점프 ex> JZ address
ja: 보다 위인 경우 (CF=0&ZF=0)인경우 점프 ex> JA address
jge 보다 크거나 동일한 경우 (SF = 0)인 경우 점프 ex> JGE address

0개의 댓글