엄범



get_userinput이 제일 소스가 길어서 거기에 취약점이 있을 것 같아 집중적으로 봤는데 정작 다른 곳에 있었음... 시간을 무지 낭비함.


풀고 나서 보니까 NX가 비활성화 되어 있었다 ㅡㅡ; 어쩐지 checksec으로 봤을 때는 disable로 뜨는데 stack에 x권한이 왜 있지? 싶었더라니... 반드시 nxtest를 사용해야겠다.


SROP같다.

```

gdb-peda$ checksec

CANARY    : disabled

FORTIFY   : disabled

NX        : ENABLED

PIE       : disabled

RELRO     : disabled


$ ldd ohce

        not a dynamic executable


start addr : 0x4000B0

```


Note ) 이런 바이너리는 HexLay가 이상하게 나올 수 있으니 주의! 완전히 이상하게 나오는건 아닌데, 어떤 부분을 빼먹거나 변수를 잘못 추적해서 HexLay만 보면 놓치는 부분이 생겨 시간이 굉장히 오래걸린다! HexLay와 gdb를 동시에 보면서 gdb의 instruction을 하나하나 따라가며 대조해보는게 훨씬 빠름.


Note ) `` sys_read``에 bp 거는 경우

원래는 `` sys_read``에 `` size(rdx)``를 넘는 문자열을 입력해도 반복문 내에서 `` syscall``할 때 마다 버퍼에 남아있는 문자열을 조금 씩 가져오기 때문에 문제가 발생하지 않지만, `` sys_read``에 bp를 거는 경우 `` size``를 초과하는 부분은 애초에 버퍼로 들어가지 않아 버퍼가 비어있게 된다. 따라서 다음 `` syscall`` 시 처음 입력 받는 것 처럼 입력을 대기하게 되므로 주의.

`` syscall`` 바로 다음 instruction에 bp 걸고 디버깅하면 입력하는 데이터가 stack/register context에 보이기 때문에 편하다.


이상한 부분 #1 get_userinput

rsp(top) + rbx(idx)가 buf의 주소(rbp-0x20~rbp 지점)이며 sys_read시 여기에 받는다.

buf가 가득 찰 때 까지(0x20) 입력 받다가, 가득 차면 stack을 0x20 확장하고 buf 위의 공간 전체를 0x20만큼 위로 옮겨쓴다(이동시킨다.)

여기가 핵심일 것 같아서 자세히 보느라 시간을 엄청 많이 소모함.

실제로 rbx(idx)를 `` LOWORD()``로 계산하기 때문에 `` 0xffff``가 넘어가면 `` 0x0``이 되어버린다.


leak #2 echo

`` \n``포함 `` 0x20`` byte만큼 입력하면 그 뒤에 있는 `` sfp``가 leak된다. 근데 #1 때문에 0x20을 초과해서 입력해봐야 소용없고, 0x20 배수로 입력해도 동일하게 동작한다.

rbp 조작 #3 echo_reverse

#2와 동일하게 `` 0x20``만큼 입력했더니 leak되면서 fault가 발생한다. 이어져있는 `` sfp``까지 포함해서 reverse해버리기 때문에, `` rbp``가 변경되면서 fault가 발생.

VULN ) 따라서 `` rbp``를 조작할 수 있으며, 바로 이어지는 다음 gadget을 고려하면 `` rsp -> ret``도 조작할 수 있다.
```
   0x40018b:    mov    rsp,rbp
   0x40018e:    pop    rbp
=> 0x40018f:    ret    

```


``c "/bin/sh"`` 등의 마땅한 string이 바이너리에 없기 때문에, stack에 입력하여 사용해야 한다.

#3을 이용해 leak하려면 leak을 수행하고 나서 바이너리가 종료되지 않도록 하기 위해 `` rbp``가 적당한 곳에 위치하도록 해야한다.

만만한 곳이 .data section이나, get_userinput에서 ``c \x00``을 받으면 buf에 저장되기는 하지만 `` length``가 ``c \x00`` 전까지만 계산된다.

그러면 `` rbp``에 stack을 지정할 수 밖에 없으므로 바이너리가 종료되는 것을 막을 수 없다.

따라서 그냥 #2를 이용해 leak하는게 좋다.



```

leaked sfp : 0x7fffffffe2e0


------Layout------

buf (e2b0)  : e2c0

rbp-0x18 : 

rbp-0x10 : sfp

rbp-0x08 : ret

rbp (e2d0)  : e2e0

rbp+0x08 : ret

e2e0 : 0x0000000000000000

-----reverse------

buf      : e2e0

rbp-0x18 : 

e2c0 : ===sfp=== 어딘가로...

rbp-0x08 : ===ret===

e2d0 (rbp)  : e2c0

rbp+0x08 : ret

```


  1. `` 0x20`` 배수로 parameter를 입력해두고,
  2. 어딘가로 ret해서 `` RAX : 0xf``로 만들고
  3. 다시 syscall해서 `` sigreturn`` 실행.


set rax

바이너리에서 제공하는 `` sys_read``는 get_userinput 뿐인데 `` rdx(size) == 1``로 고정이다. 그래서 이를 이용해 `` rax``를 설정할 수는 없다.

그렇다고 `` rdx``를 조작할 수 있는 gadget이 있는 것도 아니라서, `` sys_read``를 사용하는 방법으로는 어렵다.


대신 echo 메뉴의 `` sys_write``를 사용하면 가능할 것 같다. 15개 입력하고 15개 출력하도록 하면 되니까.

reverse 되면 arg 넘기기가 귀찮아지기도 하고, reverse는 0x00에서 에러가 발생하기 때문에 arg넘기는건 1. echo를 사용해서 넘기고, rbp 조작만 2. echo(reverse)를 사용하도록 한다.

data top

fake_sfp2

 

 ret (echo_addr)


ret (syscall_addr)

 sigreturn 직전 rsp

 /bin/sh\00

 0x20 dummy

 ..... sigcontext ( 256, 0x100 ) .....


  1. echo를 이용해 leak.
  2. echo를 이용해 payload 입력해두기.
  3. reverse에 `` 0x20``만큼 입력하며 rbp가 입력해둔 payload(data top)를 가리키도록 설정.
  4. 다시 echo로 ret해서 `` RAX : 0xf``로 만들고
  5. syscall해서 `` sigreturn`` 실행.

https://github.com/umbum/pwn/blob/master/exploit/secu_ohce.py