저번에 Hello World를 출력하는 코드가 프로그램으로 번역되는
컴파일 과정을 살펴보았다.
오늘은 번역된 결과인 어셈블 언어를 공부를 시작해볼 것이다.
저번에 만들었던 코드를 조금 수정해서 printf에 넘겨주는 함수 인자를 2개로 만들어보았다.
(함수 인자 2개)
(출력 모습)
프로그램이 동작하는 결과는 같다.
다만 이 프로그램을 어셈블언어로 살펴보면
(인자 전달)
PUSH 두개가 보이고 그 후에 call이 나온다.
먼저 간단히 설명하자면 PUSH로 스택에 전달할 인자 2개를 넣어 둔 후
함수를 호출하면서 함수에 인자를 전달한다. 바로 이게 함수에 인자를 전달하는 방법 중 하나이다.
* 함수에 인자를 전달하는 방법
1. 스택 메모리를 통한 전달
이 방법을 통해 어셈블언어로 직접 구현해보겠다.
(어셈블언어)
여기서 extern은 C언어에서도 사용하는 키워드로 외부의 함수를 이용하기 위해 사용하는 것이고
우리는 printf 를 사용하기 위해 extern으로 알려주었다.
그 다음으로 segment로 data 영역과 text 영역이 보인다. text 영역에는 코드를 작성하고 data 영역에는 우리가 사용할 문자열을 저장해두었다.
함수에 전달하는 값은 순서가 반대로 입력이 된다. 스택구조는 제일 먼저 들어온 것이 가장 나중에 나오는 자료구조의 특징때문이다.
이 어셈블 코드를 목적파일로 만들고 링크작업까지 끝내고 objdump로 살펴보면 다음과 같이 확인할 수 있다.
(objdump 모습)
우리가 입력해준 데이터 msg1, msg2의 값의 주소값으로 들어간 것을 확인 할 수 있다.
여기서 msg1, 2 는 우리가 레이블이라고 표현하는데 주소값을 컴파일 전에는 알 수 없으므로 네이밍 해준 것이다.
이번에는 printf에 인자를 3개 줘보겠다. 먼저 C코드에서의 모습이다.
(C코드 모습)
이러한 코드를 어셈블언어로 작성하면?
(어셈블언어)
위와 같다. 스택구조로써 PUSH 하는 순서는 반대로 넣어주고 함수를 call 하면 된다.
실행 화면을 확인해보면 다음과 같다.
(실행 화면)
실행 화면을 보면 기존의 출력과 똑같이 나오는 것을 확인 할 수 있다.
또 objdump로 다시 살펴보면
(objdump)
3개의 인자로 넘겨주려고 했던 것들이 PUSH 되고 함수가 call 되는 것을 확인 할 수 있다.
2. 레지스터를 통한 인자 전달
두번째 방법으로는 레지스터를 통해 함수에 인자를 전달 할 수 있다. ( 사실 어셈블언어에서 함수라는 개념은 없지만 지금은 이해하기 쉽게 함수라 표현하였다.)
* 레지스터 - CPU가 사용하는 고속의 기억장치
1. 범용 레지스터( general register ) (순서대로 이용한다.)
1). EAX( Extended Accumulator Register )
2). EBX( Extended Base Register )
3). ECX( Extended Counter Register ) : 반복관련
4). EDX( Extended Data Register ) : 보조적인 데이터 저장
-> 사실 이 용도로만 쓰지는 않는다. 범용이라 아무대서나 쓰이기도한다.
레지스터로 함수에 인자를 전달해보겠다. 우선 printf는 스택메모리를 통해 인자를 전달하도록 정해져있기 때문에 우리가 이 내용을 확인하려면 시스템 콜을 알아야한다.
* 시스템 콜 ( System Call )
-> 커널에 접근할 수 있는 인터페이스이다.
- 클래스에서 멤버에 접근하기 위한 메서드를 공개해놓은것과 비슷한 개념이다.
- 즉, 커널 자원에 접근하기 위한 함수라고 생각하면된다.
레드햇 6.2버젼에서는 /usr/src/linux-2.2.14/include/asm-i386/unistd.h 위치에 시스템콜이 정리되어있다.
(시스템콜)
약 200여가지 시스템 콜을 사용할 수 있다.
사용하는데 방법을 알아보기 위해서는
# man 2 함수이름
이런식으로 메뉴얼을 볼수 있다. 우리는 화면에 출력할 write를 살펴보겠다.
(write 시스템콜)
ssize_t write(int fd, const void *buf, size_t count);
3개의 인자가 필요하다.
정리하면 buf에 있는 내용을 count만큼 fd로 전달한다.
이 내용을 C코드로 작성해보면 다음과 같다.
(c코드)
fd 는 표준 출력인 1로 주었고 문자열을 buf에 입력하고 문자열 수 14를 count에 입력하였다.
프로그램 실행 결과는 다음과 같다.
(실행 결과)
그렇다면 이러한 시스템콜이 printf에도 쓰이지 않았을까?
-> 그렇다! 우리가 사용했던 C코드로 만든 프로그램이 사용한 시스템콜을 확인해보면 된다.
strace -> 해당 프로그램이 사용하는 시스템콜 목록을 확인할 수 있다.
ltrace -> 사용하는 라이브러리를 보여준다.
strace를 이용하여 c코드로 짠 프로그램을 실행시켜보면
(strace)
printf 가 사용된 프로그램에 write 시스템콜이 사용된 것을 확인할 수 있다.
지금까지 정리한 내용을 토대로 Hello, World!를 출력하는 프로그램을 어셈블 언어를 이용하여 작성해보겠다.
(어셈블 코드)
레지스트 eax에는 시스템콜 번호를 써준다. write의 시스템콜번호는 4번이므로 4를 입력해주었다.
그 다음으로는 인자를 차례로 적어주면된다.
ebx에는 fd의 값
ecx에는 버퍼에 담긴 내용 (여기서는 레이블을 이용해 주소값을 전달한다.)
edx에는 크기
그리고 마지막에 int는 정수형이 아니라 인터럽트를 뜻한다.
int 0x80은 시스템콜 인터럽트를 의미한다.
int 0x80 인터럽트가 걸리면 바로 eax를 참조하고 eax에 적힌 시스템콜번호를 확인하여 시스템콜을 수행하게 된다.
자! 컴파일하여 실행해보자!
(컴파일)
(실행)
잘 실행되는 것을 확인할 수 있다.
여기서 마지막에 Segementation 어쩌구 오류가 나오는데
우리가 프롤로그, 에필로그를 써주지 않아서 그렇다. 아직 우리가 공부하지 않았지만 시스템콜 중 exit를 이용하면 이 에러메세지가 안뜨게 종료시킬 수 있다.
그렇다면 exit를 추가해보자!
(exit(0) 추가)
이것도 똑같은 시스템 콜이므로
eax에 시스템 콜번호
그 다음 0으로 인자를 줄것이므로 ebx에는 0 그리고
int 0x80 으로 시스템콜 인터럽트를 걸면 된다.
(실행모습)
그렇게 되면 위와같이 실행이 정상적으로 종료된다.
'Hacking > System Hacking' 카테고리의 다른 글
System Hacking - 어셈블리어(사칙연산) (0) | 2017.04.14 |
---|---|
System Hacking - 데이터 저장(메모리이용) (0) | 2017.04.13 |
System Hacking - 레지스터 (0) | 2017.04.12 |
System Hacking - Compile (0) | 2017.04.08 |
System Hacking - 실습환경 구성(Red Hat) (0) | 2017.04.07 |