[ftz] level 19: swimming in pink
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() 함수에 /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" 문자열을 찾자.
/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 |
댓글