• Pwn 学习 格式化字符串


    1.测试代码

    #include
    #include
    #include
    
    int init_func(){
        setvbuf(stdin, 0, 2, 0);
        setvbuf(stdout, 0,2 ,0);
        setvbuf(stderr, 0, 2, 0);
        return 0;
    }
    
    int dofunc(){
        char buf2[0x8];
        char buf[0x8] = {};
    
        int *p;
        buf[0] = 0x61;
        buf[1] = 0x62;
        buf[2] = 0x63;
        buf[3] = 0x64;
        buf[4] = 0x65;
        buf[5] = 0x66;
        buf[6] = 0x67;
        buf[7] = 0x68;
        strcpy(buf2, "deadbeef");
    
        printf("buf_str is %s\n", buf);
        printf("buf_addr_p is %p\n", buf);
        printf("buf_addr_x is %x\n", buf);
        printf("buf[0]_d is %d\n", buf[0]);
        printf("buf[0]_10d is %10d\n", buf[0]);
        printf("buf[0]_c is %c\n", buf[0]);
        printf("buf[0]_10c is %10c\n", buf[0]);
        printf("buf_str is %s\n", p);
        printf("buf_addr is %p\n", p);
        printf("buf_addr_p is %p,next %p,next %p,next %p,next %p,next %p,next %p,next %p,next %p\n", buf);
        return 0;
    }
    
    int main(){
        init_func();
        dofunc();
        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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    2.格式化字符串

    %[parameter][flags][field width][.precision][length]type
    
    • 1

    每一种 pattern 的含义请具体参考维基百科的格式化字符串 。以下几个 pattern 中的对应选择需要重点关注

    parameter

    • n$,获取格式化字符串中的指定参数

    flag

    • field width
      输出的最小宽度
    • precision
      输出的最大长度
    • length,输出的长度
      hh,输出一个字节
      h,输出一个双字节
    • type
    type符号
    d/i有符号整数
    u无符号整数
    x/X16 进制 unsigned int 。x 使用小写字母;X 使用大写字母。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空。
    o8 进制 unsigned int 。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空。
    s如果没有用 l 标志,输出 null 结尾字符串直到精度规定的上限;如果没有指定精度,则输出所有字节。如果用了 l 标志,则对应函数参数指向 wchar_t 型的数组,输出时把每个宽字符转化为多字节字符,相当于调用 wcrtomb 函数。
    c如果没有用 l 标志,把 int 参数转为 unsigned char 型输出;如果用了 l 标志,把 wint_t 参数转为包含两个元素的 wchart_t 数组,其中第一个元素包含要输出的字符,第二个元素为 null 宽字符。
    p,void * 型,输出对应变量的值。printf(“%p”,a) 用地址的格式打印变量 a 的值,printf(“%p”, &a) 打印变量 a 所在的地址。
    n不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
    %'%'字面值,不接受任何 flags, width。

    3.代码演示结果

    ┌──(kali㉿kali)-[~/Desktop/git/ctf-pwn/fmt_test1]
    └─$ ./fmt_test1     
    buf_str is abcdefghdeadbeef
    buf_addr_p is 0x7ffebf91a7c8
    buf_addr_x is bf91a7c8
    buf[0]_d is 97
    buf[0]_10d is         97
    buf[0]_c is a
    buf[0]_10c is          a
    buf_str is ���==/
    buf_addr is 0x56039f67e100
    buf_addr_p is 0x7ffebf91a7c8,next (nil),next (nil),next (nil),next 0x7ffebf91852c,next 0x56039f67e060,next 0x6867666564636261,next 0x6665656264616564,next 0x56039f67e100
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.修改源代码

        printf("buf_str is %s\n", buf);
        printf("buf_addr_p is %p\n", buf);
        printf("buf_addr_x is %x\n", buf);
        printf("buf[0]_d is %d\n", buf[0]);
        printf("buf[0]_10d is %10d\n", buf[0]);
        printf("buf[0]_c is %c\n", buf[0]);
        printf("buf[0]_10c is %10c\n", buf[0]);
        printf("buf_str is %s\n", p);
        printf("buf_addr is %p\n", p);
        printf("buf_addr_p is %p,next %10$p,next %p,next %p,next %p,next %p,next %p,next %p,next %p,next %p,next %p\n", buf);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.测试结果

    ┌──(kali㉿kali)-[~/Desktop/git/ctf-pwn/fmt_test1]
    └─$ ./fmt_test1
    buf_str is abcdefghdeadbeef
    buf_addr_p is 0x7ffef8963a08
    buf_addr_x is f8963a08
    buf[0]_d is 97
    buf[0]_10d is         97
    buf[0]_c is a
    buf[0]_10c is          a
    buf_str is ���==/
    buf_addr is 0x557e18117100
    buf_addr_p is 0x7ffef8963a08,next 0x7ffef8963a30,next (nil),next (nil),next (nil),next 0x7ffef896176c,next 0x557e18117060,next 0x6867666564636261,next 0x6665656264616564,next 0x557e18117100,next 0x7ffef8963a30
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以看到%10p的作用是直接打印第十个%p位置上的指针内容

    5.gdb调试

    在这里插入图片描述
    格式化字符串调用参数在x64中的顺序:
    rsi -> rdi -> rdx -> rcx -> r8 -> r9 -> stack
    通过格式化字符串可以无限泄露栈上的数据

    * 回复评论:如有不对欢迎批评

    在这里插入图片描述
    编译器是谁写的我不知道,但是RSI是不是第一个被print了

    * 确实有问题,我重新调了一下,很久没搞二进制了

    在这里插入图片描述

    按照结果来看
    RSI => RDX => RCX => R8 => R9 => RSP(这里开始就是stack了)

  • 相关阅读:
    技术分享 | MySQL 存储过程中的只读语句超时怎么办?
    Dart笔记:glob 文件系统遍历
    走访喜开路 | 共建全民大健康元宇宙服务体系预立项
    【C/C++内功心法】剖析预处理过程,详解其他预处理指令,提升C/C++内功
    【网管日记】linux服务器上apt-get换源
    2020最新Java面试题
    18444 分数拆分
    1-BIO最佳概述
    【代码随想录算法训练营】第49天 | 第九章 动态规划(十)+ 复习第20天 第六章 二叉树(五)
    APP自动化测试-8.移动端混合应用自动化测试
  • 原文地址:https://blog.csdn.net/MrTreebook/article/details/126430150