ssoL2 TISTORY

intel x86 어셈블리, 스택, 콜링 컨벤션 본문

sec/reversing

intel x86 어셈블리, 스택, 콜링 컨벤션

ssoL2 2021. 3. 3. 22:53
intel x86
  • 인텔이 개발한 cpu
  • 다양한 종류 많음 8086, 80186 등
  • 근데 x86이 제일 널리 쓰임
  • 64 bit cpu를 요즘 많이 쓰는데 그건 x64 혹은 amd64라고 불림

 

어셈블리어
  • cpu가 이해하는 기계어와 1:1로 매칭되는 저급언어 -> c, java 등 고급언어
  • cpu 아키텍처마다 어셈블리 다름 ex) x86, x64, arm, mips 등
  • 문법은 두개임 Intel, AT&T
  • 구조 => [명령 오퍼랜드1, 오퍼랜드2]

 

레지스터
  • cpu에 존재
  • eax : accumulator esgister (함수 return 값 저장)
  • ebx : base
  • ecx : count
  • edx : data register
  • esi : source index
  • edi : destination index
  • esp : stack pointer (스택 포인터)
  • ebp : base pointer (스택 프레임의 기준)
  • eip : instruction pointer (현재 실행 주소)

 

어셈블리 명령어
  • mov destination, source : source를 destination에 대입
  • lea destination, [source] : source의 주소를 destination에 대입
  • add destination source : destination += source
  • sub destination source : destination -= source
  • inc eax : 레지스터 값을 1 올린다. (increase)
  • dec ebx : 레지스터의 값을 1 내린다. (decrease)
  • xor destination, source : destination = destination XOR source
  • and destination, source : destination = destination AND source
  • or desination, source : destination = destination OR source
  • cmp destination, source : destination과 source 비교 => destination이 source보다 ~할 때 성립
  • jmp offset : offset주소로 eip 지정
  • je (jump equal) : 두 값이 같으면 jump
  • jb (jump below) : destination < source 일 때 jump (unsigned)
  • ja (jump above) : destination > source 일 때 jump (unsigned)
  • jl (jump less) : destination< source 일 때 jump (signed)
  • jg (jump greater) : destination > source 일 때 jump (signed)
  • jne (jump not equal) : 두 값이 같지 않으면 jump
  • call offest : 현재 eip를 memory에 저장 후 offset으로 jmp
  • push ebp : esp를 감소시키고 ebp를 esp가 가리키고 있는 곳에 저장
  • pop ebp : esp가 가리키고 있는 값을 ebp 레지스터로 복원
  • ret : esp가 가리키고 있는 값을 eip로 바꿈

 

memory 구조

  • Stack (스택) : 함수들이 사용할 공간으로 지역변수 위치
  • Heap (힙) : 동적할당을 위한 공간으로 malloc() 함수 할당
  • Data (데이터) : 문자열이나 정적 변수 존재
  • BSS : 전역 변수 존재
  • Text (Code) : 실제 프로그램의 코드 존재 (opcode)

 

Strack frame
  • 스택안에 프레임 단위로 공간 할당
  • 함수가 끝나면 스택 메모리에 저장해놓은 Return Address를 참조해 이전 함수로 돌아감
  • push : esp -= 4 하고 지정한 값을 스택에 저장
  • pop : esp가 가리키고 있는 값(포인터)을 지정 레지스터에 저장esp += 4
  • ret : 현재 esp가 가리키고 있는 값eip에 저장esp += 4 
  • -> pop eip와 같음
  • call : push eip; jmp offset 
  • leave : mov esp, ebp; pop ebp

 

어셈블리 분석 예시
#include <stdio.h>
int add(int x, int y);
int main()
{
        int a,b;
        a=10;
        b=20;
        printf("%d\n", add(a,b));
}
int add(int x,int y)
{
        return x+y;
}

 

1. main 함수

  • call <add> 함수 발견

2. add 함수

  • push rbp; mov rbp, rsp => 함수 프롤로그
  • pop rbp; ret => 함수 에필로그
  • 참고로 64 bit proces라서 r로 시작함. 32 bit는 e로 시작

 

  • DWORD : 4 Byte
  • 위에 어셈블리 전체적으로 한번 과정을 돌려보면 재밌음 >> 필수
  • 대충 최종적으로 아래 그림과 같음 + 레지스터 eax, ebx, ecx, edx, esp, ebp, eip 같이 사용

 

SFP
  • "Stack Frame Pointer"
  • 이전 함수의 EBP를 스택에 저장해놓음
  • 약간 스택 프레임을 구분하기 위한 ebp 저장 포인터

 

Little Endian
  • 메모리 주소에 4 Byte 씩 거꾸로 배치됨

 

Hand-lays
  • 지역 변수 할당함
  • -> ex) mov DWORD PTR [ebp-xx], yy
  • 함수 호출 전 인자 정리함
  • -> ex) push DWORD PTR [ebp-xx] , 이때 거꾸로 push됨
  • 매개변수 전달 받는 방식
  • -> ex) call 함수 안에서, mov edx, DWORD PTR[ebp+xx], 이땐 순서대로 
  • 함수 종료 전 리턴 값 지정
  • -> ex) add eax, edx , return값은 eax에 저장

 

Hand-lays 예시
(gdb) disas main
Dump of assembler code for function main:
0x080483db <+0>: push ebp
0x080483dc <+1>: mov ebp,esp
0x080483de <+3>: sub esp,0x10
0x080483e1 <+6>: mov DWORD PTR [ebp-0x8],0x0
0x080483e8 <+13>:mov DWORD PTR [ebp-0x4],0x0
0x080483ef <+20>: mov DWORD PTR [ebp-0x8],0x0
0x080483f6 <+27>: jmp 0x8048402 <main+39>
0x080483f8 <+29>: mov eax,DWORD PTR [ebp-0x8]
0x080483fb <+32>: add DWORD PTR [ebp-0x4],eax
0x080483fe <+35>: add DWORD PTR [ebp-0x8],0x1
0x08048402 <+39>:cmp DWORD PTR [ebp-0x8],0x9
0x08048406 <+43>:jle 0x80483f8 <main+29>
0x08048408 <+45>:mov eax,0x0
0x0804840d <+50>: leave
0x0804840e <+51>:ret
End of assembler dump.
(gdb)

--------------------------------------------------------------
#include <stdio.h>

int main()
{
	int a=0;
	int b=0;
	for(a=0; a<=9; a++)
	{
		b+=a;
	}
	return 0;
}
(gdb) disas main
Dump of assembler code for function main:
0x0804845b <+0>: push ebp
0x0804845c <+1>: mov ebp,esp
0x0804845e <+3>: sub esp,0x4
0x08048461 <+6>: mov DWORD PTR [ebp-0x4],0x0
0x08048468 <+13>: lea eax,[ebp-0x4]
0x0804846b <+16>: push eax
0x0804846c <+17>: push 0x8048530
0x08048471 <+22>: call 0x8048340 <__isoc99_scanf@plt>
0x08048476 <+27>: add esp,0x8
0x08048479 <+30>: mov eax,DWORD PTR [ebp-0x4]
0x0804847c <+33>: cmp eax,0x10
0x0804847f <+36>: jne 0x8048490 <main+53>
0x08048481 <+38>: push 0x8048533
0x08048486 <+43>: call 0x8048320 <puts@plt>
0x0804848b <+48>: add esp,0x4
0x0804848e <+51>: jmp 0x804849d <main+66>
0x08048490 <+53>: push 0x804853b
0x08048495 <+58>: call 0x8048320 <puts@plt>
0x0804849a <+63>: add esp,0x4
0x0804849d <+66>: mov eax,0x0
0x080484a2 <+71>: leave
0x080484a3 <+72>: ret
End of assembler dump.

(gdb) x/s 0x08048530
0x8048530: "%d"
(gdb) x/s 0x08048533
0x8048533: "Correct"
(gdb) x/s 0x0804853b
0x804853b: "Wrong"
(gdb)

----------------------------------------------------------
#include <stdio.h>
int main(void)
{
	int a=0;
	scanf("%d", &a);
	if(a==16) 
	{
		puts("Wrong")
	}
	else 
	{
		puts("Correct")
	}
}
(gdb) disas main
Dump of assembler code for function main:
0x080483db <+0>: push ebp
0x080483dc <+1>: mov ebp,esp
0x080483de <+3>: sub esp,0x10
0x080483e1 <+6>: mov DWORD PTR [ebp-0x8],0x7a69
0x080483e8 <+13>:mov DWORD PTR [ebp-0x4],0x0
0x080483ef <+20>: lea eax,[ebp-0x8]
0x080483f2 <+23>: mov DWORD PTR [ebp-0x4],eax
0x080483f5 <+26>: mov eax,DWORD PTR [ebp-0x4]
0x080483f8 <+29>: mov eax,DWORD PTR [eax]
0x080483fa <+31>: lea edx,[eax+0xa]
0x080483fd <+34>: mov eax,DWORD PTR [ebp-0x4]
0x08048400 <+37>:mov DWORD PTR [eax],edx
0x08048402 <+39>:mov eax,0x0
0x08048407 <+44>:leave
0x08048408 <+45>:ret
End of assembler dump.
(gdb)

------------------------------------------------------------
#include <stdio.h>
int main()
{
	int a=0x7a69;
	int *b=0;
	b=&a
	*b=*b+0xa	
}
(gdb) disas main
Dump of assembler code for function main:
0x080483db <+0>: push ebp
0x080483dc <+1>: mov ebp,esp
0x080483de <+3>: sub esp,0x20
0x080483e1 <+6>: mov DWORD PTR [ebp-0x14],0x7b
0x080483e8 <+13>:lea eax,[ebp-0x14]
0x080483eb <+16>:add eax,0x4
0x080483ee <+19>:mov DWORD PTR [eax],0x64636261
0x080483f4 <+25>: mov WORD PTR [eax+0x4],0x65
0x080483fa <+31>: mov eax,0x0
0x080483ff <+36>: leave
0x08048400 <+37>:ret
End of assembler dump.
(gdb)

-----------------------------------------------------------
#include <stdio.h>
#include <string.h>
typedef struct _obj
{
int a;
char b[16];
}obj;

int main()
{
obj o;
o.a = 123;
strcpy(o.b, "abcde");
}

 

<Hand-lay 느낀점>

  1. return 0 쓰든 안쓰든 마지막은 mov eax, 0x0으로 같다.
  2. for문의 방식 1) mov 첫번째인자 2) jmp 3) cmp+je 비교로 두번째인자 4) jmp 후 for문 내용 5) 세번째 인자
  3. if문같은 jmp에서 코드에 먼저 쓰여진 것부터 어셈블리도 쓰여짐. 따라서 점프가 위로하고 아래로 하고 항상 다름.
  4. 함수 call 이전에 인자 전달 방식 1) lea eax, [ebp-0x4] 2) push offset으로 string 전달
  5. 포인터랑 일반 변수 선언 똑같이 생김. 뒤에 주소 담는 것보고 분별
  6. 포인터 가리키는 법 1) lea로 주소 담고, 2) mov PTR에 담음
  7. 포인터 덧셈에서 '포인터'를 암시하기 위해 두 번 거치는 것이다. 만약 일반 변수였다면 한 번만 했겠지(나만이해ㅋ)
  8. 구조체는 일반 변수처럼 선언하는 와중에 변수 주소+0x4를 이용한다 -> 구조체
  9. 구조체 변수들의 사이즈 아는 방법은 맨 처음 사용된 일반 변수에서 사용한 사이즈만큼 뺌
  10. 구조체 char 배열에 string 저장은 할당연산자로 안되므로 'strcpy' 함수를 사용함 (이때 gdb에도 안나타남)

 

 

 

 

 

BY 센빠이

'sec > reversing' 카테고리의 다른 글

OEP  (0) 2021.03.06
디버거 이론, Ollydbg, ELF, gdb  (0) 2021.03.05
디버거, IDA  (0) 2021.03.05
Comments