main 함수의 인자 / 시스템 프로그래밍( 시스템 콜 )에 대해서 정리한다.
1. main 함수의 인자
#> ./a.out hello
hello
* 위 내용은 hello라는 인자를 받아서 그 인자의 내용인 hello를 출력하는 프로그램이다.
- 포인터 배열 사용
- 포인터 배열: 배열인데 주소를 원소로 하는
- 배열 포인터: 포인터인데 배열을 나타내는
c 표준에 정의되어있는 main 함수 원형 2가지
int main( int argc, char *argv[] );
int maint();
* main 함수를 사용할때 인자를 생략할 수 있지만 생략해도 int argc, char *argv[] 인자는 존재한다.
( 다만 인자를 사용하려면 명시해줘야 한다 )
#> ./a.out hello 프로그램 코드
hello
int main( int argc, char *argv[] );
{
printf("%s \n", argc[1]);
return 0;
}
* int argc 인자의 경우 argument의 갯수를 나타낸다.
( int argc 인자의 경우 어셈블리에서 가져오려면 [ebp+8]을 하게되면 주소를 가져올 수 있다 )
* 실행 프로그램의 이름도 argument로 친다. ( 첫번째 인자 )
* char *argv[]의 시작 주소를 가져올때는 [ebp+12] 위치에서 가져온다.
( 가변 배열이기 때문에 인자가 들어가는 경우에 주소는 변할 수 있다 )
char *argv[] -> 0xbffffbc4 // [ebp+12]
0xbfffffbc4 0xbfffffbc8 0xbfffffbcc
[0xbfffffcb9] [0x0000000 ] [ ] ...
* 위 상황에서 두번째 인자가 존재한다면 아래와 같이 두번째 메모리값이 채워지고 그다음 메모리가
널(0x00)로 채워지게 되는 상황이 될 것이다. ( 아래와 박스와 같은 상황이 된다 )
0xbfffffbc4 0xbfffffbc8 0xbfffffbcc
[0xbfffffcb9] [0x???????? ] [0x00000000] ...
( main 함수의 첫번째 인자인 int argc의 경우 함수가 실행되고 [ebp+8] 주소에 위치하기 때문에
어셈블리 코드로 해당 인자의 주소에 접근해 메모리 값을 가져와서 출력 해봤다.. )
( 실행 프로그램의 이름도 인자로 치기 때문에 argc의 값이 1이 나오게 된다 )
( 인자의 갯수가 늘어날 때마다 argc의 출력 값 또한 늘어나게 된다 )
( char *argv[]는 배열 포인터로 인자들의 주소를 가지고 있다..
첫번째 인자의 경우 [ebp+12]에 위치하고 있기 때문에 해당 주소에
접근하게 되면 실행 프로그램의 이름을 가져올 수 있다. )
( 첫번째 인자는 실행 프로그램의 이름이기 때문에 ./arg가 출력 결과로 나오는걸 볼 수 있다. )
[실습]
#> ./a.out hello 프로그램 어셈블리로 작성
- 출력 결과: hello
0xbfffffbc4: ./arg
0x???????? : hello
0xbfffffbc4 0xbfffffbc8 0xbfffffbcc
[0xbfffffcb9] [0x???????? ] [0x0000000] ...
* a.out 프로그램에 hello라는 두번째 인자를 주게 되면 위와 같이 두번째 인자는 메모리값으로 채워지고 3번째 인자가
0x00000000 상태로 널값이 채워지게 된다.
( 가변 배열이기 때문에 인자 입력의 끝을 알리기 위해 다음 인자의 메모리값을 널값으로 채운다 )
( 실행 프로그램의 이름이 첫번째 인자이고
프로그램 실행시 넘겨주는 인자는 두번째 인자이기 때문에 [ebp+16]의 주소에 접근해야
두번째 인자에 주소에 접근해 해당 메모리 값을 가져올 수 있다. )
시스템 프로그래밍
- 리눅스: 시스템 콜
- 윈도우즈: WIN API
!! shell을 이용해서 하드웨어에 접근하려면 항상 커널을 통해서 접근해야 한다.
* 시스템 콜은 C언어와 별개로 함수 형태로 제공된다.
* 커널에 자원을 사용할수 있는 명령어
* 시스템 콜은 번호를 이용해서 관리된다 ( 예를 들어서 write systemcall의 경우에는 4 )
!! printf의 경우에도 결과적으로는내부적으로 write systemcall을 호출해서 출력하고 있다.
* 어셈블리에서는 eax에 시스템 콜 번호를 넣어주고 첫번째 인자부터 ebx, ecx, edx에 넣어주고 3개가 넘을시에는
stack을 이용한다.
* int(인터럽트) 명령어를 이용해서 시스템 콜을 호출한다. ( 80h에서 80은 0x80 )
( call 명령을 이용해서는 시스템 콜을 할수 없다 )
리눅스 시스템 콜 참조 사이트
https://syscalls.kernelgrok.com/
* 검색 해보면 이 외에도 많은 참조 사이트가 존재한다.
( 시스템 콜을 C언어에서 사용할 때는 write system call의 경우 <unistd.h> 헤더파일을 포함시켜줘야 한다..
C언어의 경우 시스템 콜을 직접 실행하는게 아니라 mapping 되어있기 때문에 사용이 가능한 것이므로
헤더파일을 포함시켜줘야 되는것 같다.. )
( 시스템 콜은 번호로 관리가 되고 있기 때문에 어셈블리에서는 시스템 콜을 사용할때 eax에 해당 되는
시스템 콜 번호를 넣어준다.. )
( 프로그램 실행시 segment default가 나는 부분은 exit system call을 이용해서 잡아줄 수 있고
두번째 인자로는 int error_code가 들어가게 되는데 표준 에러인 0을 넣어주면 된다 )
!! 유용한 명령어
1. ltrace
- 실행 프로그램에서 사용중인 C 라이브러리를 추적
2. strace
- 실행 프로그램에서 사용중인 시스템 콜을 추적
!!! 해당 명령어들만 잘 이용해도 직접 바이너리를 열어보지 않고 프로그램의 구조를 파악할 수 있다.
'프로그래밍 > SYSTEM HACKING' 카테고리의 다른 글
[SYSTEM HACKING] 디버거(DeBugger) 사용법( GDB ) / 바이트 오더( byte order ) (0) | 2017.11.13 |
---|---|
[SYSTEM HACKING] 시스템 콜 실습 ( mymkdir, mycat, myshell ) (0) | 2017.11.02 |
[SYSTEM HACKING] 어셈블리 call & jmp 명령의 차이 / 스택 메모리를 이용한 인자 전달 / reverse.asm 지역변수 사용해서 표현하는 실습 (2) | 2017.11.01 |
[SYSTEM HACKING] 실행중인 프로세스의 전체 메모리 구조 / 스택에서 사용되는 레지스터 / 어셈블리에서 함수 (0) | 2017.10.26 |
[SYSTEM HACKING] 어셈블리 반복문( jmp ) (0) | 2017.10.24 |
댓글