min8282

[공통과정] 시스템 해킹/OOB, Format String 실습 및 메모리 보호기법 본문

K-Shield.Jr

[공통과정] 시스템 해킹/OOB, Format String 실습 및 메모리 보호기법

min8282 2024. 7. 18. 09:06

2. Out-Of-Boundary

  • 사용자 입력에 대한 검증이 없어 범위를 벗어나는 곳의 데이터를 접근할 수 있는 취약점

 

버퍼 오버플로우 vs. OOB

버퍼 오버플로우 같은 경우는 프로세스 흐름을 공격자가 원하는 흐름으로 바꾸기 위해 메모리 상의 복귀 주소를 변조(공격자의 입력값으로 덮어씌움) 시켜 공격자가 원하는 주소를 실행하게 하고, OOB는 사용자 입력에 대한 검증이 없어서 입력 범위를 벗어나는 곳의 데이터를 접근할 수 있는 차이가 있다.

 

 

실습 전 코드 실행 및 확인)

그림1. oob.c
그림2. oob 실행 결과

코드를 보면 알다시피 번호를 입력하면 버퍼에 있는 해당 번호의 값을 출력해주는 프로그램이다.

gdb 실행)

그림3. gdb ./oob

gdb를 이용하여 oob 파일 분석 시작

그림4. disass main

main 함수 디스어셈블링 수행

 

실행 흐름과 스택 할당 살펴보기)

그림5. 메인 함수 스택 할당(프롤로그)

  • PUSH rbp : rbp를 스택에 PUSH
  • mov rbp, rsp : rsp를 복사 후, rbp 이동
  • sub rsp, 0x40 : rbp - 0x40 한 값에 rsp 이동

그림6. 변수 설정

mov DWORD PTR [rbp-0x34], 0x2da : 0x2da는 730으로 temp 변수 할당하는 부분

그림7. 배열에 해당 값 할당

mov DWORD PTR [rbp-0x34], 0x1~0xa -> 그림7 

그림8. 안내문구 출력
그림9. 사용자 입력값 수신대기
그림10. 결과문구 출력

그림5부터 그림10까지 스택 할당에 대해 알아봤으니 실제 주소값과 값 확인ㄱㄱ

그림11. 브레이크 포인트 설정 후 실행

break point를 걸고 주소값과 값을 직접 확인 해보겠다. bp를 main 함수 0번에 설정하고, 실행한다.

그림12. mov, mov

프롤로그 부분은 실행했다 가정.

rax 이상한 값을 할당. rax에 대해서 현재는 우리가 알 수 없다?

그림13

xor -> 값을 0으로 초기화한다. why? 스택에서 변수 할당 전에는 0으로 초기화 하고 저장하기 때문

그림14

rbp - 0x34를 하면 dd9c인데 왜 dd98일까?

현재 8바이트씩 할당되는 운영체제(64비트)에서 분석중이다. 저장하는 변수가 integer라 4바이트씩 할당되기 때문에 8바이트를 4바이트만큼만 채우고 짤라서 쓰기 때문에 주소가 이상하게 보이는 거다. (그냥 참고해라~ dd9c가 맞다만 알기)

1 대입 확인 !

그림16

2 대입 -> rbp - 0x2c = dda4에 들어가야함. 마찬가지로 8비트씩 자르는 운영체제라 살짝 이상하게 나옴

그림17

배열에 변수를 끝까지 저장하고 scanf 부분까지 왔다.


3. Off-by-one

  • 경계 검사 시 하나의 오차가 있을 때 발생하는 취약점
  • 버퍼의 경계 계산 혹은 반복문의 횟수를 제대로 고려하지 못해 발생

off_by_one.c
off_by_one


4. Format String

  • 프로그래밍 시 함수 등에서 문자를 입력/출력 받기 위한 문자열 포맷
  • %d, %s, %c, %p와 같은 문자열

format string 예시

printf 함수와 같이 함수 이름이 f(formatted)로 끝나는 함수들은 포맷 스트링을 처리하는 경우가 많음

이런 함수 내부에는 포맷 스트링이 필요로 하는 인자의 개수와 함수에 전달된 인자의 개수를 비교하는 루틴이 존재하지 않음

포맷 스트링을 이용해 다양한 형태로 값을 출력할 수 있음

Format String 요소
fsb32_read.c

fsb32_read 실행

fsb32_read 실행 결과

 

format string 실행 - AAAA %p %p %p 입력

%p를 입력 값에 넣어서 format string 취약점 분석 시도

AAAA의 16진수가 두 번째 부분에 나타남

format string 실행 - BBBB %p %p %p 입력

이번에는 BBBB도 시도. 동일하게 BBBB에 대한 16진수 값이 나타남

즉, 4번째 인자와 첫 번째가 관련 있음을 알 수 잇다. 이 부분을 활용해서 공격!!

 

File.txt

이제 flag_buf의 주소값을 알아내야 하기 때문에 gdb를 활용해서 분석을 시도해야 한다.

우리는 fsb32_read 파일을 실행해서 Format String 버그를 활용해 File.txt 내용을 출력하는 것이 목표다.

disass main

123부터 149 사이를 확인해보면 flag_buf의 주소를 찾을 수 있을 것 같다. why? file read 하기 전 함수를 초기화 하는 부분이기 때문.

break point - 123에서 break

123부터 실행하기 위해 break point를 123으로 설정

 

실행

r로 실행

pwndbg> r

flag_buf의 주소

flag_buf의 주소는 찾음 = 0x804c060

이제 이 주소를 강제로 나타나게 해야 한다.

이제 다시 터미널로 이동

 

우리가 원하는 것은 flag_buf에 들어 있는 값을 알아내는 게 목표

입력값 설정 구조)

  • 첫 번째에는 flag_buf 주소를 넣고, => 주소값
  • 두 번째는 아무 거나 %x 넣고, => 포맷스트링 
  • 세 번째는 문자열을 출력해주는 %s를 넣어 준다. 내용을 확인할 것이기 때문에 %s(문자열)임!! => 포맷스트링 

컴퓨터는 주소를 반대로 읽는다. = 빅엔디안

  • 빅엔디안 : 1,2,3,4,5 순서대로 읽음 / 사람이 읽는 방식
  • 리틀엔디안 : 5,4,3,2,1 반대로 읽음 / 기계가 읽는 방식
  • 리틀에디안 쓰는 이유? -참고- x86은 리틀 / 다른 기기는 빅에디안 쓰기도 한다. 

최종 결과 - flag.txt 내용 출력 완료

따라서 주소값을 입력할 때

0x0804c060 -> [\x08]-[\x04]-[\xc0]-[\x60] 나눠지고, 해당 값을 리틀 에디안 방식으로 거꾸로 작성.

결론 : fsb32_read 파일에서 format string 취약점을 활용해 file.txt를 읽어낼 수 있었다.

 

참고)

주소를 1바이트씩 끊어 읽는 이유

  • 리틀 엔디안 방식에서 1바이트씩 끊어 읽는 이유는 메모리에 바이트 단위로 저장될 때 각 바이트의 위치를 정확히 파악하고, 그 순서를 바꾸는 것이 중요하기 때문입니다. 이를 통해 데이터가 올바르게 해석되고 처리될 수 있다.

파이썬 사용 이유)

  • "\x60\xc0\x04\x08"을 주소값으로 인식 해야 한다. but, 문자열로 인식하기 때문에 파이썬 사용. 즉, 입력값을 넣을 때 주소값을 단순 문자열로 인식하지 않고 주소로 인식하게 하기 위해서 파이썬을 사용한다.
  • 처음 입력할 때 AAAA %p %p %p를 진행 했지만 우리는 첫 번째와 세 번째 값이 관련이 있는 것을 알기 때문에 파이썬에서 인자를 3개만 입력한 것.

Format String Bug 2

실습 전 코드 실행 및 확인)

fsb32_write 실행
fs32_write

목표)

입력값에 포맷스트링 공격으로 changeMe 내용을 변경하는 것이 목표.

 

분석 순서)

  1. changeMe 주소 찾기
  2. 인자 위치 찾기
$ gdb fsb32_wirte

gdb를 사용해서 fsb32_wirte 디버깅 실행

changeMe break point 찾기

printf 함수가 발생하는 위치를 보고 break point 찾기.

여기서 changeMe = 0x42424242가 main 함수 위에 있기 때문에 저 위를 찾는 것은 아직 어려움

우리가 지금 보는 구조는 stack 을 들여다 보는 것임. changeMe(전역변수)는 BSS, Data에 있기 때문에 찾기 어렵다.

따라서 main 함수 안에 있는 changMe를 찾기. -> pirnf문 

changeMe의 값은 BBBB로 주소값은 0xffffce84. -> 땡! 틀렸습니다.

printf를 출력하기 위해 changeMe의 주소값이 아닌 값을 가지고 와서 스택 안에 넣고 출력 해야 하는데, 0xffffce84는 printf의 안의 changeMe의 주소였다.

changeMe의 주소값을 eax에 대입

99에서 eax로 changeME 주소 확인 후,

 + 0x28 = changMe 주소가 된다.

0x/8/04/c0/28

 

ebx + 더하면됨

0

6개는 포맷스트링 아무거나 입력 후, 7번째 포맷스트링을 잘 이용

첫 번째 인자와 7번째 인자가 관련 있는 것을 확인 AAAA = 0x41414141

리틀 에디안으로 뒤집으면 : [\x28] [\xc0] [\x04] [\x08]

 

  1. 리틀 엔디안 구조로 바꿔서 넣어준다.
  2. 323에 맞춰 문자를 넣어줘야 한다. -> %323c
    1. %c = 문자 1개
  3. %7$n -> 4개 + 323개를 붙여준다. 
// 최종 exploit
$ (python2.7 -c 'print "\x28\xc0\x04\x08%323&7$n"'; cat) | ./fsb32_write

실행 결과

%n이 길이값 정해주는 것. 즉, %7$n은 7번째에 넣기 위해서 


Format String Bug 3 - x64

목표)

someVar의 값을 변경 해야 한다.(64비트) 앞 문제와 크게 다르진 않지만, 실행 할 때마다 동일한 값을 입력해도 someVar의 주소가 달라진다.

 

64비트로 넘어가면 8바이트로 바뀐다. 32바이트는 4바이트였음

맛보기 문제라서 정답을 알려주시고 풀이를 해주심. 파이썬 스크립트로 문제를 풀 때는 이렇게 사용한다 정도 알려주셨다.

fsb.c

$ python3 exploit_fsb.py

exploit_fsb.py를 사용해서 fsb.c의 포맷스트링 취약점을 exploit 시도.

실행 결과

포맷 스트링 버그를 일으킨 것을 확인


메모리 보호 기법

메모리 보호 기법의 종류)

  • 취약점 자체를 보완하는 것은 아니지만 취약점을 통한 공격(Exploit)을 막는 방법
  • 표 이외의 다양한 메모리 보호 기법이 존재함


ASLR(Address Space Layout Randomization)

  • 스택이나 힙, 라이브러리 등의 주소를 랜덤으로 프로세스 주소 공간에 배치함으로써 실행할 때마다 데이터의 주소가 바뀌게 하는 기법

실습)

  • 명령어 : cat /proc/self/maps
  • ASLR을 적용했을 때 실행 시 주소가 바뀌는 것을 확인 가능

주소가 랜덤하게 계속 바뀐다. -aslr

코드분석을 할 때는 ASLR을 끄고 한다. 계속 바뀌면 안되니까

현재는 ASLR을 끄니까 변경이 안 됨.

echo 0은 끄기, 1은 켜기


NX(No-eXecute) bit

  • 메모리 영역과 쓰기에 사용되는 메모리 영역을 분리하는 보호 기법
  • 실행 가능한 메모리 영역이 아닌 스택, 힙과 같은 영역에서 코드가 실행되는 것을 막는 보호 기법

nx vs. non_nx

nx 파일과 non_nx의 NX가 걸린 것을 확인함

없애라 하는게 nx

 

dep -> 윈도우


ASCII-Armor

  • 공유 라이브러리 영역의 상위 주소에 0x00을 포함시키는 방법
  • RTL(Return To Library) 공격에 대응하기 위한 방법
  • 공격자는 NULL 바이트가 삽입된 주소로는 접근할 수 없음
  • NULL 바이트가 삽입되기 때문에 BOF 공격을 시도해도 공격 실행 중 라이브러리 함수를 호출 할 때 종료됨

Stack Canary

  • 스택 버퍼 오버플로우를 막기 위해 만들어진 기법으로 Canary 값을 삽입 및 변조 유무를 확인하여 방지
  • 스택상의 변수들의 공간과 SFP(Stack Frame Pointer) 사이에 Canary를 추가
  • stack smashing detected와 Aborted 에러는 스택 버퍼 오버플로우가 탐지되어 프로세스가 강제 종료되는 것
  • 스택 시작할 때랑 마지막 값을 저장해서 비교하는 것
  • -시작할 때마다 카나리가 랜덤하게 나온다.////

참고)

  • 케쉴주_공통과정_시스템 해킹 강의자료