GCC Asembler

2017. 12. 6. 04:500x03 Reversing Theory

728x90

코드 가상화를 진행하기 위해서는 우선 GCC Inline Assembly를 공부해야한다고 생각하고, GCC Inline Assembly를 포스팅한다.


우선 LINUX에서 GCC, 즉 GNU C 컴파일러는 AT&T/UNIX 어셈블리 문법을 사용한다.


AT&T 어셈블리어는 INTEL 어셈블리어와 문법에 있어 꽤 다르다. 


주요한 차이를 먼저 알아보자.


1. 출발지와 목적지가 반대로 되어 있다.


EX) INTEL 

OPCODE dst src


EX) AT&T

OPCODE src, dst


2. 레지스터 표기법이 다르다.


레지스터는 %를 붙인다.


EX) %EAX, %EBX


3. Operand에 $를 붙인다. 16진수를 0x로 표기한다.


INTEL : int 80h


AT&T : int $0x80 


4. Operand의 사이즈 

AT&T 구문에서 메모리 피연산자의 크기는 연산코드 이름의 마지막 문자로 부터 결정된다. 

예를 들어 opcode 의 접미사가 b,w,l 이라고 하자.

b = byte(8bit)

w = word(16bit)

l  = long (32bit)


하지만, INTEL은 byte ptr , word ptr, dword ptr로 표현한다. 


5. 메모리 피연산자

INTEL : [ ]

AT&T : ( )


INTEL 의 section : [base + index*scale + disp] 


AT&T 의 section : (base, index, scale) 


상수 값에는 $를 붙이지 않는다.


완전 기초중의 기초 AT&T Inline Assembly 


asm("movl %ecx, %eax");  /* ecx의 값을 eax에 복사 (4byte) */


__asm__("movb %bh, (%eax) "); /* b의 값을 eax에 복사 (1byte) */


asm 으로 써도 되고 __asm__으로 써도 된다.


만약 프로그램 내에 asm 이라는 키워드가 존재하게 되면 이는 충돌을 할 수 있기 때문에 가능한 __asm__으로 작성하는 것을 습관화 하자.


더블쿼터(")나 \n, \t를 사용할 때는 다음과 같이 사용하면 된다.


__asm__ ("movl %eax, %ebx\n\t"

  "movl $56, %esi\n\t"

  "movl %ecx, $label("%edx, %ebx, $4)\n\t"

  "movb %ah, (%ebx)" ); 


작성한 코드에서 일부 레지스터를 변경하는 경우 그 변경 사항을 수정하지 않고 ASM에서 복귀하게 되면 잘못 된 결과를 도출할 수도 있다. 

GCC가 레지스터 내용의 변경에 대해 알지 못하기 때문이다.

특히 컴파일러가 최적화를 수행 할 때 문제가 발생하게 된다. 


확장된 asm에서는 피연산자를 지정할 수도 있다. 

이를 통해 입력 레지스터, 출력 레지스터, clobbered 레지스터 목록을 지정할 수가 있다. 


asm ( assembler template

: output operands

: input operands

: list of clobbered registers

);


어셈블러 템플릿은 어셈블리 명령어로 구성된다.

각 피연산자는 피연산자 제약 문자열과 괄호 안의 C 표현식으로 설명된다.

콜론은 어셈블러 템플릿을 첫 번째 출력 피연산자와 분리하고 

다른 콜론은 첫 번째 입력에서 마지막 출력 피연산자를 구분한다.

쉼표는 각 그룹 내의 피연산자를 구분한다.

피연산자의 총 수는 기계 설명의 명령어 패턴에서 최대 10개 또는 최대 피연산자 수 중 큰 값으로 제한된다. 

만약, 출력 피연산자가 없지만 입력 피연산자가 있는 경우 출력 피연산자가 있는 곳을 둘러싼 두 개의 연속 된 콜론을 배치할 수 있다.


asm ("cld\n\t"

"rep\n\t"

"stosl"

: /* no output registers */

: "c" (count), "a" (fill_value), "D" (dest)

: "%ecx", "%edi"

);