본문 바로가기

3. Pwnable (포너블)

[2023.10.14] 포너블 3주차 팀활동

1. 인프런 생활코딩 - linux 강의 섹션 7-8 수강 후 강의 리뷰 & 어려웠던 점, 흥미로웠던 점 공유

[요약 정리에 사용된 사진은 강의 화면을 캡쳐하였습니다.]

2. 달고나 문서 (~ chap 04) 내용 요약

 

[LINUX]

섹션7

 

권한 기본

egoing 사용자가 echo 명령어를 이용해 perm.txt에 hi를 입력한다.

 

k8805 사용자가 echo 명령어를 사용해 perm.txt 에 접근하려 하지만 권한을 egoing이 가지고 있기 때문에  해당 명령은 실행되지 않는다.

 

permission의 골격

위 사진에 대해 설명해 보자면 맨앞 '-' 는 type을 나타내고 'rw-rw-r--' 은 access mode를 나타낸다. 첫번째 egoing은 owner를 나타내고 두번째 egoing은 group을 나타낸다.

'rw-rw-r--' 을 'rw-/rw-/r--' 로 나눠 보자면 첫번째 'rw-' 는 owner가 read 와, write의 권한이 있음을 뜻하고, 두번째 'rw-'는 group도 read와 write의 권한이 있음을 나타낸다. 반면 'r--'는 other의 권한을 뜻하는데 ohter(이 시스템에 있는 모든 사용자) read의 권한만을 가지고 있는 것을 뜻한다. 즉 '-' 는 excute(없다)를 뜻한다.

 

 

권한을 변경하는 방법 - execute

 

chmod 는 mode를 change하다 라는 뜻인데 'chmod o-r perm.txt' 이와 같이 쓰면 other의 read 권한을 탈취하겠다는 명령어 이다. 'chmod o+r perm.txt' 이와 같이 쓰면 other의 read 권한을 추가하겠다는 명령어이다.

 

'chmod u-r perm.txt' 명령어를 통해 read 권한을 제한시켰다. 이 경우에는 자신의 문서라고 해도 read 권한을 제한시켰기 때문에 permission denied가 뜬다.

 

실행의 개념과 권한 설정 - execute

 

<그림1>
<그림2>

 

 

위 <그림1> 은 'nano hi-machine.sh'를 통해 bin/bash 라는 프로그램을 통해 echo로 'hi hi hi hi' 문자를 부여한 상태이다.

5행을 보면 hi-machine.sh 파일이 존재하는 것을 볼 수 있다.

'./hi-machine.sh'를 치면 Permission denied가 뜨지만  반면 '/bin/bash hi-machine.sh'를 보면 제대로 실행되는 것을 확인할 수 있다.

'./hi-machine.sh'  <- 해당 명령어를 실행시키고 싶다면, hi-machine.sh에 실행 권한을 부여시키면 된다. 이때 사용하는 것이

'execute' 이다.

 

 

chmod를 통해 hi-machine.sh에 excute 권한을 부여한 모습이다.

 

chmod를 통해 hi-machine.sh에 excute 권한을 부여하고 'hi-machine.sh'를 실행하면 다음과 같이 실행된다.

 

반면에 k8805 사용자는 hi-machine.sh 에 대해 x 권한이 없기 떄문에 실행되지 않는다. 만약 k8805 에서도 실행시키고 싶다면 egoing에 'chmod o+x hi-machine.sh' 명령어를 사용해 other에 x 권한을 부여하면 된다.

 

directory 권한

egoing 사용자에서 perm 이라고 하는 디렉토리 권한중에 write가 없기 떄문에 k8805 사용자가 perm 디렉토리 안에 a.txt를 만들려고 해도 Permission denied가 떠 디렉토리 안에 파일을 생성할 수 없다. (rm perm.txt 및 파일 이름을 바꾸는 것도 불가)

 

chmod  -R o+w perm 에서 -R은 perm 안에 있는 하위 디렉토리 포함해서 모든 디렉토리를 포함하는 뜻이다.

 

 

 

chmod 사용법 정리 - class & operation

 

chmod { options } mode{,mode} file1 {file2 ...}

 

방법 1 : Octal modes

ex) chmod 111 perm. txt 를 입력하면&nbsp; rwx 를 다 execute로 변경
<그림1>

 

<그림2>

'chmod a+r perm .txt' 명령어는 모든 사용자가 perm.txt  에 대해 read 권한을 갖게 한다. 'a+r' 대한 'a+rwx'를 쓰게 되면 모든 사용자가 read,write,execute 권한을 갖게된다. 나머지 명령어 조합은 <그림1>과 <그림2>를 참고해 만들 수 있다.

 

 

섹션 8. 그룹

 

파일과 디렉토리를 여러 사용자들이 공동으로 관리할 수 있는 방법 - group

 

1. group의 이름을 부여한다

2. file에 group을 지정

3. file에 대한 group 권한을 부여 (R,W,X)

4. group에 포함된 사용자가 file로부터의 권한을 가진다.

 

groupadd

 

useradd -G {group-name} username : 그룹에 사용자를 추가하는 명령어

groupadd developers : 그룹을 추가 -> 일반 사용자는 앞에 sudo 붙여야 실행

usermod -a -G {그룹 이름} {사용자 이름} : 그룹 내에 어떤 사용자를 수정할 것인지에 관한 명령어 ->일반 사용자는 앞에 sudo 붙여야 실행

 

위에 설명한 명령어들을 실습한 결과이다. sudo 를 붙여 관리자 권한일 때만 실행되는 것을 확인할 수 있다.
'cd/var/developer' 에 들어가 'man chown' 명령어를 친 화면이다. 이 명령어를 통해 파일의 오너와 그룹을 change할 수 있다. 즉 현재 디렉토리의 소유자와 그룹을 바꿀 수 있는 명령어이다.
'sudo chown root:developer'을 이용해 소유 group을 바꾼 예시이다.

 

[달고나 문서]

 

프로그램 구동 시 Segment에서는 어떤 일이?

C 프로그램을 어셈블리 코드로 변환하기 위해서 아래와 같은 옵션으로 컴파일

-S 옵션을 이용하여 컴파일

이렇게 만들어지는 어셈블리 코드는 컴파일러의 버전에 따라 다르게 생성되며 만들어진 어셈블리 프로그램은 simple.asm이라는 파일 이름으로 생성되었다.

simple.c 프로그램이 컴파일 되어 실제 메모리 상에 어느 위치에 존재하기 될지 알아보기  위해서 컴파일을 한 다음 gdb를 이용하여 어셈블리 코드와 메모리에 적재될 logical address  를 살펴보자.

앞에 붙어 있는 주소는 logical address로 이 주소를 자세히 보면 function()함수가 아래에 자리 잡고 main()함수는 위에 자리잡고 있음을 알 수 있다.

 

segment의 크기는 프로그램마다 다르기 때문에  최상위 메모리의 주소는 그림과 같이 구성되지 않을 수도 있다.

 이 segment의 logical address는 0x08000000 부터 시작하지만 실제 프로그램  이 컴파일과 링크되는 과정에서 다른 라이브러리들을 필요로하게 된다. 따라서 코딩한 코드가 시작되는 지점은 시작점과 일치하지는 않을 것이다. 뿐만 아니라 stack segment 역시  0xBFFFFFFF까지 할당 되지만 역시 필요한 환경 변수나 실행 옵션으로 주어진 변수 등등에  의해서 가용한 영역은 그 보다 조금 더 아래에 자리잡고 있다.

이제 프로그램이 시작되면 EIP 레지스터 즉, CPU가 수행할 명령이 있는 레지스터는 main()함수가 시작되는 코드를 가리키고 있을 것이다. main() 함수의 시작점은 0x80482fc가 되겠다.

ESP가 정확히 어느 지점을 가리키는지 알아보기 위하여 gdb를 이용하여 알아 본 레지스터 값

 

 

Simple.c 프로그램 실행과정

<step 1>

· EIP : main()함수 시작점을 가리킴

 

· ESP : 스택의 맨 꼭대기를 가리킴(프로그램이 수행되며 수많은 스택 명령(PUSH, POP)을 수행할 것). 시스템 구조에 따라 ESP가 가리키는 지점 혹은 가리키는 지점 아래 지점에 데이터가 위치가 정해진다.

 

· base pointer : 이전에 수행하던 함수의 데이터를 보존하는 ebp를 저장한다. 

 

·  함수 프롤로그 과정 : 함수 시작 시 stack pointer와 base pointer를 새로 지정

 

 

 

 

 

 

<step 2>

· push %ebp : 이전 함수의 base pointer를 저장 (stack pointer는 4바이트 아래인 0xbffffa78를 가리킴)

· mov %esp, %ebp : ESP값을 EBP에 복사 (base, stack pointer가 같은 지점을 가리키게 됨)

· sub $0x8, %esp : ESP에서 8을 빼는 명령어. (ESP -> 8byte 아래 지점 / 스택에 8byte 공간 생김 = 스택 8byte 확장.) 해당 명령 수행 후 ESP에는 0xbffffa70이 들어감

· and $0xfffffff0, %esp :  ESP와 11111111 11111111 11111111 11110000과 AND 연산을 수행(ESP 주소값 맨 뒤 4bit 0으로 만듬)

· mov $0x0 : EAX 레지스터에 0을 넣는다

· sub %eax,%esp : ESP에 들어있는 값 - EAX에 들어 있는 값(stack pointer를 EAX만큼 확장하고자)

· sub $0x4 : 스택을 4byte 확장 (ESP에 든 값은 0xbffffa6c가 된다)

 

 

 

 

 

<step 3> - step 2까지 과정 수행 결과[ ESP는 12byte 이동 ]

· function(1,2,3)을 수행하기 위해

  PUSH $0x03 > PUSH $0x02 > PUSH $0x01 명령을 차례대로 수행한다. (stack은 lifo 구조이기 때문에 3 2 1순으로 넣는다)

 

· call 0x80482f4 : function()함수가 위치한  0x80482f에 있는 명령 수행.

call은 함수 실행 끝난 후 이후 명령 주소를 스택에 넣은 다음 EIP에 함수 시작 지점 주소 넣는다. ( add $x10, %esp 명령이 있는 주소)

 

· return address : 함수 수행이 끝나고 나면 이제 어디에 있는 명령을 수행해야 하는 지를 스택에서 pop해서 알 수 있게 되는 것.

 

-> 이제 EIP에 function 함수가 있는 0x80482f4 주소값이 들어가게 된다.

 

 

 

 

<step 4> 

· 현재 EIP : function()함수가 시작되는 지점을 가리키고 있음

 

· 현재 stack : main()함수에서 넣었던 값이 쌓여 있음

 

· push %ebp / mov %esp, %ebp : 함수 프롤로그 수행. main()함수에서 사용하던 base pointer가 저장되고 stack pointer를 function()함수의 base pointer로 삼음

 

 

 

 

 

 

 

 

 

<step 5> - function() 함수 프롤로그 마친 후 만난 명령

· sub $0x28, %esp : 스택을 40바이트 확장 (스택 word 단위 처리, gcc 버전에 의해 용량 변화가 존재)

 

· function 함수의 인자 : function() 함수의 base pointer, return address위에 존재

 

-> 상부 그림13의 main함수 호출시 주어지는 인자 argc, argv가 위치한 곳과 같은 배치를 갖고 있다.

 

 

 

 

 

 

 

 

<step 6> - 우리에게 필요한 데이터를 쓸 수 있게 된 상태

·  동작 방식 : mov $0x41, [$esp -4], mov $0x42, [$esp-8]과 같은 형식. ESP 기준 스택의 특정 지점에 데이터를 복사해 넣는 방식으로 동작

 

 

 

 

 

 

 

 

 

해당 스텝에서의 스택 모습

 

 

<step 7> - leave instruction 수행

· leave instruction : 함수 프롤로그 작업을 되돌리는 일

· push %ebp / mov %esp, %ebp : 그동안의 함수 프롤로그

· mov %ebp, %esp / pop %ebp : 함수 프롤로그를 되돌리는 작업

 

- > leave instruction은 위 두 작업 한꺼번에 수행. (main()함수의 base pointer를 복원시키는 작업)

 

· stack pointer(pop 연산 이후): 1 word 위로 올라간 후 return address가 있는 지점 가리킴 

· ret instruction :  이전 함수로 return 하라는 의미 (EIP 레지스터에 return address를 pop하여 집어넣는 역할) [pop %eip라 표현할 수도 있지만 EIP는 직접수정 불가능. 동작은 비슷하다.]

 

 

 

- ret

· return address : POP되어 EIP에 저장.

- > stack pointer : 1 word 위로 올라간다.

· add $0x10, %esp : 스텍을 16바이트 줄인다.

- > stack pointer : 0x804830c에 있는 명령 수행 이전 위치로 돌아간다.

 

[leave, ret] 수행 시

: 각 레지스터들의 값은 main()함수 프롤로그 작업을 되돌리고 main()함수 이전으로 돌아감(init_process() 함수(운영체제가 호출하는 함수. 개발자X)로 되돌아갈 것.)