Day7 Exploit 2
Contents
익스플로잇(2)✔
shellcode 란?✔
-
쉘코드 shellcode : 쉘을 실행시킬 수 있는 코드이다.
-
어셈블리어 코드를 문자열로 삽입할 수 있도록 기계어로 변환하여 묶은 것이다.
-
setuid
가 설정된 프로그램에서return address
를 조작할 수 있을 때 쉘을 실행시킬 수 있는 어셈블리 코드를 문자열로 삽입하고 삽입된 문자열의 주소값으로return address
를 조작한다.
-
Hello, World!!! 프로그램을 문자열로 만들어보기✔
-
지난주에 다음과 같은 문자열을 출력하는 어셈블리 코드를 코딩했었다. 이 코드를
hello.asm
으로 저장하자.global _start _start: mov rax, 1 mov rdi, 1 mov rsi, message mov rdx, 16 syscall message: db "Hello, World!!!", 10
-
이러한 어셈블리어를 기계어로 컴파일 한 후에 기계어 코드를 헥사값으로 만들어보자.
nasm -felf64 hello.asm ld -s hello.o -o hello objcopy -O binary -j .text hello hello.hex for hex in `od -A n -t x1 hello.hex`; do echo -nE "\x$hex"; done > shellcode.txt
-
그리고 이렇게 완성된
shellcode.txt
에는 그 어셈블리어에 대응되는 기계어가 헥사값으로 변환되어 있다. 이제 이 헥사값들을 C 프로그램에 집어넣어보자.cat shellcode.txt >> shellcode_test.c
-
그리고
shellcode_test.c
를 다음과 같이 완성해보자.char * shellcode = "\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\xbe\xa5\x00\x40\x00\x00\x00\x00\x00\xba\x0d\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\x48\x31\xff\x0f\x05\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x0a"; int main(void) { void (*fp)(void); fp = shellcode; fp(); return 0; }
-
shellcode
라는 문자열을void
반환형이면서 매개변수 형태는void
인 함수 포인터fp
에 대입시켰다. 그리고 곧바로 호출하고 있다. 그러나 컴파일 하고 실행해봐도 "Hello, World" 가 출력되지 않는다. 그것은shellcode
문자열이\x00
널바이트를 만나서\xb8\x01
까지만 인식되고 나머지는 인식되지 않기 때문이다. 그러므로 어셈블리어를 수정해서 널 바이트가 생성되지 않도록 고쳐보자.global _start _start: push 1 pop rax push 1 pop rdi mov rbx, 0x0a212121646c726f ; "\n!!!dlro" push rbx mov rbx, 0x57202c6f6c6c6548 ; "W ,olleH" push rbx ; 이 시점에서 스택에 "\n!!!dlroW ,olleH" 문자열이 이렇게 저장됨 push rsp ; 이 시점에서 스택에 문자열을 가르키는 주소값이 저장됨 pop rsi ; 이때 rsi 가 문자열을 포인팅하게 됨. 즉 rsi 에 문자열의 주소값이 저장됨 push 16 pop rdx syscall push 60 pop rax xor rdi, rdi ; 똑같은 걸 xor 연산해서 rdi 가 0 이 됨. syscall
-
mov
는push
와pop
을 활용해서 없앤다. 왜냐하면mov
에 전달되는 값이8
바이트로 채워지지 않으면 널 바이트가 포함되기 때문이다. 문자열을 만들기 위해mov
명령어를 써야할 때는 헥사값8
바이트를 꽉꽉 채워저mov
명령어에 전달함으로써 널 바이트를 없앴다. 그리고0
을 대입해야 하는 명령어는xor
명령어로 대체한다. 이렇게 되면 널 바이트가 생성되지 않는다. 다시 컴파일하고 헥사값을 추출한 후 C 프로그램에 넣어보자.char * shellcode = "\x6a\x01\x58\x6a\x01\x5f\x48\xbb\x6f\x72\x6c\x64\x21\x21\x21\x0a\x53\x48\xbb\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x53\x54\x5e\x6a\x10\x5a\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x0a"; int main(void) { void (*fp)(void); fp = shellcode; fp(); return 0; }
-
위와 같이 널바이트를 제거한 헥사코드를 문자열에 저장한 후 다시 함수포인터로 형변환 하고 호출하면
Hello, World!!!
가 출력된다.-
#include <stdio.h>
같은 헤더파일도 선언해주지 않았다. -
코드로만 봐서는 순수하게 문자열 전역변수를 함수 포인터로 형변환한 후 호출한 것 뿐이다. 그런데도
Hello, World!!!
가 출력되었다!
-
shellcode 생성✔
-
위와 같은 원리로
Hello, World!!!
를 출력하는 것이 아닌 쉘을 실행시킬 수 있는 코드를 헥사코드로 변환하고 널 바이트를 제거하여 만든 것을 shellcode 라고 한다. 실질적인 쉘 코드를 만들기에는 시간이 부족하기 때문에 일단 인터넷이 있는 쉘코드를 갖다가 쓰도록 하자. -
이 링크에 보면 누군가가 쉘 코드를 잘 만들어놓았다. 리눅스
x64
아키텍쳐 용 쉘 코드이니 바로 갖다가 쓸 수 있는 쉘 코드이다.
shellcode 를 BOF 를 통해 스택에 삽입하기✔
이렇게 만들어진 쉘 코드를 다음과 같이 스택에 삽입한 후 NOP
코드를 얼마정도 넣어서 리턴 어드레스 직전까전 닿게 한다. 그리고 리턴 어드레스를 쉘코드가 있는 곳의 주소값으로 덮어써버린다. 그러면 프로그램은 쉘 코드의 주소값으로 뛰게 되고 해커는 권한이 상승된 쉘을 얻게 된다.
bof6 ~ bof8 풀어보기✔
-
bof5
의 비밀번호를 알아내었으니bof5
유저로 접속하고bof6
의 비밀번호를 파헤쳐보세요. -
그리고
bof8
까지 풀어보세요. -
정답은 각각의 소스코드에 주석처리 되어 있습니다.
과제✔
HW.md 파일에 따라 과제를 하시면 됩니다. (발표를 하며 설명을 할 수 있어야 합니다)