Programming Language/Assembly

Linux System Call

D4tai1 2019. 1. 26.

1. 이론

▶ 먼저 아래 그림을 보려고 한다. (그림 그리기 힘들다 ㅠㅠ)

 

 

 

▶ 위로 갈수록 하이레벨이고 아래로 갈수로 로우레벨이다.

 

(1) 이 그림으로 하고 싶은 말은 OS별로 제공되는 [Linux에서는 system call | Windows에서는 API]은 다르다.

 

(2) 예를 쉽게보면 우리가 프로그램을 설치할 경우에...

▶ Windows 7용, Windows 10용, Ubuntu18.04용, CentOS7 용 등등 같은 프로그램이지만 OS에 따라 다른 것을 다운 받는다.

▶ 그렇다면 하나의 프로그램을 다 같이 사용하면 안될까? 라는 의문이 든다.

 

(3) 아직은? 불가능하다..

 [1] 이제 다시 그림을 보고 설명을 하려고 한다.

 [2] 커널 위에 System call은 각각의 호출되는 번호가 있다.

 [3] system call은 C언어보다 로우레벨이기 때문에 C언어의 라이브러리를 사용하는 것보다 불편함을 느끼게 된다.

 [4] system call을 직접 사용시에 파라미터 요소를 다 알고 사용해야 하기 때문이다.

 [5] 이 부분은 처음 시스템프로그래밍(커널서비스를 이용하는 프로그래밍)을 접한 사람이 느끼는 불편함이다.

 [6] 문법도 낯설고 눈으로 직관적으로 와닿지 않기 때문에...

 

(4) 자바는..?

 [1] 자바의 경우는 자바컴파일러가 .java를 .class로 컴파일하고 .class는 자바 바이트코드이다.

 [2] 바이트코드는 기계어가 아니기 때문에 OS에서 바로 실행되지 않는다.

 [3] 흔히 알고있는 자바가상머신이 OS가 바이트코드인 .class를 해석하도록 도와준다.

 [4] 그래서 자바의 경우는 OS에 종속적이지 않다.

 

(5) Linux의 printf와 Windows의 printf??

 [1] 내가 처음 C언어를 할 때는 이런 구조보다는 그냥 알고리즘 문제를 풀고 자바를 배워도 알고리즘문제를 풀고 GUI를 이용해 눈으로 보여지는 것에 관심이 있었다.

 [2] 그러기 때문에 printf함수는 어디서 써도 같은 것을 사용하는줄 알았다.

 [3] C표준 라이브러리는 해당 OS의 systemcall을 이용해서 작성되었다.

 [4] 한마디로 리눅스에서 사용하는 printf와 윈도우즈에서 사용하는 printf의 라이브러리 내부구성은 다르다는 말이다.

 [5] 결국 우리가 쓸 때는 만들어진 라이브러리를 사용하기 때문에 상관은 없다.

 [6] 그러나 라이브러리를 제작해서 사용하거나 systemcall을 직접 사용할 경우는 분명 OS의 플랫폼 영향을 받을 것이다.

 

(6) 헤더파일은 무엇이고 라이브러리는 또 무엇일까?

 [1] 헤더파일 (.h) - 사람이 읽고 해석할 수 있는 파일

  ▶ 쉽게보면 stdio.h를 쓰지 않으면 printf를 사용할 수 없는 것의 원리로 보아도 좋다.

  ▶ 한 마디로 어떤함수가 있는지 함수에 대한 정의를 적은 것과 같다고 생각해도 무방하다.

  ▶ Windows API를 하면서 헤더파일을 만들고 정의해주면서 모듈화 한 것을 생각해도 좋다.

 

 [2] 정적라이브러리(.lib) - 라이브러리는 컴파일된 바이너리(기계어) 

  ▶ 필요한 함수를 모두 가지고 있어서 실행파일만으로 독자적으로 돌아간다.

  ▶ 한 마디로 실행파일과 한 몸이라는 말이다.

  ▶ 만약 라이브러리를 수정하려면 몸을 분리할 수 없기 때문에 실행파일까지 새로 만들어서 배포해야한다.

  ▶ 또 실행파일이 여러 개 실행될 경우 메모리에 로드할 때 동일한내용이 중복해서 있기 때문에 효율적이지 못하다.

  ▶ 너무 단점만 말했는데 dll의 경우와 반대로 dll이 없거나 오류가 발생해도 문제 없이 혼자 잘 돌아간다.

 

 [3] 동적라이브러리(.lib) - dll이 아님...

  ▶ dll의 어느 주소에 어떤 함수가 있는지에 대한 정보가 들어있다.

  ▶ 정적라이브러리는 다 들고 있기 때문에 무겁지만 동적라이브러리는 다른 프로세스와 공유하기 때문에 효율적이다.

  ▶ 동적라이브러리는 실행파일에 포함되지 않고 각각의 파일로 존재(커플과 비슷)하고 실행파일이 실행시 함께 로드(놀 때는 함께)된다.

  ▶ 동적라이브러리는 dll이 아니고 dll을 만들면 함께 생기는 lib와 같다.

  ▶ 생긴 lib는 실행파일 실행 시 dll을 호출(놀기 전 놀자고 부르는)하기 위한 정보가 들어있다.

  ▶ 만약 라이브러리 내용(필요한 함수의 내용)를 수정하려면 정적라이브러리처럼 하지 않고 dll만 수정해서 배포하면 된다.

  ▶ 동적라이브러리(lib)는 dll을 부르는 내용만 들어있기 때문이다.

 

 [4] dll

  ▶ 실행파일이 자주 사용하는 함수를 따로 모아놓은 것이다.

  ▶ 내부구조가 실행파일과 거의 비슷하며 실행파일 실행 시 메모리에 로드된다.

  ▶ 실행파일은 메모리에 로드된 dll을 사용하여 공용으로 다른 실행파일과 함께 사용한다.

  ▶ 단 dll에 문제가 발생 시 실행파일도 정상적으로 돌아가지 않는다..

 

※ 조금 쉽게 이해하려면?

  ▶ 실행파일(나), dll(남자친구 혹은 여자친구), lib(메신저 혹은 전화)라고 생각하고...

  ▶ 실행파일이 실행될 때 (=내가 놀고 싶을 때)

  ▶ 동적라이브러리(lib)가 dll을 호출한다. (=전화로 남자친구 혹은 여자친구를 부른다)

  ▶ 실행파일이 메모리에 로드될 때 dll과 함께 로드된다. (=만나서 놀 때는 함께 논다)

  ▶ 프로세스가 종료되고 메모리에서 지워진다. (=다 놀고 각자 자기집 간다)

 

(7) 컴파일과 링킹

 [1] Compile

  ▶ 프로그램 내의 문법적인 오류를 검사 후 오브젝트파일인 기계어 코드로 변환한다.

  ▶ 오브젝트파일의 확장자는 통상 .obj(Windows)를 사용하거나 .o(Linux)를 사용한다.

  ▶ 헤더파일을 따라가서 사용가능한지 확인하는 것과 같다.

 

 [2] Linking

  ▶ 오브젝트파일이 실행파일이 된 후 사용해야 하는 함수(헤더파일에서 정의한 함수)를 정적 혹은 동적으로 연결한다.

  ▶ 즉, 라이브러리를 사용하려면 라이브러리의 헤더파일이 있어야 한다.

  ▶ 이유는 링커가 확인하는 심볼의 이름을 컴파일러가 만들어주고 그래야 라이브러리를 연결할 수 있기 때문이다.

  ▶ 가끔 컴파일에서는 오류가 없지만 링킹 시에 에러가 발생한다면 lib가 dll을 못 찾거나 dll에 정상적으로 구현이 안되거나 오염된 경우이다.

  ▶ 정적라이브러리는 오브젝트파일과 함께 링킹되어 하나의 실행파일이 완성된다.

  ▶ 동적라이브러리는 오브젝트파일과 dll을 호출하는 내용이 함께 링킹되어 실행파일이 실행 시 dll이 호출된다.

  ▶ 라이브러리 링크방법은 #pragma comment(lib, "[라이브러리이름].lib")를 적어주면 된다.

  ▶ Windows API로 음악재생하는 실행파일을 제작할 때 "Winmm.lib"를 사용하기도 했었다.

 

 (8) 내 컴퓨터는 64비트인데 32비트 프로그램이 돌아가던데??

  ▶ 엄연히 말하면 돌아갈 수 없다.

  ▶ 이유는 64비트와 32비트의 주소체계도 다르지만 사용하는 system call 번호도 다르기 때문이다.

  ▶ 그러나 대단한 사람들이 64비트에서 32비트를 사용할 수 있도록 하였다.

- 먼저 이 그림은 아래 로고를 보면 알겠지만 가져온 것이다.

 

 [1] 64비트환경 부팅 시 WoW64win.dll, WoW64.dll이 32비트의 ntdll.dll을 호출한다.

 [2] 이후 32비트 환경에서 필요한 kernel32.dll, user32.dll등을 가져와서 그 위에서 32비트 프로세스가 동작한다.

 [3] 만약 64비트환경에서 32비트 프로세스가 커널에 직접 엑세스한다면... 먹통이 될 가능성이 크다.

 [4] 반대로 32비트환경에서는 64비트 프로세스가 돌아갈 수 없다.

 [5] 32비트환경에서 접근할 수 있는 주소의 범위가 다르기 때문이다.

  + 어떤 기사에서 봤는데 요즘 32비트에서 돌아가는 64비트 악성코드가 있다고 했었던 것 같습니다.

  + 그 이름은 sodin이고 랜섬웨어의 한 종류입니다.

  https://www.boannews.com/media/view.asp?idx=82356

 

  + 천국의 문(Heaven’s Gate)이라고 불리는 기술을 이용했답니다.

 이 부분은 기사가 나오고 추가한 내용입니다.

 

 

※ 리눅스 시스템 콜에 대해 작성하려고 했는데 엉뚱한 곳으로 빠져서 서론만 길어졌다..

 

먼저 32비트환경에서 간단하게 사용하는 방법을 적어보려고 한다.

 

2. System Call

(1) 번호로 호출

  ▶ 위 화면은 32비트 환경에서의 리눅스 시스템 콜 번호와 사용방법을 나타낸 것이다.

 

(2) sys_write - 파일 혹은 화면에 쓰기

 [1] 사용방법

  ▶ sys_write를 사용하려고 보면 eax에 호출할 시스템 콜의 번호, ebx에 파일 디스크립터, ecx에 출력할 내용이 저장된 메모리주소, edx에 출력할 버퍼의 사이즈(길이)를 넣으면 된다.

  ▶ 용어 설명을 간략히 하면 시스템 콜은 번호로 호출한다고 위에서 설명했다.

  ▶ 파일 디스크립터는 핸드폰 단축번호와 비슷한 원리로 생각해도 좋다.

   + 예를 들어 남자친구 혹은 여자친구에게 매일 전화를 해야하는데 전화할 때마다 번호를 찍어서 전송하면 번거롭고 불편하니 단축번호 2번으로 저장을 해 놓은 것이라고 생각하면 간단하다. (100%동일하지는 않지만 이해가 안간다면 원리는 비슷하다.)

   + 그러면 다음에 전화할 때는 숫자 2만 누르면 알아서 연결이 되기 때문이다.

   + 즉, 파일 디스크립터는 작업하기 용이하도록 파일을 열어놓은 상태를 저장하고 있으며 작업 후 해제해 주어야 한다.

  ▶ c언어로 보면 char buf[100] = "d4tai1"; 과 같이 초기화된 변수를 선언했다면 buf(시작주소)가 ecx에 들어가고, 100이 edx에 들어간다고 보아도 좋다.

 

 [2] 소스(test1.asm)

  ▶ 텍스트영역과 데이터영역은 안다고 가정하고 설명하려고 한다.

  ▶ 6라인은 eax에 sys_write의 번호인 4를 넣는다.

  ▶ 7라인은 파일 디스크립터 즉 어디에 출력할 것인지를 작성한다.

  ▶ ebx에 화면(콘솔)에 출력하는 1을 넣는다.

  ▶ 8라인은 ecx에 출력할 문자열의 주소를 넣고

  ▶ 9라인은 edx에 출력할 문자열의 길이를 넣는다.

  ▶ 11라인은 시스템 콜을 호출(0x80)하기 위한 인터럽트이다.

  ▶ 17라인은 현재주소에서 msg의 시작주소를 뺀 값, 즉 msg의 길이가 저장된다.

 

 [3] 시연

  ▶ -g 는 오브젝트파일에 디버깅(gdb 등)을 위한 정보(변수명, 모듈명)등을 추가 기록하는 옵션(배포용일 경우 사용x)이다.

  ▶ -o 는 생성할오브젝트파일의 이름을 지정할 경우 사용한다.

  ▶ -f 는 파일시스템 포맷을 설정하는 옵션이다.(elf는 리눅스, pe는 윈도우즈)

  ▶ -m32 는 32비트형태로 오브젝트파일을 생성한다는 의미이다.

 

(3) sys_open - 파일열기, 닫기

 [1] 소스

  ▶ 5라인은 eax에 sys_open의 번호인 5를 넣는다.

  ▶ 6라인은 ebx에 오픈할 파일의 이름을 저장한 주소를 넣는다.

  ▶ 7라인은 ecx에 readonly를 의미하는 0을 넣는다.

   + 추가적으로 writeonly는 1, readwrite는 2, accessmode는 3을 의미한다.

  ▶ 8라인은 edx에 소유자, 소유자그룹, 일반사용자에게 부여할 권한을 8진수로 넣어준다.

  ▶ 9라인은 시스템 콜을 호출(0x80)하기 위한 인터럽트이다.

  ▶ 11라인은 시스템 콜의 반환결과인 파일디스크립터를 fd에 저장한다.

  ▶ 13라인은 eax에 sys_close의 번호인 6를 넣는다.

  ▶ 14라인은 ebx에 닫을 파일디스크립터(번호)를 넣는다.

  ▶ 15라인은 시스템 콜을 호출(0x80)하기 위한 인터럽트이다.

  ▶ 18라인은 파일이름을 저장한다.

 

 [2] 시연

  ▶ 아무런 결과가 출력되지 않는다. 당연히 파일을 읽고 출력을 하지 않았기 때문이다.

 

(4) sys_creat - 파일생성 후 열기 및 닫기

 [1] 소스

  ▶ 5라인은 eax에 sys_creat의 번호인 8를 넣는다.

  ▶ 6라인은 ebx에 생성할 파일의 이름을 저장한 주소를 넣는다.

  ▶ 7라인은 ecx에 소유자, 소유자그룹, 일반사용자에게 부여할 권한을 8진수로 넣어준다.

  ▶ 8라인은 시스템 콜을 호출(0x80)하기 위한 인터럽트이다.

  ▶ 10라인은 시스템 콜의 반환결과인 파일디스크립터를 fd에 저장한다.

  ▶ 11라인은 eax에 sys_close의 번호인 6를 넣는다.

  ▶ 12라인은 ebx에 닫을 파일디스크립터(번호)를 넣는다.

  ▶ 13라인은 시스템 콜을 호출(0x80)하기 위한 인터럽트이다.

 

 [2] 시연

  ▶ 새로운 파일이 생성된 것을 확인할 수 있다.

 

(5) sys_read, sys_write - 입력 후 읽은 내용 출력

 [1] 소스

  ▶ 6라인은 eax에 sys_read의 번호인 3를 넣는다.

  ▶ 7라인은 ebx에 읽을 파일의 핸들 값을 지정한다.

  ▶ 8라인은 ecx에 읽은 내용을 저장할 메모리 주소를 넣어준다.

  ▶ 9라인은 edx에 읽을 파일의 사이즈를 넣어준다.

  ▶ 13라인은 eax에 sys_write의 번호인 4를 넣는다.

  ▶ 14라인은 ebx에 저장할 파일의 핸들 값을 지정한다.

  ▶ 15라인은 ecx에 쓸 내용이 저장된 메모리 주소를 넣어준다.

  ▶ 16라인은 edx에 쓸 내용의 사이즈를 넣어준다.

 

 [2] 시연

  ▶ 콘솔로 입력하였고, 입력한 내용이 출력되었다.

 

(6) 파일 복사하는 프로그램

 [1] 소스

segment .text  global main main: 	; print start_msg 	; write(1[stdout], msg_addr, msg_len); 	mov eax, 4				; sys_write() 	mov ebx, 1				; standard output 	mov ecx, start_msg		; buffer address 	mov edx, start_msg_len	; buffer size 	int 0x80				; system call 호출 	 	; 읽기용으로 파일열기(open read file) 	; open(filename_addr, 0[read_only], permission) 	mov eax, 5				; sys_open() 	mov ebx, openfile		; filename address 	mov ecx, 0				; read only 	mov edx, 0777o			; permissions 	int 0x80				; system call 호출 	 	mov [sfd], eax			; handle save  	; 쓰기용으로 파일생성(creat the file) 	; creat(filename_addr, permission) 	mov eax, 8				; sys_creat() 	mov ebx, creatfile		; filename address 	mov ecx, 0644o			; permissions 	int 0x80  	mov [dfd], eax 			; handle save  	; flie copy copy_start: 	; read(file_descriptor, buffer_addr, buffer_size) 	mov eax, 3				; sys_read() 	mov ebx, [sfd]			; file descriptor[열은 파일의 핸들 값]] 	mov ecx, buffer			; buffer address 	mov edx, 0x1			; buffer size 	int 0x80  	cmp eax, 0x00 	je end_reading  	; write(file_descriptor, msg_addr, msg_len); 	mov eax, 4 	mov ebx, [dfd]			; file에 쓸 경우에는 file descriptor를 전달 	mov ecx, buffer 	mov edx ,1 	int 0x80  	jmp copy_start	  end_reading: 	; close(file_descriptor) 	mov eax, 6				; sys_close() 	mov ebx, [sfd]			; open()이나 creat()로 생성한 file_descriptor 	int 0x80  	mov eax, 6 	mov ebx, [dfd] 	int 0x80  	; print end_msg 	mov eax, 4 	mov ebx, 1 	mov ecx, end_msg 	mov edx, end_msg_len 	int 0x80  	ret  segment .data 	openfile db 'readme.txt', 0x00 	creatfile db 'write.txt', 0x00 	 	start_msg db 'start filecopy', 0x0a, 0x00 	start_msg_len equ $ - start_msg 	 	end_msg db 'end filecopy', 0x0a,  0x00 	end_msg_len equ $ - end_msg  segment .bss 	sfd resd 1 	dfd resd 1 	buffer resb 20  

 

 

 [2] 시연

  ▶ 정상적으로 파일이 복사된 것을 확인할 수 있다.

 

※ 이는 linux에서 c언어 프로그램을 작성할 때 #include<unistd.h>를 적고 사용할 수도 있다.

 

'Programming Language > Assembly' 카테고리의 다른 글

메모리와 변수  (0) 2018.07.14
레지스터  (0) 2018.07.14
어셈블리어란?  (0) 2018.07.14

댓글