Skip to content

Day7 Exploit 2

Contents

익스플로잇(2)

shellcode 란?

  • 쉘코드 shellcode : 쉘을 실행시킬 수 있는 코드이다.

    • 어셈블리어 코드를 문자열로 삽입할 수 있도록 기계어로 변환하여 묶은 것이다.

    • setuid 가 설정된 프로그램에서 return address 를 조작할 수 있을 때 쉘을 실행시킬 수 있는 어셈블리 코드를 문자열로 삽입하고 삽입된 문자열의 주소값으로 return address 를 조작한다.

Hello, World!!! 프로그램을 문자열로 만들어보기

  1. 지난주에 다음과 같은 문자열을 출력하는 어셈블리 코드를 코딩했었다. 이 코드를 hello.asm 으로 저장하자.

    global    _start                  
    _start:                           
        mov       rax, 1             
        mov       rdi, 1            
        mov       rsi, message     
        mov       rdx, 16         
        syscall                  
    message:                    
        db        "Hello, World!!!", 10
    
  2. 이러한 어셈블리어를 기계어로 컴파일 한 후에 기계어 코드를 헥사값으로 만들어보자.

    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
    
  3. 그리고 이렇게 완성된 shellcode.txt 에는 그 어셈블리어에 대응되는 기계어가 헥사값으로 변환되어 있다. 이제 이 헥사값들을 C 프로그램에 집어넣어보자.

    cat shellcode.txt >> shellcode_test.c
    
  4. 그리고 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;
    }
    
  5. 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
    
  6. movpushpop 을 활용해서 없앤다. 왜냐하면 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;
    }
    
  7. 위와 같이 널바이트를 제거한 헥사코드를 문자열에 저장한 후 다시 함수포인터로 형변환 하고 호출하면 Hello, World!!! 가 출력된다.

    • #include <stdio.h> 같은 헤더파일도 선언해주지 않았다.

    • 코드로만 봐서는 순수하게 문자열 전역변수를 함수 포인터로 형변환한 후 호출한 것 뿐이다. 그런데도 Hello, World!!! 가 출력되었다!

shellcode 생성

  • 위와 같은 원리로 Hello, World!!! 를 출력하는 것이 아닌 쉘을 실행시킬 수 있는 코드를 헥사코드로 변환하고 널 바이트를 제거하여 만든 것을 shellcode 라고 한다. 실질적인 쉘 코드를 만들기에는 시간이 부족하기 때문에 일단 인터넷이 있는 쉘코드를 갖다가 쓰도록 하자.

  • 이 링크에 보면 누군가가 쉘 코드를 잘 만들어놓았다. 리눅스 x64 아키텍쳐 용 쉘 코드이니 바로 갖다가 쓸 수 있는 쉘 코드이다.

shellcode 를 BOF 를 통해 스택에 삽입하기

이렇게 만들어진 쉘 코드를 다음과 같이 스택에 삽입한 후 NOP 코드를 얼마정도 넣어서 리턴 어드레스 직전까전 닿게 한다. 그리고 리턴 어드레스를 쉘코드가 있는 곳의 주소값으로 덮어써버린다. 그러면 프로그램은 쉘 코드의 주소값으로 뛰게 되고 해커는 권한이 상승된 쉘을 얻게 된다.

  • image

bof6 ~ bof8 풀어보기

  • bof5 의 비밀번호를 알아내었으니 bof5 유저로 접속하고 bof6 의 비밀번호를 파헤쳐보세요.

  • 그리고 bof8 까지 풀어보세요.

  • 정답은 각각의 소스코드에 주석처리 되어 있습니다.


과제

HW.md 파일에 따라 과제를 하시면 됩니다. (발표를 하며 설명을 할 수 있어야 합니다)