취약점: gets() 함수는 버퍼가 over되어도 체크하지 않는다는 점에서 취약하다.
1) NOP 주소->ret
Nopsled 기법 중 NOP의 주소를 ret에 넣는 방법 사용
buffer+sfp+ret(NOP주소)+NOP+shellcode
ret 뒤의 NOP의 임의의 주소를 줘서 NOP에서 흘러가 shellcode가 실행될 수 있도록 하는 것이다.
2. ret 전까지 크기 구하기
함수 프롤로그 과정 이후(push %ebp/mov %esp, %ebp),
sub $0x10, %esp
스택을 16바이트 확장했다.
코드에서 char buffer[16];에 해당함을 알 수 있다.
따라서 buffer(16) + sfp(4) + ret(4)의 구조를 가질 것이며
ret 전에 buffer + sfp, 총 20바이트를 채워서 ret까지 오버플로우를 발생시키고 ret에 원하는 주소를 넣으면 된다.
2. NOP 주소 구하기
NOP의 주소를 구하기 위해 gets() 함수 호출 후 add명령 부분의 주소인 0x8048407에서 break했다.
(1번의 disass main한 것을 보면 알 수 있다.)
run으로 실행시켜서 A 20개에 ret을 덮고 NOP까지 고려해서 B를 줬다.
“A”는 buffer+sfp(총 20)을 채웠으므로 그 이후 ret + NOP + 쉘코드이므로 NOP은 최소 BBBBBBBBBBBBBBBBBBBB의 20 byte 정도 주면 선택한 NOP 주소에 NOP이 채워질 것이다. 그리고 NOP 이후에 쉘코드를 넣으면 ret에 담긴 NOP의 주소에 의해 NOP을 만나면서 흘러가서 쉘코드가 위치한 곳까지 갈 것이다.
x/63wx $esp로 확인해보면 0x41414141이 'A'로 채워진 것이고, 0x42424242가 'B'로 채워진 것이다.
NOP의 주소를 0xbffffb54로 선택했다.
3. payload 작성
처음에는 저렇게 표준입력으로 입력하는 방법을 시도했는데 계속 세그폴이 떴다.
그리고
이렇게 명령어를 >으로 파일에 저장하면 표준입력이 파일에 저장되는 것이므로 cat 명령어로 파일을 읽으면 될 줄 알았지만
실제 goblin 파일로 해보니까 permission denied가 떴다.
생각해보니까 다른 복사파일로 했을 때 명령어 저장 후 cat으로 한 것이 인자로 받은 것의 효과가 있었으니까, 명령어와 cat을 같이 goblin 파일에 주면 될 것 같았다.
(python -c ‘print(“A”*20 + “\x54\xfb\xff\bf” + “\x90”*20
+“\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x89\xe1\xcd\x80”)’; cat) | ./goblin
이렇게 작성했다. 앞의 명령어 2개를 goblin 파일에 대해 순차적으로 실행한다.
이렇게 쉘을 딸 수 있었다.
my-pass 입력하니까 다음 레벨의 패스워드를 얻을 수 있었다.
2) 환경변수 주소->ret
1. NOP+shellcode를 환경변수에 등록하기
NOP을 포함하여 쉘코드를 SHELLCODE라는 환경변수로 등록했다. NOP을 넣음으로써 NOP에서 쉘코드로 흘러가게 하려는 것이다.
2. 환경변수 주소 구하기
환경변수를 인자로 받으면 그것의 주소를 getenv로 구해서 출력하는 코드이다.
컴파일을 하고,
실행 시 아까 만든 SHELLCODE를 인자로 주면 이 환경변수의 주소를 알아낼 수 있다.
3. payload 작성
이전 방법에서와 같이 buffer+sfp는 A로 채워서 overflow가 발생하도록 한다. 그리고 ret의 주소에 아까 구한 환경변수의 주소를 넣는다.
이번에도 ;cat과 |를 사용한다.
whoami로 확인해보면 goblin으로 공격에 성공했고, my-pass로 패스워드도 얻었다.
이 밑의 내용은 이 블로그를 참고해서 작성했다.
3) 파이프를 사용하는 이유
1. 리다이렉션을 사용하는 경우
$ 명령어 > 파일
명령어의 표준출력을 모니터 대신에 파일에 저장한다.
$ 명령어 < 파일
명령어의 표준입력을 키보드 대신 파일에서 받는다.
python 명령의 표준출력을 input 파일에 저장한다.
->우리가 알고있는 "AAAAAAAA..."이 저장된다.
./goblin의 표준입력을 input 파일에서 받는다.
->표준입력은 그대로 표준출력하므로 input 파일 내용인 "AAAAAA..."을 그대로 출력하게 된다.
하지만 seg fault 오류가 생기고, core dumped된다.
core 파일이 생성된 것을 확인할 수 있다.
2. 파이프를 사용하는 경우
$ 명령어1 | 명령어2
명령어1의 표준출력이 파이프를 통해 명령어2의 표준입력이 된다.
stdin을 쉘 상에 전달하기 위해 pipe를 사용한다.
pipe를 사용하면 stdin을 전달받는 프로세스(./goblin)에서 stdout 오류 메세지가 출력되지 않는다.
core 파일은 생성된다.
따라서 파이프를 사용한다.
4) python 명령어와 cat 명령어를 그룹으로 묶어 사용하는 이유(;cat 사용 이유)
1. cat 명령어를 사용한 이유
printf 실행 전 gets() 함수에서 segmentation fault로 종료되었다.
문제가 생긴 이유는 gets() 함수에서의 stdin 때문이다.
이전 문제들에서처럼 인자로 payload를 전달할 경우에는 스택 프레임이 알아서 사이즈가 커진다.
하지만 stdin으로 payload를 전달할 경우에는 stdin으로 입력되는 크기에 상관없이 스택 프레임의 크기는 변하지 않아 문제가 발생한다.
따라서 위의 왼쪽 그림과 같이 스택 프레임보다 큰 입력을 줬을 때, 이 입력은 스택 프레임을 넘어서 kernel 영역을 침범하게 된다. kernel 영역은 유저 권한으로 접근이 불가능하여 seg fault가 발생하면서 gets() 함수에서 비정상 종료된 것이다.
해결방법은 스택 프레임 크기를 크게 만들거나 kernel 영역을 침범하지 않을만큼 payload의 길이를 줄이는 것이다.
스택 프레임의 크기를 크게 만드려면, stdin을 전달함과 동시에 매개인자를 전달한다. (이때 내용은 아무거나 상관없다고 한다.)
python 명령을 파이프를 사용해서 이 표준출력을 전달하는데, ./goblin 시 매개인자를 줘서 스택 프레임이 커질 수 있도록 했다. seg fault가 출력되지 않았다.
하지만 쉘도 뜨지 않는다. 왜 쉘이 실행되지 않은 것일까?
ltrace 명령어는 library 단위로 프로그램의 흐름을 tracing한다.
__libc_start_main 부분이 쉘코드가 실행되는 부분으로 쉘은 정상 실행되었지만 확인할 수 없었던 이유는
(이 부분이 제대로 안 되서 다른 블로그에서 가져왔다...)
read 함수는 쉘에서 명령어를 입력하는 부분이다. 정상적이라서 프로그램이 read에서 입력을 기다려야 하지만
read함수가 종료되고 쉘이 종료됐다.
python 명령이 payload를 전달한 후 쉘이 실행되는데 쉘은 stdin으로부터 EOF(End Of File)를 전달받는다.
cat >>으로 코드를 작성한 후 ctrl+D로 입력완료를 알려주는 것과 같은 효과이다.
따라서 입력이 끝났다고 간주하고 쉘이 종료된 것이다.
정리하자면, python에서 payload를 ./goblin에 stdin으로 전달하는 과정에서 쉘에 EOF를 전달하는 문제가 발생한다.
따라서 쉘이 계속 실행되도록 하려면 cat 명령어를 사용한다.
cat 명령어는 파일을 지정하지 않으면 표준입력을 받고 그대로 표준출력한다.
따라서 cat 명령어를 사용해서 cat의 역할로 계속 입력받도록 해서 쉘이 EOF를 받지않고 계속 입력받을 수 있도록 한다.
2. (python...; cat)으로 사용해야 하는 이유
이제 python 명령과 cat 명령을 같이 사용해야 하는 것을 알았다.
따라서
$ 명령어1; …; 명령어n
나열된 명령어들을 순차적으로 실행한다.
이 세미콜론(;)을 사용하여 두 명령어를 같이 사용하도록하면 된다.
하지만 여기서 또 문제가 발생한다.
이렇게 2개의 명령어를 세미콜론(:)으로 goblin에 stdin으로 전달하면, python 명령의 출력 결과가 출력되고,
cat 명령어로 입력받고 출력한다.
이것은 의도한대로의 결과가 아니다.
cat으로 쉘이 종료되지 않고 입력이 계속되도록 해야 하는데, cat 명령어로 종료되지는 않았지만 쉘은 이미 종료되었고 cat 명령어가 독자적으로 실행되었다.
따라서 괄호로 python 명령과 cat 명령어를 묶어줘야 한다.
$ (명령어1; …; 명령어n)
나열된 명령어들을 하나의 그룹으로 묶어 순차적으로 실행한다.
[LOB] level10: vampire -> skeleton (0) | 2019.05.01 |
---|---|
[LOB] level8: orge->troll (0) | 2019.04.28 |
BOF(Buffer Overflow) 공격-고전적인 방법 (0) | 2019.03.29 |
[LOB] gremlin -> cobolt (0) | 2019.03.28 |
[LOB] gate -> gremlin (0) | 2019.03.28 |