3회차 목표 : 정보보안 공부
버퍼 오버플로우로 쉘코드를 이용해 공격하는 법 배우기.
3회차 결과 :
버퍼 오버플로우로 함수 주소를 변경하여 컨트롤 플로우를 변경하였다. 공격자가 만든
코드를 실행하는 것으로 가정해서 메모리에 동적으로 코드를 올리고 실행할 수 있도록
하던 내용이다.
쉘 코드라고 해서 작성을 할 때, 머신 코드 레벨로 추출하는 과정이 있다. 소스코드를 컴
파일해서 컴파일된 상태로 머신 코드를 추출해서 취약한 프로그램에 삽입하는 내용. 삽
입되는 코드가 쉘 코드이다.
페이지 2 이미 보았던 그림. 점프나 리턴, 콜로 끊어지는 단위. 다른 블록으로 점프하는
코드. 순차적으로 실행되는 흐름을 원하는 곳으로 뛰게 하는 것이 지난 시간. 지난 시간
의 예제는 호출이 되지 않는 주소를 줘서 한 것이고,
페이지 3 이번에는 임의의 로직을 실행할 수 있는가. 간단한 예제로
페이지 4 스캔에프로 넘치게 한 것. 스택에 존재하는 여러 개체들을 오버라이트 할 수
있었다. 스택 베이스 주소도 있고, 파라미터들도 있고, 로컬 배리어블도 스택에 저장된다.
쭉 버퍼의 뒤쪽에 있다면 버퍼가 넘어가는 부분이라면 공격자의 의도대로 할 수 있다.
보통 많이 하는 게 리턴 주소를 변경. 리턴하는 시점에 실행 흐름이 변경되어 컨트롤 플
로우를 변조하는 형식의 예제. 중요하게 보호해야하는 데이터는 컨트롤, 컨디션 데이터,
정보를 담고 있는 PID 같은 것들, 루트, 논-루트, 아이디에 따라 권한이 달라지는데 공격
자가 저장된 부분을 조작할 수 있다면, 루트 권한으로 변경할 수 있고, 컨트롤 데이터로실행흐름을 변경할 수도 있다. 공격 상황에 따라 적절하게 공격 복잡도를 낮추면서 할
수 있는 걸 찾아서 공격하게 된다. 함수 포인터나 루팅. 프로세스 구조체를 권한 상승하
든지 그런 공격대상에 따라 차이가 있다. 루팅을 위해 컨트롤 플로우를 바꾸든지, 구조체
내에서 PID 값을 변경하든지, 쉬운 방법으로 공격을 할 수 있다.
스택에 공격자가 만들어놓은 코드를 만들고, 점프할 수 있도록 하여 악성 코드를 실행하
도록 한다. 이러한 말셔스 코드를 어떻게 만드는가가 오늘 내용이다.
페이지 7 통하는 경우, 안 통하는 경우가 많다. 시스템 보안 쪽으로 기초적으로 거쳐가는
내용이다.
페이지 8 시스템 콜. os, application이 있는데 어플리케이션이 os에 요청하는 경우가 있
다. os에만 있는 기능을 요청할 때, 중요 자원을 건드릴 때 시스템 콜을 통해 어플리케이
션이 받아온다.
페이지 9 쉘코드라는 것. 악성 코드. 보통 어셈블러로 짜여지고, 머신코드 형태로 16진수
형태의 명령어로 만들어진다. 취약점이 있다면 공격할 때, 실행하고 싶은 형태를 쉘코드
형태로 만들어서 삽입한다. 루트 쉘을 새로 Invoke하는 목적으로 작성된다.
페이지 10 시스템콜. 유저에서 커널로 접근하는 방법이다. 여러 기능을 제공한다. 인풋
읽기, 아웃풋 프린트, 프로세스 종료 및 생성 등등이 OS에게 요청해서 실행하도록 한다.
OS의 하드웨어 관리를 어플리케이션이 요청하여 사용한다. 커널의 분리로 중요 자원을
보호한다.
페이지 11 실습의 경우에는 중간고사 이후에는 arm이라는 모바일 프로세서를 이용. gdb
로 할 때는 intel을 이용한다. 32bit를 x86 아키텍쳐라고 한다. cpu 권한을 나타내는 게
해당 페이지의 그림이다. ring 0가 가장 권한이 높다. 유저 어플리케이션은 ring 3로 돌아
간다. 권한이란 것은 명령어에 대해서 실행할 권한을 뜻한다. 캐시메모리를 플러시하고
리프레쉬 하는 것은 유저 어플리케이션에서 바로 할 수 없을 수 있다. page table 관리의
명령은 os에서만 한다. cpu가 ring 0 권한에서 돌아갈 때만 가능하다. ring 0에서는 대부
분의 명령어 사용가능. ring 3는 제한된다. 유저 영역은 ring 3에서 돌게 하고 중요 명령
어 실행 시 권한이 부족하여 안된다.
페이지 12 커널 메모리로 세팅된 부분은 ring 0일 때만 접근할 수 있다. OS가 중요한데
일반 어플리케이션으로부터 자원을 보호하는 걸 아키텍쳐를 통해 보호한다.
페이지 13 Libc c 라이브러리이다. 필요한 api도 들어가 있고, 시스템 콜 관련된 애들이
여기 있다. os에 요청하기 위한 코드들이 들어가 있다. printf, malloc 등. 알고보면 os에
요청하는 syscall 이 있다.
페이지 14 어떻게 요청하는가. 32비트 환경에서는 어셈블리를 실행하면 ring 3 -> ring 0로 cpu 모드가 변경이 된다. 유저에서 커널로 진입. 진입 시 os에 요청의 종류는 EAX의
번호로 들어가게 된다. EAX의 값을 보고 어떠한 요청인지 파악한다. 아규먼트도 레지스
터로. EBX, ECX 등등에는 필요한 파라미터들이 들어가게 된다.예를 들어 write source,
target의 주소를 넣는다.
페이지 15 다른 레지스터들을 이용해서 아규먼트 세팅. 더 많이 필요할 경우 스택이나
포인터를 이용한다.
페이지 16 exit의 예제. api를 실행하면 소스코드는 저것이 전부이다. Libc의 하나이다. 스
태틱 컴파일을 하면 라이브러리가 동적이지 않게 들어간다. 프로그램을 종료시키는 역할
이지만, 시스템 콜을 요청하게 되어 있다는 것을 알 수 있다.
페이지 17 gdb로 확인해 보면 어셈블리로 볼 수 있다. \
exit은 분기해서 가게 된다.
_
페이지 18 디스어셈블로 이렇게 나온다. 스태틱 컴파일을 해야한다. 그냥 컴파일하면 안
나온다. exit을 갖고 있지 않기 때문이다. 스태틱으로 하면 하나의 라이브러리에 다 들어
가게 된다. 6줄의 어셈블리에서 0x80과 같이 이전 페이지에서 알려준 내용이 나오게 된
다. 내가 원하는 서비스 요청 시 어떤 레지스터에 어떤 걸 넣는지 분석해서 잘 만들어야
한다. eax레지스터에 들어가는게 move 0xfc(10진수로 252)가 들어가게 된다. 그리고
0x1(현재 나 자신을 종료)도 들어간다. 스레드 다 죽이고 나 자신 죽이는 동작으로 이루
어진다.
페이지 19 쉘코드. exit이라는 라이브러리 서비스에 어셈브리를 보았는데, 쉘코드를 어찌
만드는가
페이지 20 일반적인 내용. 컴팩트하고 간단해야 한다. 스택 오버라이트를 하는데 이전
버퍼 오버플로우는 의미 없는 값을 넣어서 했다. 쉘코드는 쉘코드를 넣어서 점프해서 오
도록 하기 때문에 상황에 따라 크기가 작아야 할 수 있다. 내가 쓸 수 있는 부분부터 타
겟까지의 크기가 상황마다 다를 수 있기에 최대한 작은 악성 코드를 넣어야 스택이 작더
라도 공격이 가능하다. 에러 핸들링 없으면 크래쉬 난다는 건 그리 중요한 얘기는 아니
다.
페이지 21 eax에 세팅하는 게 중요하다. 시스템 콜의 호출 과정 알 수 있다.
페이지 22 문법 상 이렇게 짜는구나 확인만 하면 된다. asm 파일의 확장자로 만들고 어
셈블리 만들기. 파라미터 세팅, 스테이터스 저장(mov). 이 어셈블리를 컴파일하는 툴
nasm
페이지 23 nasm으로 오브젝트 파일 만들고, 링킹 시키는 과정을 거치면 생성된 파일이
exit
shellcode. 어셈블리로 바로 짤 수도 있다. 쉘코드 만드는 과정을 소개하는 중이다._페이지 24 바이너리 덤프를 떠보는 것이다. objdump 리눅스 기본 프로그램을 이용해서
디스어셈블 -d 옵션을 줘서 바이너리를 해석해서 어셈블리와 머신코드를 해석해준다. 실
제로는 bb 00 00 이런 게 머신코드. 기계가 이해하는 것. cd 80은 int 0x80이다.라는 것.
저 숫자 바이트들이 쉘코드이다. intel 같은 경우 복잡한 경우, 간단한 경우 이런데 명령
어 길이가 가변적인 특징이다. 공격자 입장에서는 할 수 있는 게 많아진다. 공격자가 컨
트롤 플로우로 시작점을 다르게 할 것이다. cpu의 해석이 달라지게 되어 실행이 된다.
arm이나 그런 애들은 4바이트로 정해져 있다. 임의 바이너리를 이용할 수 있는게 intel은
가변적이라 해석되는 명령어가 달라지고, 공격자 입장에선 옵션이 많아진다. 쉘코드같은
삽입이 불가할 경우, 공격대상 어플리케이션의 코드들을 이용해서 공격하는데 점프의 위
치에 따라 행동이 달라지니 공격자에겐 좋다. 머신 코드를 얻어내면 쉘코드를 얻어낸다.
활용 가능한 것.
페이지 25 1바이트 씩 쉘코드를 넣어주고, 함수 포인터로 인식시킨다. 함수 포인터에 배
열 주소를 형 변환해서 넣어주고 실행을 하면 실행된다. 테스트 해보기. 데이터 영역에
있는데 실행이 되는 상황이다. 스택에 적재될 것이다. 스택에 있는 무언가가 실행이 된
다. funct가 배열이라 스택 영역에 있을 것이고, 점프해서 실행이 되는 상황이다.
페이지 26 저걸 하려면 컴파일 시 execstack이라는 옵션을 주어야 한다. 스택에게 실행
권한을 주는 것이다. 코드, 데이터 영역이 있다. 코드는 read only에 exe 권한 있다. data
는 exe 권한은 없고 읽고 쓰기가 가능. 그래서 보통의 경우에는 스택에 실행 권한이 없
다. 기본 옵션으로는 스택의 실행 권한은 없다. 오늘의 내용은 쉘코드 만들기가 공통인
데, 스택에다 넣고 하는 것이다. 웬만해서는 통하지 않는다. 실험을 위해서 이렇게 한다.
strace라는 툴로 실행을 시키면 시스템 콜의 히스토리를 다 볼 수 있다. exit(0)의 호출을
볼 수 있다. 시스템트레이스 툴을 이용해서 로그를 확인하는데 exit(0)을 찾을 수 있다.
실습 환경에서는 32비트라 옵션을 안 줘도 된다. 64비트라면 32비트로 컴파일하고 싶다.
-m32 옵션을 줘서 컴파일.
페이지 27에 마지막에 쉘코드에 넣은대로 exit(0)의 실행을 확인할 수 있다. 머신코드가
쉘코드.
페이지 28 공격에 쓸 수 있는 형태로 쉘코드를 수정해줘야 한다. 어떤 부분을 수정해야
하는가.
페이지 29 아까 그 컴파일된 바이너리를 계속 이용해서 설명. 오브젝트 덤프 파일을 통
해서 널 바이트를 없애줘야 한다. scanf시 삭제된다. 쉘코드 내에 널이 있으면 잘려버리
기 때문이다. 기본적으로 널 바이트를 삭제해야한다. 바로 사용하지는 못 한다. 없애는
방법은 여러 개.
페이지 30 이런 식으로 하면 된다. ebx는 4바이트, 00 00 00 00이 들어간다. xor ebx ebx하면 결과적으로 논리 연산에 의해 0인데 0이 들어가지는 않는다. mov eax,1시 컴파일
어셈블리로 바뀌면 eax, 0x00000001이 들어간다. 널바이트가 들어가게 된다. mov al,1 l은
1바이트 레지스터이다. 이러면 앞에 0이 없이 1만 옮겨져서 앞에 0 3개가 사라진다.
페이지 31 널바이트를 없애기 위해 이렇게 한다. 같은 표현이고 세팅이 가능하다. 공격
시 방해가 되는 요소들을 제거할 수 있다. 어셈블리 파일 만들고 오브젝트 만들고 링크
시키고 실행하면 된다.
페이지 32 코드 보면 이렇게 되어 있다. 아까와 동일한 역할을 하지만 크기도 6바이트로
작아지고 0 바이트도 없어진다. 직접 내가 쉘코드를 짜지는 않는다. 자동화 툴이 있다.
어떤 식으로 만들어지는가 확인하는 것이다.
페이지 33 쉘을 실행하는 것.
페이지 34set UID 걸린 애들이 루트 권한을 탈취, 루트 권한이 있는데 쉘을 실행하면 루
트권한의 쉘이 되니까. 앞에서는 exit만 얘기했으나 시스템 콜을 통해서 쉘을 만들 수 있
다.
페이지 35 하이레벨로 짜보고, 컴파일하고 어셈블리 얻어내고 분석한다. 공격 방해 요소
널바이트 확인 후 제거. 실행해보기.
페이지 36 fork와 execve 어플에서 execve가 있다하면 얘가 현재 가지고 있는게 모두 오
버라이트 된다. 다른 것으로 오버라이트. 포크는 자식 프로세스 만들고 execve하면 자식
이 새로운 프로세스가 생성됨. 오버라이트는 안 됨. 기존의 돌고 있던 애를 execve를 이
용. 혹은 자식을 생성해서 자식을 이용.
페이지 37 execve를 이용. 인풋 아규먼트를 이용. 실행할 프로그램의 주소 happy\[0]이
들어가 있다. 전체 주소 및 환경 변수를 준다. 컴파일해서 실행하면 실행된다. 어떤 역할
을 하는 쉘코드를 짜려면 하이레벨로 구현. 제대로 동작하는지
페이지 38 에서 컴파일한다.
페이지 39 라이브러리를 분석해봐야 한다. execve를 하는 애를 찾는다. 메인 함수 분석.
이런 어셈블리가 나온다. execve를 호출하는 애를 분석. 노란 부분이 세팅하는 부분이다.
이 라이브러리를 실행하는데 필요한 아규먼트를 확인. 시스템콜과 같은 경우에 어플리케
이션 내에서 스택을 이용해서 넣는다. 스택에 파라미터 세팅하는 부분. execve 호출에
\
\
execve를 이용한다.
_
_
페이지 40 3개 아규먼트 확인.
페이지 41 프로그램 이름, happy라는 이름. 환경변수는 NULL로. 메인함수에서 호출하는
것을 봤다. 이제 execve의 구성을 보자.페이지 42 최종 관문에서 시스템콜을 이용한다. 0xb에서 eax에 넣는다. 값을 레지스터에
넣는다. 아규먼트를 받아서 스택에 있는 값들을 꺼내서 세팅을 해준다. 쉘코드를 직접
어셈블리로 짜보면서 분석.
페이지 43 스택에 푸쉬하고, bin/sh의 스트링을 넣는 아스키 코드 위에서부터 푸쉬해서
넣는다. 슬래쉬 하나 더 넣어도 상관 없으니까 2f가 / 이다. esp 값을 넣어주고, eax는 0
을 ecx에 넣고 0xb를 %al에 넣어준다. 깔끔하게 끝내기 위해서 exit을 넣어준다. 이렇게
만든 걸 16진수 형태로 넣어준다. 함수포인터를 이용해서 넣어주듯이 변환해서 실행한
다. 쉘이 뜬다. scanf로 버퍼오버플로우 해서 넣을 수 있는 것이다. 만드는 원리이다.
페이지 44 는 만들어 놓은 DB를 볼 수 있다. 특정 목적으로 하려면 아키텍쳐 마다 만들
어져 있다.
'[활동 정리] - 비밀번호 : helloㅁㅁㅁ > [2024]동계 모각코 개인' 카테고리의 다른 글
[모각코] 동계 모각코 5회차 개인목표 및 결과 (0) | 2025.01.21 |
---|---|
[모각코] 동계 모각코 4회차 개인목표 및 결과 (0) | 2025.01.21 |
[모각코] 동계 모각코 2회차 개인목표 및 결과 (0) | 2025.01.10 |
[모각코] 동계 모각코 1회차 개인목표 및 결과 (0) | 2025.01.10 |
[모각코] 동계 모각코 계획 정리 (0) | 2025.01.10 |