3. Pwnable (포너블)

[2022.11.19] 포시즌 - 포너블팀 활동보고

불량식품킬러 2022. 11. 25. 22:54

1. 생활 코딩 리눅스 강좌 섹션 6 : 사용자(user)

  • 다중 사용자1 (Multi user 1)
  • 유닉스계열의 시스템은 다중사용자 시스템임. 따라서 권한 설정이 중요함. 여러명이 운영체제를 같이 사용하는 방법에 대한 설명에 대한 필요성을 말함. -
  • 다중 사용자2 (Multi user 2)접속 사용자 식별 명령어 : who → 컴퓨터에 접속해있는 사용자를 알려주는 명령어
  • 사용자 식별 명령어 : id → 현재 사용자의 uid와 gid를 알려줌.
  • 관리자와 일반 사용자 ( super(root) user VS general user)
    • 단순 일반 사용자는 sudo 명령어 접근 자체도 막힐 수 있음.
    • super user 는 보통 닉네임 앞에 root 가 붙음.
    • 명령줄 뒤에 붙는 ~$ 기호는 보통 일반 유저 사용자라는 뜻 root 사용자는 ~#이 붙는다.
    • root 사용자로 만들기 위한 명령어 : su - [사용자 이름]
    • 또는 - root
    • 관리자 계정의 사용은 많은 권한을 운용하기 때문에 주의가 필요함. 일반 사용자로 돌아갈 때는 exit 명령어를 이용한다.
    우분투에서는 일반 사용자의 root 사용자의 권한에 접근하는것을 막아 놓는 경우가 있는데 필요하면 lock을 풀어야함.이후에 su -root 를 사용해 관리자 권한으로 접속일반 사용자의 home 디렉토리는, /home 하위에 존재함.cd /root는 최상위 루트 디렉토리 하위에 존재하는 root 라는 디렉토리임 (root 사용자의 home 디렉토리임)
  • cd /root는 관리자, root 계정만 접근 가능 cd/ 가 최상위 루트 디렉토리,
  • 다시 lock을 거는 경우에는, sudo passwd -l root (l은 lock 의 약자) 사용.
  • 명령어: sudo passwd -u root (u는 unlock의 약자임) 를 사용.
  • sudo 는 super user의 권한을 빌려오는 명령어
  • 사용자 추가(Add user)-unix add user 검색명령어 : sudo useradd -m [추가하고 싶은 사용자 이름]사용자 변경의 경우 su - [사용자 이름]추가한 사용자가 sudo 명령을 사용할 수 있게 하는 방법
    1. 이미 존재하는 사용자에 sudo 권한을 실행 시킬 수 있게끔 해주는 방법이 있음. (대신 sudo 권한을 실행 시킬 수 있는 사용자 계정에서 실행 시켜야 함 !!) 명령어 : sudo usermod -a -G sudo [추가했던 사용자 이름]
  • 1.사용자를 만들때부터 sudo 권한을 주는 경우가 있고,
  • 사용자 패스워드 설정은, sudo passwd [사용자 이름]
  • 추가 하고 나서 ls 확인
  • 사용자 추가의 경우 sudo 명령어 붙여 사용’(꼭 외울 필요는 없다는 말 하면서 검색해서 사용하라고 하심)
  • 필요할 때마다 검색해서 사용하는 것을 추천

2. 생활 코딩 리눅스 강좌 섹션 7 : 권한 (permission)

권한에 대한 설명의 경우, 이 사진 한장으로 정리 가능함.

유닉스 계열에서 권한(permmision)을 주는 대상 : file & directory

사용자 별로 접근 가능한 파일이나 디렉토리에 대한 제한을 주는 것.

권한의 범위에는 (Read & Write & Excute) 가 있다. 읽기 쓰기 실행

명령어 : echo ‘hi’ > perm.txt (파일 입출력 명령어 복습)

파일의 소유자가 아닌 사람에게 허용된 권한이 아닌 동작을 실행시키면 거부됨.

 

위의 사진에 설명되어 있는 내용 잘 익혀두기 중요함*

access mode 를 바꾸는 명령어에 대한 학습

  • 명령어 : chmod
  • 사용 예시 : chmod o-r [파일명] → other 사용자의 읽기 권한을 해제
  • 사용 예시2 : chmod o+r [파일명] → other 사용자의 읽기 권한을 추가
  • 사용 예시3. : chmod u-r [파일명] → user(소유자의 읽기 권한 해제)
  • 실행과 권한 설정 -(execute)r - readx -execute실행 권한을 추가하는 방법 또한 chmod 명령어를 사용
  • 만약, 실행가능한 파일이라면, 파일 명에 초록색으로 실행가능하다고 뜸.
  • 이번에는 실행 권한에 대해서 알아보는 단계임
  • w - write
  • 권한의 범위에는 rwx가 있음.
  • 디렉토리에 대한 권한 (directory)수업에서 나온 예시 명령어 : mkdir perm;cd perm;echo ‘hi’ > perm.txt → ;(콜론)을 이용해서 명령어 한 줄에 다중으로 명령 실행 가능디렉토리의 접근 권한을 변경해 줄때는, 위에서 배운 명령어에, 맨 뒤에 파일명이 아니라 디렉토리명을 넣어주면 된다.
    • 사용 예시 : chmod o-r [디렉토리명] → other 사용자의 디렉토리 내에 있는 파일들에 대한 read 접근(디렉토리내에 있는 파일이 뭔지 볼 수 있는지에 대한 권한) 권한 허용 명령어 (ls -l 을 실행시에 권한이 없다고 뜸)
    • 디렉토리에서 w는 디렉토리 내 파일생성에 대한 권한을 말한다.
    • 다중 디렉토리안에서 모든 디렉토리 내에 있는 권한을 수정하고 싶을 경우에는, chmod -R o+w [디렉토리명]을 쓴다.
  • cd .. 은 상위 디렉토리 이동 명령어 기억하기
  • 디렉토리 만드는 명령어 : mkdir
  • chmod 사용법 정리
  • chmod는 일일이 하나씩 명령어를 쳐가면서바꿔야하는 단점이 있음. 보완적인 방법으로 숫자를 사용하는 방법도 있는데, 아래 사진을 참고하면 쉽게 이해 할 수 있을 것 이다.
  •  

이렇게 이진수로 권한을 나타내고 실행 권한에 따라서 숫자를 달리하여 명령을 주는데, 예를들어 chmod 111 [파일명]을 명령어로 넣으면 그 파일에 대해서 owner ,group, other 의 권한이 모두 x만 들어가게 되는것이다. 중복으로 권한을 넣어주고 싶으면 각 자리의 수를 더해서 활용하면된다.

이러한 방법 말고도, a 옵션을 넣어주는 방법도 있음.

사용 예시 : chmod a+w [파일명]

이러한 명령어를 넣어주면 all → 모든 사용자에 대해서 권한 추가가 실행됨 a=rwx 나 a=r 과 같이 응용가능함.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

3. 해커지망생들을 위한 달고나 BOF 문서 뽀개기 : 28- 32

 

 Buffer overflow의 이해

 

버퍼(buffer)란?

시스템이 연산 작업을 하는데 있어 필요한 데이터를 일시적으로 메모리 상 의 어디엔가 저장하는데 그 저장 공간을 말한다.

 대부분의 프로그램에서는 바로 이러한 버 퍼를 스텍에다 생성한다. 스텍은 함수 내에서 선언한 지역 변수가 저장되게 되고 함수가 끝 나고 나면 반환된다. 이것은 malloc()과 같은 반영구적(free()를 해 주지 않는 이상 이 영역 을 계속 보존된다)인 데이터 저장 공간과는 다른 것이다.   buffer overflow는 미리 준비된 버퍼에 버퍼의 크기 보다 큰 데이 터를 쓸 때 발생하게 된다.

<그림 14>에서 보는 스텍의 모습은 40바이트의 스텍이 준비되어 있으나 40바이트 보다 큰 데이터를 쓰면 버퍼가 넘치게 되고 프로그램은 에러를 발생시키 게 된다.  41~44바이트의 데이터를 쓴다면?  이전 함수의 base pointer를 수정하게 될 것이다. 더 나아가 45~48바이트를 쓴다면 return address가 저장되어 있는 공간을 침범하게 될 것이 고 48바이트 이상을 쓴다면 return address뿐만 아니라 그 이전에 스텍에 저장되어 있던 데 이터 마저도 바뀌게 될 것이다.

여기서 시스템에게 첫 명령어를 간접적으로 내릴 수 있는 부분은 return address 가 있는 위치이다. return address는 현재 함수의 base pointer 바로 위에 있으므로 그 위치는 변하지 않는다.

 공격자가 base pointer를 직접적으로 변경하지 않는다면 정확히 해당 위치에 있는 값이 EIP에 들어가게 되어 있다. 따라서 buffer overflow 공격은 공격자가 메모리상의 임의의 위치에다 원하는 코드를 저장시켜 놓고 return address가 저장되어 있는 지점에 그 코드의 주소를 집어 넣음으로 해서 EIP에 공격자의 코드가 있는 곳의 주소가 들어가게 해 공격을 하는 방법이다.

공격자는 버퍼가 넘칠 때, 즉 버퍼에 데이터를 쓸 때 원하는 코드를 넣을 수가 있다.

 

 

 

예시 문제 1.

 

strcpy(buffer2, receive_from_client);

 

이 코드는 client로부터 수신한 데이터를 buffer2와 buffer1에 복사한다.

알다시피 strncpy() 과 같은 함수는 몇 바이트나 저장할지 지정해 주지만 strcpy함수는 길이 체크를 해 주지 않 기 때문에 receive_from_client 안에 들어있는 데이터에서 NULL(\0)를 만날 때까지 복사를 한다. <그림 14>와 같은 스텍 구조에서 45~48바이트 위치에 있는 return address도 조작해 줘야 하고 공격 코드도 넣어줘야 한다. <그림 15>와 같은 구성의 공격 코드를 생각해 보자 (실제 값들은 아무런 의미가 없는 임의의 값이다).

 

클라이언트인 공격자가 전송하는 데이터는 receive_from_client에 저장되어 버퍼에 복사될 것이다. 그 데이터가 <그림 15>와 같이 구성하여 전송한다고 가정하자. 그리고 strcpy가 호 출되어 receive_from_client가 buffer2에 복사가 될 것을 예상하면 <그림 14>와 <그림 15>를 함께 보았을 때 다음과 같이 매칭될 것이다.

 

 

strcpy가 호출되고 나면 스텍안의 데이터는 <그림 17>과 같이 된다.

<그림 17>은 receive_from_client의 데이터를 버퍼에 복사한 후의 모습이다. 들어가 있는 데이터들을 가만히 보면 <그림 16>에서 만들어낸 데이터와 순서에 있어 약간의 차이가 있 음을 알 수 있다.

Byte order 데이터가 저장되는 순서가 바뀐 이유는 바이트 정렬 방식이다. 현존하는 시스템들은 두 가 지의 바이트 순서(byte order)를 가지는데 이는 big endian 방식과 little endian 방식이 있다. big endian 방식은 바이트 순서가 낮은 메모리 주소에서 높은 메모리 주소로 되고 little endian 방식은 높은 메모리 주소에서 낮은 메모리 주소로 되어 있다. IBM 370 컴퓨터와 RISC 기반의 컴퓨터들 그리고 모토로라의 마이크로프로세서는 big endian 방식으로 정렬하 고 그 외의 일반적인 IBM 호환 시스템, 알파 칩의 시스템들은 모두 little endian 방식을 사 용한다. 예를 들어 74E3FF59라는 16진수 값을 저장한다면 big endian에서는 낮은 메모리 영역부 터 값을 채워 나가서 74E3FF59가 순서대로 저장된다. 반면 little endian에서는 59FFE374 의 순서로 저장된다. little endian이 이렇게 저장 순서를 뒤집어 놓는 이유는 수를 더하거나 빼는 셈을 할 때 낮은 메모리 주소 영역의 변화는 수의 크기 변화에서 더 적기 때문이다.

예를 들어 74E3FF59에 1을 더한다고 하면 74E3FF5A가 될 것이고 메모리상에서의 변화는 5AFFE374가 된다. 즉 낮은 수의 변화는 낮은 메모리 영역에 영향을 받고 높은 수의 변화 는 높은 메모리 영역에 자리를 잡게 하겠다고 하는 것이 little endian 방식의 논리이다. 높 은 메모리에 있는 바이트가 변하면 수의 크기는 크게 변한다는 말이다. 하지만 한 바이트 내에서 bit의 순서는 big endian 방식으로 정렬된다. 참고로 네트웍 byte order는 big endian 방식을 사용한다. 이러한 byte order의 문제 때문에 공격 코드의 바이트를 정렬할 때에는 이러한 문제점을 고려해야 한다. 그러므로 little endian 시스템에 return address 값을 넣을 때는 바이트 순서 를 뒤집어서 넣어주어야 한다.

 

<그림 17>에서 보는 바와 같이 return address가 변경이 되었고 실제 명령이 들어 있는 코드는 그 위에 있다. 이 시점까지는 아무런 에러를 발생하지 않을 것이다. 하지만 함수 실 행이 끝나고 ret instruction을 만나면 return address가 있는 위치의 값을 EIP에 넣을 것이고 이제 EIP가 가리키는 곳의 명령을 수행하려 할 것이다. 이 때 이 주소에 명령어가 들어 있 지 않다면 프로그램은 오류를 발생시키게 된다. 또한 공격자는 자신이 만든 공격 코드를 실 행하기를 원하므로 EIP에 return address 위에 있는 쉘 코드의 시작 주소를 넣고 싶어 한다.

어떻게 하면 이 주소를 알아낼 수 있을까? 그 방법은 다음 장에서 살펴보도록 하자. 일단은 쉘 코드가 들어있는 지점의 정확한 주소를 찾았다고 생각하자. 의 그림을 참고해 볼 때 주소는 0xbffffa60이다. <그림 17>을 다시 그려 쉘 코드와 return address를 묘사해 보면 <그림 18>과 같다.

<그림 18>에서 보여주는 공격 코드는 execve(“/bin/sh”,…) 이다. 즉 쉘을 띄우는 것이다. 실제 쉘 코드가 그림처럼 들어가 있는 것은 아니다. 쉘 코드를 기계어 코드로 변환하여 1 word 단위로 넣어가면서 따져보면 알 수 있겠으나 <그림 18>은 저 위치에 저런 의미의 코드가 들어있다는 개념을 표현한 것이므로 그 개념만 이해하기 바란다.

쉘 코드의 시작 지점은 스텍상의 0xbffffa60이다. 따라서 함수가 리턴될 때 return address 는 EIP에 들어가게 될 것이고 EIP는 0xbffffa60에 있는 명령을 수행할 것이므로 execve(“/bin/sh”,…)를 수행하게 된다. 이것이 바로 buffer overflow를 이용한 공격 방법이다.

 

 

 

 

 

 

 

*해당 문서 및 강의를 동아리 팀 스터디 목적으로 이용하였음을 밝힙니다.*