PE - Datei an Executable anhängen

Hi, ich hatte vor ein Programm zu schreiben, mit dem man den ausführbaren code einer ausführbaren Datei an eine andere hängt.
Z.B. um ne Passwortabfrage vorzuschieben. Außerdem wollte ich dann noch ne GUI zu schreiben.
Leider habe ich ein paar Probleme mit der grundsätzlichen Umsetzung. Ich kriege nen Pagefault bzw. nachdem ich den Charakterflag auf executable umgestellt habe eine "frame is not in stack limits".
Ich wäre froh wenn mir jemand helfen würde, das ding fertig zu machen. Bei fragen zum Code oder C Programmierung allgemein helfe ich generell auch gerne. Ich poste mal den Code und schreibe was er macht.

Funktionsweise ist:
den header wie ihn die PE_header struct darstellt einlesen (klappt)
dann den verändere ich die Werte wie folgt:
- errechne den entry point
- addiere den errechneten entry point mit der alten PE_header.VirtualSize
- erhöhe den SizeOfImage mit der größe des Anhängsels
- mache das gleiche mit der VirtualSize
- und mit der SizeOfRawData
- setze die characteristics auf executable

In diesem Fall möchte ich dass statt der Ausführung von Diablo^^ ein "Hello World" erscheint. Ich teste das btw. immer mit wine^^

Das sind ein paar Typen die ich erstellt habe, da ich unter Linux programmiere und kein DWORD und WORD zur verfügung steht. ich bin mir nicht sicher ob ihr wisst, was ein union ist. (alles in einem union teilt sich den selben speicher) Mit diesen Datentypen kann ich zb. ne zahl über das valu zuweisen und in den filebuffer über das bytearray schreiben. an sich ist das verdammt praktisch.
Code:
/*
 * WORD
 */
union WORD {
	unsigned short value;
	char BYTE[2];
};

/*
 * DWORD
 */
union DWORD {
	unsigned long value;
	char BYTE[4];
};

struct PE_HEAD {
	DWORD HeaderAdress;

	WORD NumberOfSections;		// 06
	DWORD AddressOfEntryPoint;	// 28
	DWORD OEP;
	DWORD ImageBase;			// 34
	DWORD SizeOfImage;			// 50

	/*
	 * properties of last section required
	 */
	DWORD VirtualSize;			// [PE+0xF8]+(NumberOfSections-1)*0x28 + 08
	DWORD VirtualAddress;		// [PE+0xF8]+(NumberOfSections-1)*0x28 + 0C
	DWORD SizeOfRawData;		// [PE+0xF8]+(NumberOfSections-1)*0x28 + 10
	DWORD Characteristics;		// [PE+0xF8]+(NumberOfSections-1)*0x28 + 24
};

Dann habe ich noch den restlichen code. angeführt von ner funktion die die Datei einliest, noch nix besonderes:
Code:
/*
 * reads the file binary
 */
inline unsigned long readFile(char* pathToFile, char* &byteArray){
	/*
	 * open file
	 */
	FILE *fin = fopen(pathToFile, "r");
	if (fin == NULL)
		return false;

	/*
	 * get file size
	 */
	struct stat buf;
	stat(pathToFile, &buf);
	unsigned int fileSize = buf.st_size;
	//and allocate memory
	byteArray = new char[fileSize];

	/*
	 * readfile now
	 */
	fread(byteArray, sizeof(char), fileSize, fin);

	fclose(fin);
	return fileSize;
}
Diese Funktion liest den bytecode durch und speichert die headerinformationen in die struct die ich oben definiert habe. ich habe die hex-werte mit intwerten ersetzt, damit ich über das array drauf zugreifen kann.
Code:
/*
 * seek for offsets
 * modify global variables
 *
 * return some header stuff via function argument
 * returns the position of the PE header via return
 */
inline DWORD parseFile(const char* byteArray, unsigned long sizeOfArray, PE_HEAD &header) {
	DWORD header_adress;
	/*
	 * PE OFFSET
	 * PE offset is allways at 3C == 60dez
	 */
	memcpy(header_adress.BYTE, byteArray+60, 4);
	/*
	 * SECTIONS OFFSET
	 * 06 == 6dez after PE entry point
	 */
	memcpy(header.NumberOfSections.BYTE, byteArray+header_adress.value+6, 2);
	/*
	 * ENTRY POINT OFFSET
	 * 28 == 40dez after PE entry point
	 */
	memcpy(header.AddressOfEntryPoint.BYTE, byteArray+header_adress.value+40, 4);
	/*
	 * ENTRY POINT OFFSET
	 * 32 == 52dez after PE entry point
	 */
	memcpy(header.ImageBase.BYTE, byteArray+header_adress.value+52, 4);
	header.OEP.value = header.AddressOfEntryPoint.value + header.ImageBase.value;
	/*
	 * IMAGE SIZE
	 * 50 == 80dez after PE entry point
	 */
	memcpy(header.SizeOfImage.BYTE, byteArray+header_adress.value+80, 4);
	/*
	 * PROPERTIES OF LAST SECTION
	 * [PE+0xF8]+(NumberOfSections-1)*0x28
	 */
	memcpy(	header.VirtualSize.BYTE,
			byteArray+(header_adress.value+248+(header.NumberOfSections.value-1)*40) + 8, 4);
	memcpy(	header.VirtualAddress.BYTE,
			byteArray+(header_adress.value+248+(header.NumberOfSections.value-1)*40) + 12, 4);
	memcpy(	header.SizeOfRawData.BYTE,
			byteArray+(header_adress.value+248+(header.NumberOfSections.value-1)*40) + 16, 4);
	memcpy(	header.Characteristics.BYTE,
			byteArray+(header_adress.value+248+(header.NumberOfSections.value-1)*40) + 36, 4);

	header.HeaderAdress = header_adress;
	return header_adress;
}

das hier ist wohl die kritische funktion, weil sie den header editieren muss, hier ind vermutlich die logischen fehler drin.
Code:
/*
 * overwrites the old entrys with new ones
 * deltaSize == size of NEW code
 */
inline void modHeaderProperties(char* &byteArray, PE_HEAD PE_header,
								/*DWORD newEntryPnt,*/ unsigned long deltaSize)
{
	unsigned long m_lTmp;
	DWORD m_dTmp;
	DWORD header_adress = PE_header.HeaderAdress;

	/*
	 * ENTRY POINT OFFSET
	 * 28 == 40dez after PE entry point
	 */
	m_dTmp.value = PE_header.OEP.value + PE_header.VirtualSize.value;
	memcpy(byteArray+header_adress.value+40, m_dTmp.BYTE, 4);

	/*
	 * IMAGE SIZE
	 * 50 == 80dez after PE entry point
	 */
	m_dTmp.value = PE_header.SizeOfImage.value + deltaSize;
	memcpy(byteArray+header_adress.value+80, m_dTmp.BYTE, 4);
	/*
	 * PROPERTIES OF LAST SECTION
	 * [PE+0xF8]+(NumberOfSections-1)*0x28
	 */
	m_dTmp.value = PE_header.VirtualSize.value + deltaSize;
	memcpy(	byteArray+(PE_header.HeaderAdress.value+248+(PE_header.NumberOfSections.value-1)*40) + 8,
			m_dTmp.BYTE, 4);
	/*
	 * last section begin
	 */
/*
	m_lTmp = PE_header.HeaderAdress.value+248+(PE_header.NumberOfSections.value-1)*40;
	m_dTmp.value = m_lTmp;
	memcpy(	byteArray+(PE_header.HeaderAdress.value+248+(PE_header.NumberOfSections.value-1)*40) + 12,
			m_dTmp.BYTE, 4);
*/
	m_lTmp = PE_header.SizeOfRawData.value + deltaSize;
	m_dTmp.value = m_lTmp;
	memcpy(	byteArray+(PE_header.HeaderAdress.value+248+(PE_header.NumberOfSections.value-1)*40) + 16,
			m_dTmp.BYTE, 4);

	m_dTmp.value = 140000020;
	memcpy(	byteArray+(PE_header.HeaderAdress.value+248+(PE_header.NumberOfSections.value-1)*40) + 36,
			m_dTmp.BYTE, 4);
}

naja und der rest macht im prinzip folgendes:
-prepareTag soll die Section Table der exe auslesen
-addTag soll diese Tables anhängen

Code:
/*
 * parse and cleave a file whicht will get insertet
 * to the file which will get injected
 */
inline unsigned long prepareTag(char* pathToFile, char* &byteArray) {
	char *buffer;
	int size = readFile(pathToFile, buffer);

	/*
	 * look for entry point
	 */
	PE_HEAD header;
	parseFile(buffer, size, header);

	unsigned long begOfSecs = header.HeaderAdress.value + 248;

	/*
	 * delete all before of the entry point
	 * i think we shouldn't need this shit
	 */
	int new_size = size - begOfSecs;
	byteArray = new char[new_size];
	memcpy(byteArray, buffer+begOfSecs, new_size);

	return new_size;
}

/*
 * modifies the jump adress
 * set to end of file or something
 *
 * returns the modded file via function argument
 */
inline unsigned long addTag(	const char* byteArray, 		unsigned long sizeA,
								const char* modByteArray, 	unsigned long sizeM,
								char* &editedFile) {
	editedFile = new char[sizeA+sizeM];

	/*
	 * load base file into buffer an appen moded file
	 */
	memcpy(editedFile, byteArray, sizeA);
	memcpy(editedFile+sizeA, modByteArray, sizeM);

	return sizeA+sizeM;
}


int main(void) {
	PE_HEAD header;
	char *buffer_base, *buffer_add, *buffer_inj;
	unsigned long size_base, size_add;

	size_base = readFile("diablo.exe", buffer_base);		//reads the file and save to buffer
	parseFile(buffer_base, size_base, header);	//looks for PE header
	//cout<<"entry point before: "<<header.AddressOfEntryPoint.value<<endl;

	size_add = readFile("hallo.exe", buffer_add);			//prepares other file for injections
	unsigned long new_ln = addTag(buffer_base, size_base, buffer_add, size_add, buffer_inj);

	modHeaderProperties(buffer_inj, header, size_add);

	//parseFile(buffer_inj, new_ln, header);
	//cout<<"entry point after: "<<header.AddressOfEntryPoint.value<<endl;

	/*
	 * write new file
	 */
	FILE *fout = fopen("inf.exe", "w");
	if (fout == NULL)
		return false;
	fwrite(buffer_inj, sizeof(char), new_ln, fout);
	fclose(fout);

	return EXIT_SUCCESS;
}
 
Grundsätzliche Fragen (so gut kann ich nun mal kein C ;) )
Du setzt Characteristics auf "Read"+"Execute" (nicht nur Execute) ?
Hast Du schon mit einem PE Editor oder ähnlichem geprüft, ob Du einen gültigen PE Header zusammenbaust?
Optimal wäre ein Start in einem Debugger wie OllyDbg.

Sofert PE Header korrekt ist:
Was genau fügst Du an die Ziel-Executable an? Eine Section oder kompletten Code?
Bist Du Dir auch im klaren dass der angefügte Code keine externen Funktionen aufrufen darf (zumindest nicht direkt) ? Denn diese nutzen zur Auflösung die ImportTabelle - welche wiederum erst beim Start der Executable durch den PE-Loader gefüllt wird (und da der code angehängt wird, weiß der PE Loader erstmal gar nichts von dieser Tabelle - geschweige denn dass die ganzen Virtuellen Adressen im angehängten Code sich alle "verschieben")
 
Ich habe es vorerst mit dem kompletten code probiert und dabei den entry point mit dem tag addiert. beim hinzufügen der section müsste ich das vermutlich nicht. allerdings glaube ich nicht dass ich so weiterkomme.
ich habe evtl dran gedacht das programm an den anfang der datei zu schieben und dann das ursprüngliche programm neu auszulesen und dann auszuführen, aber wie kann ich ein array direkt ausführen? ich will nicht auf festplatte zwischenspeichern. mal davon abgesehen, dass das nicht sonderlich elegant ist.
 
Also:
1)sollte geklärt werden, ob PE Header korrekt zusammengesetzt wird (samt flags).
Wenn Du Ziel,Anhang und Ergebnis Exen hier anhängen würdest, könnte man diese durchsehen.
2)sofern PE Header korrekt ist:
wie gesagt, einfach Code anhängen ist nicht drin. Je nach Compiler werden nämlich schon bei einer "leeren" C-Executable mit "main(){}" einige externe Aufrufe (wie getArgs, ExitProcess) generiert. Testweise könnte man stattdessen mit Nasm eine "minimalexe" generieren und schauen, ob das technische Anhängen klappt:
Code:
[segment .data]
dummy db "hello",0


[segment .text]
[global main]

main:
		push dword 0
		pop dword [dummy]
		loop_: jmp loop_
		ret
Code:
nasm -fwin32 minimal.asm
ALINK.EXE -c -oPE minimal.obj -entry main
(hab die Binary mal drangehangen)
der Sinn des Codes: keine externen Aufrufe, dafür ein LOOP in der mitte.
D.h wenn das Anhängen klappt und man die Exe startet, sollte sie "hängenbleiben" - man kann sie dann mit einem Debugger attachen und schauen, ob alles korrekt verläuft (zumindest sinnvoll, wenn man noch mehr Code hinzufügt).

ich habe evtl dran gedacht das programm an den anfang der datei zu schieben und dann das ursprüngliche programm neu auszulesen und dann auszuführen, aber wie kann ich ein array direkt ausführen?
wie stellst Du es Dir vor? Wer soll denn die Initialisierungen im zweiten Programm vornehmen? ;) Also Sections richtig mappen, eventuelle Relocations durchgehen und natürlich Imports auswerten und die ImportTabelle mit Werten füllen.
 
also ich test mal:
die parse funktion liefert durchweg richtige werte von diablo.exe, sucht aber nur nach folgenden daten:
cout<<"AddressOfEntryPoint: "<<header.AddressOfEntryPoint.value<<endl;
...
cout<<"ImageBase: "<<header.ImageBase.value<<endl;
cout<<"SizeOfImage: "<<header.SizeOfImage.value<<endl;

cout<<"VirtualSize: "<<header.VirtualSize.value<<endl;
cout<<"VirtualAddress: "<<header.VirtualAddress.value<<endl;
cout<<"SizeOfRawData: "<<header.SizeOfRawData.value<<endl;
cout<<"Characteristics: "<<header.Characteristics.value<<endl;

output:
AddressOfEntryPoint: 5687 == 00001637
OEP (+imagebase): 4199991
ImageBase: 4194304 == 00400000
SizeOfImage: 45056 == 0000B000
//last section
VirtualSize: 5736 == 00001668
VirtualAddress: 36864 == 00009000
SizeOfRawData: 8192 == 00002000
Characteristics: 1073741888 == 40000040

musste zwar von hex (pe-viewer) nach int umrechnen, aber ansonsten stimmt alles. ^^ das ist also schonmal gut.

nun werde ich mal deine minimal anhängen und den output checken, mal sehen was das im genauen so ergibt:
INFOUTPUT:
AddressOfEntryPoint: 45056
OEP (+imagebase): 4239360
ImageBase: 4194304
SizeOfImage: 46602
VirtualSize: 7282
VirtualAddress: 36864
SizeOfRawData: 9738
Characteristics: 1073741888

tja, denke das sieht nicht so richtig aus. ich habe bisher den entrypoint auf das ende der ursprünglichen datei gesetzt.
sizeofimage, virtualsize und sizeofrawdata, habe ich bisher mit der größe des anhangs addiert. den entry point muss ich evtl auf ne andere stele setzen habe ich so im gefühl, ich vermute fast, das er auf das ziel des EP von minimal zeigen sollte. aber mich verwundert bisher, wie ich den entry point so verändere dass er später auf den neuen sectionanfang zeigt.
 
1. Verwendung von dezimalen Werten==Ketzerei ;)

Zur technischen Seite:
naja, man sollte schon auf Aligning achten
http://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/pecoff_v8.doc
VirtualSize
The total size of the section when loaded into memory. If this value is greater than SizeOfRawData, the section is zero-padded. This field is valid only for executable images and should be set to zero for object files.

SizeOfRawData
The size of the section (for object files) or the size of the initialized data on disk (for image files). For executable images, this must be a multiple of FileAlignment from the optional header. If this is less than VirtualSize, the remainder of the section is zero-filled. Because the SizeOfRawData field is rounded but the VirtualSize field is not, it is possible for SizeOfRawData to be greater than VirtualSize as well. When a section contains only uninitialized data, this field should be zero.
wobei das hier nicht das Problem ist:
Deine errechnete SizeOfImage ist falsch:
diablo.exe
SizeOfImage=0x000A668
größe des Anhangs:0x60a

inf.exe
SizeOfImage: sollwert: 0xAC72
Dein Wert: 0x0B60A


Weiterhin gilt:
die letzte Section von diablo.exe hat einen größeren SizeOfRaw als VirtualSize. D.h schon in der original Exe werden in den Speicher "weniger" Bytes geladen, als in der physikalischen Datei vorhanden. Die SizeOfRaw ist=0x2000 und die fehlenden Bytes wurden vom Linker mit 00 aufgefüllt. Wenn man nun noch mehr dranhängt (und die VirtualSize trotzdem unter 2000 bleibt) werden diese immer noch nicht geladen.
aber mich verwundert bisher, wie ich den entry point so verändere dass er später auf den neuen sectionanfang zeigt.
EP Angabe ist im RVA, allerdings fügst Du ja alles in physikalischer Größe an. Du müsstest entweder die Sections vor dem hinzufügen auf die VA Werte ausrichten (d.h hinzufügen Sectionweise, wobei nach jeder Section schön auf den "SectionAlignment" Wert aufgerundet wird) oder die EP Angabe von RVA in FileOffset umrechnen.Dann wäre der neue EP=AlteImageSize+EP_der_hinzugefuegten_exe
Ich sehe da allerdings im Moment nicht so viel Sinn hinter einer allgemeingültigen Routine für EP, da man nicht ohne weiteres den Code zum laufen bekommen wird.
 
meinst du, es würde funktionieren, wenn ich:
- die rawdata-size aligne
- die virtualsize einfach größer als die rawdata setze
- den entry point so wie du beschrieben setze
- und die imagesize richtig ausrechne
 
Original von petrLo
meinst du, es würde funktionieren, wenn ich:
- die rawdata-size aligne
- die virtualsize einfach größer als die rawdata setze
- den entry point so wie du beschrieben setze
- und die imagesize richtig ausrechne
Jein. Wir reden wohl aneinander vorbei ;)
Wenn du Imagesize korrigierst, bekommst du erstmal eine halbwegs valide PE.
Heißt: sie ist startbar. Dass die Rawsize nicht aligned ist, stört den PE Loader nicht wirklich.
Virtualsize größer=setzen -> musst du, sonst wird der Anhang nicht geladen
entry point setzen: -> bringt in soweit nichts, als dass es trotzdem zu Fehlern bei der Ausführung kommt. Es bringt nur etwas, wenn du
a)entweder eine eigene, angepasste Executable anhängt - sprich, die nutzt keine Imports und hat adressunabhängigen Code.
b)die Initialisierungsschritte für die zweite Executable durchführst. Das bringt wiederum einige Probleme mit sich, unter anderem die Imagebase (Linker haben die Angewohnheit, per Default auf 0x400000 als Imagebase zu linken):
https://www.buha.info/board/showthread.php?t=54915&highlight=Imagebase
 
da ich kein assembler beherrsche (und ich denke dass sollte man, wenn man ne spezielle executable anhängen will)
und das programm später beliebige dateien verbinden soll, kommt im prinzip nur b in frage.
da ich jetzt aber wegen vermieterin stress habe, weis ich nicht wie schnell (und ob) ich das hinkriege^^
probiere erstmal den parser vernünftig hinzukriegen, und dann sehe ich weiter.
das thema ist schwieriger als ich mir das als erstes gedacht hätte. ich werde mal hier weiter reinposten wenn sich was neues ergibt.
danke erstmal. CDW

edit:
ohne änderung des entry points läuft das programm normal, wenn ich allerdings aligne, ergibt sich das problem, dass ich nach setzen des virtualsize = sizeOfAlignedRawSize den rest anscheinend mit nullen füllen müsste, ansonsten wird die exe wieder nicht ausgeführt, aber da ich darauf keine lust habe, verzichte ich mal im moment auf das alignen:

ich stelle aber mal den code spaßeshalber online:
Code:
	//ALIGNEMENT
	/*
	int findMult = 1;
	int alignmentMult = PE_header.SizeOfRawData.value / PE_header.FileAlignment.value;
	// if sizeofraw is not alignet then
	if(PE_header.SizeOfRawData.value + deltaSize % PE_header.FileAlignment.value > 0)
		// look for next aligned value
		while(true) {
			if(((findMult*PE_header.FileAlignment.value) / (PE_header.SizeOfRawData.value + deltaSize)) < 1.0)
				findMult++;
			else break;
		}
	 */
	// SizeOfRawData with alignement
	//m_dTmp.value = findMult*PE_header.FileAlignment.value;

edit 2: ich wollte eigentlich plattformunabhängigen code schreiben, aber zum laden von dlls, wird schon von win-funktionen gebrauch gemacht. ): mal sehen... ich denke ich orientier mich auch erstmal an so einen dll loader. bin gespannt was da noch alles kommt.
 
Ich hatte vor einiger Zeit mal ein paar Funktionen geschrieben, die das Arbeiten mit PEs vereinfachen(Sections hinzufügen usw.). Vielleicht kannst du etwas damit anfangen, die Sachen befinden sich im Quellcode hiervon:
[Unpacker]UnFSG2
 
Zurück
Oben