-
[HackCTF] SysROP Write-up표튜터와 함께하는 Pwnable/HackCTF Write-up 2019. 5. 7. 23:21
이번 문제는 SysROP이다. HackCTF를 풀면서 가장 많은 시간을 썼다.
새로운 공격기법을 배워서 기분은 좋다~ㅎㅎ 풀면서 Py0zz1한테 꿀팁도 많이 얻었다
바이너리는 이전 문제들과 마찬가지로 NX가 걸려있고
Partital RELRO이므로 Stack, Heap, Data 영역에
실행권한이 없으며 Got Overwrite가 가능하다.
바이너리를 실행시켜보니 사용자로부터 입력을 받는게 전부였다.
IDA를 이용해서 코드를 보도록 하자!!
역시 사용자로부터 read 함수를 이용해서 입력을 받았고
0x78 즉, 120개를 입력할 수 있었다.
어셈블리어 코드로 확인을 해보았다.
p main을 입력했지만 Symbol을 load할 수 없다고한다.
그 이유는 바로 stripped(안티디버깅)이 되어있어서이다.
그래서 IDA로 주소를 찾아서 진행하였다.(다른 방법을 사용해도 됨)
ret는 0x10 + sfp(8)로 총 24개의 값을 입력하게되면 만날 수 있었다.
그렇다면 문제를 어떻게 풀면될까? 굉장히 간단하게 생각했다.
바로 ROP로 그냥 풀면되는 것 같이 보여서 read 함수를 최대한
활용해서 필요한 주소를 leak하고 payload를 짜면 되겠지 라고 생각했다.
하지만... gadget부터가 없었다. 64bit였기 때문에 레지스터에 맞춰서
gadget을 사용해야했고 read 함수이므로 pop, pop, pop, ret 가 필요했다.
rdi, rsi, rdx 순으로 필요했다. 인자를 셋팅할 수 있는
gadget은 구할 수 있었지만 주소를 leak하는데 사용할 만한 함수가 없었다..
나름 열심히 뒤져보았는데 마땅한 함수는 없었고 이 문제는
다르게 접근해야한다는 것을 깨달았다. 바로 syscall을 이용하는 것이다.
syscall을 호출하는데 사용되는 레지스터들을 가진 gadget을 찾아보았다.
rax, rdx, rdi, rsi 가 필요했는데 마침 sub_4005E6 함수에서 찾을 수 있었다.
( 이전 gadget 그림에 같은 부분이 존재한다 )
그러므로 이 함수의 gadget과 64bit syscall table을 이용해서 공격을 진행하면 된다.
공격 진행에 앞서 syscall gadget도 필요한데 그 부분은 read 함수 내부에서
동작하고 있는 syscall gadget을 찾을 수 있었다.
하지만 해당 syscall을 호출하기위해 read@plt를 호출하게되면 eax가 0이 되서 read 함수만 호출되고
우리가 원하는 함수를 호출할 수 없다. 그러므로 우리는 read@got의 마지막 1바이트를
0x5e로 overwrite시켜서 read 함수 호출 시 syscall gadget이 동작할 수 있도록 만들어야한다.
그렇게 되면 read@plt를 호출 할 때마다 read( )가 아니라 syscall gadget이 동작 할 것이다.
ASLR이나 PIE 등의 Mitigation이 적용되더라도 offset에 대한 값은 항상
같기 때문에 이러한 방법을 사용할 수 있는 것이다.
이번에는 "/bin/sh"을 저장할 고정주소 영역을 구해보자!
고정주소를 가진 영역은 이번 문제에서는 처음으로 .data를 사용했다.
.bss는 stdout이 사용중이어서 마찬가지로 쓰기권한이 있는 .data를 이용했다.
이렇게 SysROP를 진행할 준비는 모두 마쳤으니 이제 Payload를 구성해보도록 하겠다.
< Payload >
원리는 다음과 같다. 24개의 dummy 이후 ret가 존재하는 것을 확인했으므로
24개의 dummy를 입력해준다. 그 뒤 syscall에 맞는 레지스터를 지정해주기위해
아까 찾은 gadget 주소로 ret를 덮어준다. 우리는 우선 "/bin/sh"을 실행시키기위해
고정주소를 가진 영역에 syscall을 이용해 read 함수를 호출하여 입력할 것이기 때문에
그에 맞는 레지스터를 셋팅해준다. rax(syscall_id)는 read@plt 호출 시 내부에 존재하는
mov rax, 0에 의해 rax가 0(read)으로 셋팅 될 것이므로 굳이 맞춰줄 필요가 없다.
다음으로는 아까말했던 syscall gadget을 동작시키기위해
offset이 항상 일정하다는 원리를 이용하여 read@got의 1바이트를 0x5e로 overwrite할 것이다.
하지만 바로 overwrite를 위한 payload를 구성하게되면 payload 길이가 120에 가까워져서 더 이상
exploit을 진행 할 수가 없다. 그러므로 우리는 다시 main을 호출해서 길이에 대한 문제를 해결하면 된다.
ret를 처음 시작주소(0x4005f2)로 주고 다시 payload를 구상한다.
방금 설명한대로 read 함수에 있는 마지막 1바이트를 0x5e로 overwrite해서
syscall gadget이 호출되도록 만들어주었다. 이제 read@plt가 호출되면 read@got 대신
syscall gadget이 호출 될 것이다. 이 때도 역시 이전 Payload에서 처럼 read 함수를 이용하여
overwrite 할 것이기 때문에 굳이 rax값을 바꿀 필요가 없어서 gadget을
0x4005eb로 주어 최대한 Payload를 줄였다.
이제 마지막으로 "/bin/sh"을 실행시키기위해서
execve에 맞게 레지스터를 셋팅해주면 된다. 여기서는 rax(syscall_id)를 59(execve)로
셋팅해줘야하므로 0x4005ea를 gadget으로 썼다. 그렇게 해서 read@plt를 ret로
진행하게되면 syscall gadget이 실행되면서 execve로 "/bin/sh"이 진행될 것이다.
execve("/bin/sh", NULL, NULL)
혹시나 read@plt를 이용하여 syscall gadget을 호출하는 것인데
rax를 0으로 맞춰주지 않아도 되는 이유가 궁금하다면 잘못된 생각이다.
read@got를 overwrite할 때까지는 read 함수를 호출하기 위해 rax를 0으로
해주는 것이 맞지만 overwrite이후 read@plt는 read가 아닌 syscall gadget를
호출하여 동작하는 것이기 때문에 ( read 함수를 호출해서 내부에 있는 syscall gadget을
사용하는 것이 아니라 syscall gadget자체를 호출하는 것이므로) 굳이 rax를 read로
맞춰주지 않아도 syscall gadget을 사용할 수 있으므로 원하는
함수의 rax(syscall_id)를 셋팅해서 그냥 사용하면 된다!!
이러한 문제를 풀 때 sleep를 사용하는 이유는 gdb를 이용할 때
사용자의 디버깅 속도가 너무 빠르거나 느리면 이미 send된
데이터를 받았거나 받지못함에 따라 디버깅이 제대로 동작하지 않을 수 있으니
sleep로 시간을 잘 조절해서 진행해주면 해결할 수 있다.
이렇게 구성된 Payload를 진행하게 되면~~
쉘이 따지면서 FLAG를 볼 수 있었다~~ 어려웠지만 syscall과
gadget이나 Payload 길이가 부족할 때 exploit하는 법을 배울 수 있었다.
반응형'표튜터와 함께하는 Pwnable > HackCTF Write-up' 카테고리의 다른 글
[HackCTF] Yes or no Write-up (2) 2019.05.26 [HackCTF] RTC Write-up (1) 2019.05.10 [HackCTF] Pwning Write-up (2) 2019.05.03 [HackCTF] g++ pwn Write-up (0) 2019.04.30 [HackCTF] 1996 Write-up (0) 2019.04.29 댓글