当字符只出现在堆内存中一次时,为什么这个程序会重复打印字符?
Why does this program print characters repeatedly, when they only appear in heap memory once?
我写了一个小程序来探索 C 中的越界读取漏洞,以便更好地理解它们;这个程序是故意的,有漏洞:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PAGE_SIZE 5
void print_welcome(void);
unsigned int get_pages(void);
char* string(char *str);
int main(void)
{
char stack_book[] = "This is the stack book.\n";
char *heap_book = malloc(50);
if(!heap_book)
{
fprintf(stderr, "malloc failed so we have to exit.\n");
return EXIT_FAILURE;
}
char *secret = string("secretinfo");
if(!secret)
{
fprintf(stderr, "Failed to allocate memory for secret\n");
return EXIT_FAILURE;
}
char answer[5] = { 0 };
unsigned int num_pages = 0;
strcpy(heap_book, "These are the contents of the heap book!\n");
print_welcome();
printf("First, do you prefer the stack or heap book (stack/heap)?\n");
fscanf(stdin, "%5s", answer);
printf("Now, how many pages do you want to print?\n");
fscanf(stdin, "%u", &num_pages);
if(strstr(answer, "heap") != NULL)
{
printf("Good choice! The heap book is a fantastic read. Printing...:\n\n");
size_t i;
for( i = 0; i < num_pages*PAGE_SIZE; ++i)
{
putchar(heap_book[i]);
}
}
else if(strstr(answer, "stack") != NULL)
{
printf("Excelente! The stack book is a splendid read. Printing...:\n\n");
size_t i;
for( i = 0; i < num_pages*PAGE_SIZE; ++i)
{
putchar(stack_book[i]);
}
}
else
{
printf("Come back when you're ready to read, childrens....\n");
}
putchar('\n');
free(heap_book);
free(secret);
return EXIT_SUCCESS;
}
void print_welcome(void)
{
printf("Welcome to speedreader 1.0!\nWe've loaded the books into memory, read at your own speed!\n");
}
char* string(char *str)
{
if(!str)
{
fprintf(stderr,"Runtime error: Bad pointer provided to string create function.\n");
return NULL;
}
size_t len = 0;
char *ret_ptr = NULL;
len = strlen(str);
ret_ptr = calloc(len+1, 1); //Account for null terminator
if(!ret_ptr)
{
fprintf(stderr, "Runtime error(string): calloc failed.\n");
return NULL;
}
strcpy(ret_ptr, str);
return ret_ptr;
}
当 运行 输入:heap\n100\n
,我得到输出:
Welcome to speedreader 1.0!
We've loaded the books into memory, read at your own speed!
First, do you prefer the stack or heap book (stack/heap)?
heap
Now, how many pages do you want to print?
100
Good choice! The heap book is a fantastic read. Printing...:
These are the contents of the heap book!
!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!
我的问题是:当 secretinfo!
只出现在堆上一次时,为什么这个程序打印 !secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!
:
(gdb)
0x4052a0: "These are the contents of the heap book!\n"
0x4052ca: ""
0x4052cb: ""
0x4052cc: ""
0x4052cd: ""
0x4052ce: ""
0x4052cf: ""
0x4052d0: ""
0x4052d1: ""
0x4052d2: ""
0x4052d3: ""
0x4052d4: ""
0x4052d5: ""
0x4052d6: ""
0x4052d7: ""
0x4052d8: "!"
0x4052da: ""
0x4052db: ""
0x4052dc: ""
0x4052dd: ""
0x4052de: ""
0x4052df: ""
0x4052e0: "secretinfo"
0x4052f1: ""
0x4052f2: ""
0x4052f3: ""
0x4052f4: ""
0x4052f5: ""
0x4052f6: ""
0x4052f7: ""
0x4052f8: "1[=12=]4"
0x4052fb: ""
我不明白为什么它重复 secretinfo!
这么多次。
CPU 信息(来自 1 个核心):
vendor_id : AuthenticAMD
cpu family : 23
model : 113
model name : AMD Ryzen 9 3900X 12-Core Processor
stepping : 0
microcode : 0x8701013
cpu MHz : 3792.869
cache size : 512 KB
physical id : 14
siblings : 1
core id : 0
cpu cores : 1
apicid : 14
initial apicid : 14
fpu : yes
fpu_exception : yes
cpuid level : 16
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl tsc_reliable nonstop_tsc cpuid extd_apicid pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ssbd ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xsaves clzero arat overflow_recov succor
bogomips : 7585.73
TLB size : 3072 4K pages
clflush size : 64
cache_alignment : 64
address sizes : 43 bits physical, 48 bits virtual
操作系统: Fedora 31
内核: Linux 5.8.18-100.fc31.x86_64
编译器:gcc (GCC) 9.3.1 20200408 (Red Hat 9.3.1-2)
链接器:GNU ld 版本 2.32-33.fc31
glibc: 2.30-13.fc31
编译命令:gcc ./oob_read.c -o ./oob_read -g3
如有必要,可以提供其他信息。
由于 stdout
是行缓冲的,putchar
不会直接写入终端;它将字符放入缓冲区,遇到换行符时刷新缓冲区。 stdout
的缓冲区恰好位于 heap_book
分配后的堆上。
因此在您的副本中的某个时刻,您 putchar
您的 secretinfo
方法的所有字符。它们现在位于输出缓冲区中。稍后,heap_book[i]
位于 stdout
缓冲区本身内,因此您会遇到 secretinfo
的副本。当你 putchar
它时,你有效地在缓冲区中稍远一点的地方创建了另一个副本,然后重复该过程。
您可以在调试器中验证这一点。在 glibc 上,stdout 缓冲区的地址可以通过 p stdout->_IO_buf_base
找到。在我的测试中,它刚好超过 heap_book
.
160 个字节
我写了一个小程序来探索 C 中的越界读取漏洞,以便更好地理解它们;这个程序是故意的,有漏洞:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PAGE_SIZE 5
void print_welcome(void);
unsigned int get_pages(void);
char* string(char *str);
int main(void)
{
char stack_book[] = "This is the stack book.\n";
char *heap_book = malloc(50);
if(!heap_book)
{
fprintf(stderr, "malloc failed so we have to exit.\n");
return EXIT_FAILURE;
}
char *secret = string("secretinfo");
if(!secret)
{
fprintf(stderr, "Failed to allocate memory for secret\n");
return EXIT_FAILURE;
}
char answer[5] = { 0 };
unsigned int num_pages = 0;
strcpy(heap_book, "These are the contents of the heap book!\n");
print_welcome();
printf("First, do you prefer the stack or heap book (stack/heap)?\n");
fscanf(stdin, "%5s", answer);
printf("Now, how many pages do you want to print?\n");
fscanf(stdin, "%u", &num_pages);
if(strstr(answer, "heap") != NULL)
{
printf("Good choice! The heap book is a fantastic read. Printing...:\n\n");
size_t i;
for( i = 0; i < num_pages*PAGE_SIZE; ++i)
{
putchar(heap_book[i]);
}
}
else if(strstr(answer, "stack") != NULL)
{
printf("Excelente! The stack book is a splendid read. Printing...:\n\n");
size_t i;
for( i = 0; i < num_pages*PAGE_SIZE; ++i)
{
putchar(stack_book[i]);
}
}
else
{
printf("Come back when you're ready to read, childrens....\n");
}
putchar('\n');
free(heap_book);
free(secret);
return EXIT_SUCCESS;
}
void print_welcome(void)
{
printf("Welcome to speedreader 1.0!\nWe've loaded the books into memory, read at your own speed!\n");
}
char* string(char *str)
{
if(!str)
{
fprintf(stderr,"Runtime error: Bad pointer provided to string create function.\n");
return NULL;
}
size_t len = 0;
char *ret_ptr = NULL;
len = strlen(str);
ret_ptr = calloc(len+1, 1); //Account for null terminator
if(!ret_ptr)
{
fprintf(stderr, "Runtime error(string): calloc failed.\n");
return NULL;
}
strcpy(ret_ptr, str);
return ret_ptr;
}
当 运行 输入:heap\n100\n
,我得到输出:
Welcome to speedreader 1.0!
We've loaded the books into memory, read at your own speed!
First, do you prefer the stack or heap book (stack/heap)?
heap
Now, how many pages do you want to print?
100
Good choice! The heap book is a fantastic read. Printing...:
These are the contents of the heap book!
!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!
我的问题是:当 secretinfo!
只出现在堆上一次时,为什么这个程序打印 !secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!secretinfo!
:
(gdb)
0x4052a0: "These are the contents of the heap book!\n"
0x4052ca: ""
0x4052cb: ""
0x4052cc: ""
0x4052cd: ""
0x4052ce: ""
0x4052cf: ""
0x4052d0: ""
0x4052d1: ""
0x4052d2: ""
0x4052d3: ""
0x4052d4: ""
0x4052d5: ""
0x4052d6: ""
0x4052d7: ""
0x4052d8: "!"
0x4052da: ""
0x4052db: ""
0x4052dc: ""
0x4052dd: ""
0x4052de: ""
0x4052df: ""
0x4052e0: "secretinfo"
0x4052f1: ""
0x4052f2: ""
0x4052f3: ""
0x4052f4: ""
0x4052f5: ""
0x4052f6: ""
0x4052f7: ""
0x4052f8: "1[=12=]4"
0x4052fb: ""
我不明白为什么它重复 secretinfo!
这么多次。
CPU 信息(来自 1 个核心):
vendor_id : AuthenticAMD
cpu family : 23
model : 113
model name : AMD Ryzen 9 3900X 12-Core Processor
stepping : 0
microcode : 0x8701013
cpu MHz : 3792.869
cache size : 512 KB
physical id : 14
siblings : 1
core id : 0
cpu cores : 1
apicid : 14
initial apicid : 14
fpu : yes
fpu_exception : yes
cpuid level : 16
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl tsc_reliable nonstop_tsc cpuid extd_apicid pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ssbd ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xsaves clzero arat overflow_recov succor
bogomips : 7585.73
TLB size : 3072 4K pages
clflush size : 64
cache_alignment : 64
address sizes : 43 bits physical, 48 bits virtual
操作系统: Fedora 31
内核: Linux 5.8.18-100.fc31.x86_64
编译器:gcc (GCC) 9.3.1 20200408 (Red Hat 9.3.1-2)
链接器:GNU ld 版本 2.32-33.fc31
glibc: 2.30-13.fc31
编译命令:gcc ./oob_read.c -o ./oob_read -g3
如有必要,可以提供其他信息。
由于 stdout
是行缓冲的,putchar
不会直接写入终端;它将字符放入缓冲区,遇到换行符时刷新缓冲区。 stdout
的缓冲区恰好位于 heap_book
分配后的堆上。
因此在您的副本中的某个时刻,您 putchar
您的 secretinfo
方法的所有字符。它们现在位于输出缓冲区中。稍后,heap_book[i]
位于 stdout
缓冲区本身内,因此您会遇到 secretinfo
的副本。当你 putchar
它时,你有效地在缓冲区中稍远一点的地方创建了另一个副本,然后重复该过程。
您可以在调试器中验证这一点。在 glibc 上,stdout 缓冲区的地址可以通过 p stdout->_IO_buf_base
找到。在我的测试中,它刚好超过 heap_book
.