목차

write-up/pwnable

[ftz] level 11: what!@#$?

여니두 2019. 8. 21.

목차

11_포맷스트링과 버퍼 오버플로우

 

1. 포맷스트링 취약점

- 메모리에 저장된 값과 모니터로 보는 것은 다르다.

>> 2진수로 저장된 값을 우리가 인식할 수 있는 형태로 바꿔주는 것: 포맷 스트링 인자

%x / %d / %o / %c / %s / %f / %p: 변수의 주소 16진수 출력

 

√ 포맷스트링 취약점?

[ftz] level 11: what!@#$?
포맷스트링 인자를 사용하지 않는 코드
[ftz] level 11: what!@#$?

** -mpreferred-stack-boundary=2 옵션

: stack의 경계가 2byte 단위로 증가하도록 설정

 

[ftz] level 11: what!@#$?

- main+3> 지역 변수 공간 0xc(12byte) 할당

 

*swing 문자열 주소
*swu 문자열 주소
a 10
SFP  
RET  
argc 2
argv 명령어
env 환경변수

[ftz] level 11: what!@#$?

- 위 소스코드에서는 포맷 스트링을 사용하지 않고 printf(argv[1])만 했는데도 포맷 스트링을 사용한 경우와 같이 나온다.

- 하지만 소스코드에는 포맷스트링 지정자가 없기 때문에, 입력값에 포맷스트링 지정자를 입력하면

입력한 포맷스트링 지정자가 인식되어 stack을 살펴볼 수 있게 된다.

 

>> 포맷스트링 버그가 존재하는 지 확인

[ftz] level 11: what!@#$?

>> printf("%x")

- 8048415는 메모리 주소 같다는 느낌이 든다.

 

[ftz] level 11: what!@#$?

- %x의 개수만큼 메모리 주소와 같은 문자열이 출력됨.

 

[ftz] level 11: what!@#$?

- "a"와 "2"는 다른 출력값과 자릿수가 다르기 때문에 자릿수를 동일하게 맞춰 보겠다.

[ftz] level 11: what!@#$?

 

다시 디버깅하여 스택 프레임을 확인하자.

 

[ftz] level 11: what!@#$?

- printf(argv[1]) 부분에 bp를 걸어두고, 인자값과 함께 프로그램을 실행하였다.

 

[ftz] level 11: what!@#$?
[ftz] level 11: what!@#$?

- bp에 걸려서 멈춘 지점에서의 메모리 값을 출력하였다.

- 메모리 주소 하나씩을 출력해 보면 저장돼 있는 값이 나온다.

[ftz] level 11: what!@#$?

- SFP(스택 프레임 포인터)와 RET가 연속적으로 위치해 있는 것을 알 수 있다.

 

[ftz] level 11: what!@#$?
[ftz] level 11: what!@#$?

- argc, argv(못찾음), env가 순서대로 있음을 확인할 수 있다.

 

*swing 0x8048415
*swu 0x8048410
int value 0x0000000a
SFP  
RET 0x42015574
argc 2
argv  
env  

[ftz] level 11: what!@#$?

- printf()를 호출하기 전 함수의 인자를 전달하기 위해 스택값에 인자를 푸시한다.

- 지역변수에 대한 스택 구성이 끝난 후, 함수의 인자를 스택에 PUSH하고 함수를 호출한다.

 

RET 0x08048350
인자 전달 "%8x ~ "
*swing 0x08048415
*swu 0x08048410
int value 0x0000000a
SFP  
RET 0x42015574
argc 2
argv  
env  

 

 

2. BOF

√ 문제 분석

[ftz] level 11: what!@#$?

- attackme 파일에 setuid가 걸려있다.

 

[ftz] level 11: what!@#$?

 

[ftz] level 11: what!@#$?

gdb로 분석하기 위해 tmp 디렉토리로 복사

 

√ gdb로 소스코드 분석

[ftz] level 11: what!@#$?
procedure prelude 과정

함수의 프롤로그 부분

main+3> sub esp, 0x108

:스택에 264byte만큼 공간 만듦

str[256]+dummy[8] = 264

 

[ftz] level 11: what!@#$?

>> setreuid(3092, 3092)

 

[ftz] level 11: what!@#$?

>> strcpy(ebp-264, ebp+16)

= strcpy(str, argv[1])

 

[ftz] level 11: what!@#$?

>> printf(ebp-264)

= printf(str)

 

 

√ 공격

** BOF 공격

str 256
dummy 8
SFP 4
RET 4

RET에 우리가 실행하려는 쉘코드의 주소 값을 넣으면 그 주소로 JMP하여 쉘코드 실행 가능할 것이다.

따라서, RET을 덮기 위해 str[256] + dummy[8] + SFP[4] = 268byte를 덮어야 한다.

 

√ 페이로드

str[100] + SHELL CODE[24] + str[132] + dummy[8] + SFP[4] + str의 시작주소(RET 부분)

 

* SHELL CODE

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80

 

>> NOP[100] + SHELL CODE[24] + NOP[144] + str 시작주소(RET 부분)

*** NOP 넣는 이유: 혹시 주소 값이 변경돼도 NOP 슬라이딩으로 쉘코드를 실행시키고자

 

 

BUT

ASLR 보호 기법으로 인한 스택 주소 랜덤 변경 >> str의 주소가 계속 변경됨

√ ASLR 우회 기법인 RTL(Return to Library) 기법 이용

: 환경변수에 SHELL CODE 담아 실행

 

NOP[268] + 환경변수 주소

>> 환경변수 주소 --> 쉘코드 실행

 

*** 환경변수: 등록된 데이터는 고정적인 메모리 주소를 갖게 된다.

 

[ftz] level 11: what!@#$?

- 환경변수에 익스플로잇 코드 담기 (bash 쉘 띄워주는 코드)

 

[ftz] level 11: what!@#$?
getenv.c

- 환경변수의 주소 값을 print해주는 getenv.c 작성

 

[ftz] level 11: what!@#$?

 

[ftz] level 11: what!@#$?

Level 12 Password = it is like this

 

 

포맷스트링 취약점 공격으로

[ftz] level 11: what!@#$?
[ftz] level 11: what!@#$?
[ftz] level 11: what!@#$?

- printf() 에 bp를 걸고, 인자값을 전달하여 프로그램 실행한 후 전체 메모리의 내용을 확인

 

[ftz] level 11: what!@#$?

str 윗 부분: 인자의 주소

- 포맷스트링 지정자스택의 맨 위에 PUSH되어 있음

0xbffffc1c 4byte
0xbfffdcd0 4byte
0x00000001 4byte

0x41414141~

 

str[256]

dummy 8byte
0xbfffddd8 SFP
0x42015574 RET
0x00000002 argc
0xbfffde04 argv
0xbfffde10 env

 

- 파일을 실행하면서 포맷스트링 지정자를 입력했을 때 어떤 값이 출력되는지 보자.

 

[ftz] level 11: what!@#$?

- str 배열 앞에 있는 메모리 값이 보이고, 41414141(AAAA)이 출력되었다.

>> 포맷스트링 취약점이 있는 프로그램은 메모리 구조를 볼 수 있게 된다.

 

** 특정 메모리에 원하는 값을 쓰고 싶다면?

: 포맷스트링 공격의 핵심에 해당하는 %n 지정자를 이용

 

* 포맷스트링 이용한 바이트 쓰기 옵션

1. %n

- 인수: int *

- %n 이전까지 쓴 문자열의 바이트 수 쓰기

%바이트수c%n

2. %hn

- 인수: short *

- %hn 이전까지 쓴 문자열의 바이트 수 쓰기

%바이트수c%hn

 

- %n 지정자 사용 예제

[ftz] level 11: what!@#$?
[ftz] level 11: what!@#$?

출력 결과를 보면,

AAAA 문자열 뒤에 있는 %8x 지정자에 대한 반응으로 "AAAA %8x %8x %8x %8x" 포맷스트링 문자열보다

먼저 stack에 push된 값을 순차적으로 읽어오는 것을 볼 수 있다.

(나중에 다시 이해하기)

 

0xbffffc10 &(AAAA %8x~ )
0  
0  
AAAA(41414141)  

 - %n 지정자를 이용해 어떻게 값을 쓸 수 있을까?

"AAAA%8x%8x%8x%n" 입력

> AAAA를 입력한 후, %8x 지정자가 3개 있으므로 AAAA의 주소로 SP가 이동해 있을 것

> AAAA의 16진수 값인 0x414141에 해당하는 주소에 strlen(AAAA%8x%8x%8x%n)의 결과인 0x1c를 쓰려고 할 것

(0x414141은 접근할 수 없는 주소이니 쓰기에 실패, Segmentation Fault가 뜰 것)

 

>> AAAA 문자열 대신 우리가 쓸 수 있는 메모리 주소를 지정한 후, 문자열의 수를 조절하면 우리가 원하는 메모리 주소에 원하는 값을 쓸 수 있다.

 

[ftz] level 11: what!@#$?

- 변수 i의 값이 바뀐 것을 볼 수 있음

 

목표: 원하는 방향으로 실행 흐름 바꾸기 위해 복잡한 메모리 주소값을 덮어씌워야 함.

: 리틀엔디안 저장 방식까지 고려하여 덮어씌워야 한다!

- "AAAABBBB+변수주소+길이조절+%n" 패턴

[ftz] level 11: what!@#$?

 

* 정리

ex. 원래 printf() 함수의 주소가 있는 0x08041110 메모리 값을 셸코드의 주소로 바꾼다면,

포맷스트링 취약점을 가진 프로세스가 0x08041110 주소를 통해 실행하려는 순간 셸코드가 실행되면서 셸이 떨어지게 된다.

 

+ 3. 소멸자를 이용한 실행 흐름 변경 취약점

 

√ (포맷스트링 취약점 이용) 공격

포맷스트링에서 대표적으로 언급되는 중요한 호출 시점은 main() 함수의 소멸자다.

main() 함수가 종료되는 시점에 소멸자가 호출된다고 볼 수 있다.

>> 소멸자 역할을 수행하는 함수를 SHELL CODE로 흐름을 바꾼다면 SHELL이 떨어질 것!

 

[ftz] level 11: what!@#$?

- nm 명령어: 바이너리에 포함돼 있는 오브젝트의 심볼 목록을 볼 수 있음

- 생성자: __CTOR_LIST__, __CTOR_END__

- 소멸자: __DTOR_LIST__, __DTOR_END__ (주소: 0x08049610, 0x0804960c)

>> 실제로 값을 변조해서 실행 흐름을 바꿀 수 있는 주소는 __DTOR_END__

 

>> __DTOR_END__ 주소에 있는 값을 환경변수에 있는 셸코드의 주소로 덮어쓰면 된다.

 

(p280-289 나중에)

 

# 포맷스트링 절대값 지정자

 

$(printf "AAAA낮은주소AAAA높은주소")%8x%8x%8x%정수c%n%정수c%n

 

절대적인 위치 이동: %x 지정자를 %정수$x와 같이 입력

 

 

 

'write-up > pwnable' 카테고리의 다른 글

[ftz] level 13: have no clue  (0) 2019.08.23
[ftz] level 12: it is like this  (0) 2019.08.21
[ftz] level 10: interesting to hack!  (0) 2019.08.18
[ftz] level 9: apple  (0) 2019.08.17
[ftz] level 8: break the world  (0) 2019.08.16

댓글