write-up/pwnable

[ftz] level 19: swimming in pink

여니두 2019. 8. 27.

19_공유 라이브러리를 이용한 버퍼 오버플로우

 

- 버퍼 오버플로우 차단 기법

ex. 스택가드(Stack Guard), 보안 쿠키(Security Cookie), 실행 차단 메모리(Non Executable Memory), 주소 난독화 (ASLR, Address Space Layout Randomization), 시큐리티 코딩(Security Coding) 등

 

- 우회 기법 대표: 공유 라이브러리를 이용한 버퍼 오버플로우 기법 RTL(Return To Libc)

 

√ RTL(Return To Libc)

system.c

- system() 함수에 /bin/sh 인자가 어떻게 전달되는지 보자.

 

- system() 함수 호출 전 bp를 걸고 실행시킨 다음 system() 함수로 진입하였다.

- RET 주소 앞에 /bin/sh 인자값이 먼저 PUSH되어 있는 것을 볼 수 있다.

 

...
RET
"/bin/sh"
...

- RET과 인자의 순서를 기억해야 하는 이유: RTL 공격에서 인자를 어떻게 전달해야 하는지 알아야 하기 때문

 

RTL 공격: 특정 메모리의 주소를 원하는 값으로 바꿀 수 있다는 BOF의 응용

 

>> BOF의 개념을 어떻게 RTL로 응용했을까?

- 지금까진 RET만 우리가 원하는 실행코드의 주소로 바꿨지만,

- RTL은 셸코드를 올릴 수 있는 공간이 제공되지 않은 상황이라도 방법을 바꾸면 공격 가능

 

- RTL을 가능하게 하려면 RET을 system() 함수의 주소로 바꾸면 될 것

- 하지만 system()은 /bin/sh와 같은 프로그램을 인자로 전달해야 하므로, 정확한 위치에 인자를 전달해야 함

 

char str[256]
SFP
RET [ -> &system() ]
RET of system()
&(/bin/sh)
...

(서브루틴 한번 더 읽기)

- 정확히 덮어쓴다면, 취약한 프로그램의 함수가 끝나는 RET에서 system() 함수가 실행될 것

- 이 때 실행되는 system() 함수는 8byte 앞의 stack에 PUSH돼 있는 "/bin/sh"라는 문자열을 인자로 인식해서 실행할 것

>> system("/bin/sh")이 실행되어 셸을 딸 수 있을 것!

 

 

√ 문제 분석

- buf 배열은 20byte로 선언되어 있다.

- gets()로 제한없이 받을 수 있는 BOF 취약점이 있다.

 

- RET에 공유 라이브러리 주소를 올려서 crack해보면?

: system() 함수 주소를 찾아서 실행 흐름을 바꿔보자.

 

√ gdb로 소스코드 분석

- stack에 40byte 공간 확보

: buf[20] + dummy[20]

 

>> gets(ebp-40)

= gets(buf)

>> printf("%s\n, ebp-40)

= printf("%s\n, buf)

 

0xbffff0b0 ~ 0xbffff0d8 이전까지가 확보된 것을 알 수 있다.

 

 

buf[20]
dummy[20]
SFP
RET

√ 공격

- NOP[20] + SHELL CODE + "buf 배열 시작 주소"

실패

: 실행 파일명의 길이에 따라 buf[] 배열의 메모리 주소가 바뀌는 문제와 관계가 있음

( > 에그셸 이용해야 함)

: 성공하더라도, level19로 배시셸이 나타날 것이다.

소스코드에 setreuid() 함수가 없기 때문이다.

 

>> 셸코드에 setreuid(3100, 3100)을 추가해야 한다.

 

* setreuid() 함수

: 프로그램 실행 시, 마치 리눅스에서 SUID, SGID 설정을 주는 것과 같은 개념

 

 

√ 다시 RTL Chaining 기법을 이용하여 공격해보자.

- main() 함수의 스택프레임에서 RET을 4byte의 system() 함수의 주소로 덮어야 함.

- 이 4byte 앞에(stack) system() 함수의 RET가 있어야 함.

- 또 다시 4byte 아에 셸을 실행시킬 인자인 "/bin/sh" 문자열의 주소가 있어야 함.

buf[20]  
dummy[20]  
SFP  
RET &system()
... RET of system
... &(/bin/sh)

** 공격 순서

1. 해당 바이너리에 포함돼있는 system() 함수, setreuid() 함수의 메모리 주소 확인

- main의 첫번째에 bp를 걸어준 후, 함수들의 주소를 찾아보았다.

system() 함수 주소: 0x4203f2c0

setreuid() 함수 주소: 0x420d7920

 

 

2. system() 함수 내부에 존재하는 /bin/sh 문자열의 주소 획득

 

- "/bin/sh" 문자열을 찾아야 하는데, RTL은 한방 공격이기 때문에

execve() 함수 내부에 존재하는 "/bin/sh" 문자열을 찾자.

 

whereissh.c

/bin/sh 문자열 주소: 0x42127ea4

 

 

3. setreuid 인자 정보

 

* setreuid(sub_t ruid, uid_t euid)
ruid: 실제 ID
euid: 프로세스에 적용할 ID

level20의 UID: 3100

(페이로드 작성 시, HEX 값인 0x00000C1C로 변환 후 사용)

 

 

4. chaining을 위한 pop과 ret 명령어 그룹

 

pop-pop-ret

2개의 인자가 사용될 것이므로, pop 2개와 ret 1개로 구성된 명령어 그룹이 필요하다.

 

- pop-pop-ret이 모두 연결되어 나오는 부분: 804849d

PPR 주소: 0x804849d

 

 

>> 최종 페이로드

: NOP[44] + setreuid() 시작 주소 + PPR 주소 + ruid 값 + euid 값 + system() 시작 주소 + NOP[4] + /bin/sh 주소

 

Level20 Password = we are just regular guys

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

[LOB] level 1: gate  (0) 2019.09.14
[ftz] level 20: we are just regular guys  (0) 2019.08.27
[ftz] level 18: why did you do it  (0) 2019.08.27
[ftz] level 17: king poetic  (0) 2019.08.26
[ftz] level 16: about to cause mass  (0) 2019.08.24

댓글