Ich verstehe nicht wie sich der Offset ergibt
-
ich habe den Fake86-Emulator (http://sourceforge.net/projects/fake86/)
ein bisschen erweitert um printf-Aufrufe mitzuloggen - leider kommen
ich mit der LEA-Anweisung in diesem Kontext nicht wirklich klarDer ASM Code ist aus IDA-Pro und entspricht MASM/TASM-Syntax
mein Ziel ist es den format_string und die va-args zu verarbeitenseg010:05CE ; int printf(const char *format_string, ...) seg010:05CE _printf proc far ; CODE XREF: sub_2EA2A+7P seg010:05CE ; sub_2EA2A+16P seg010:05CE seg010:05CE var_8 = word ptr -8 seg010:05CE var_4 = word ptr -4 seg010:05CE format_string = dword ptr 6 seg010:05CE seg010:05CE push bp seg010:05CF mov bp, sp seg010:05D1 sub sp, 8 seg010:05D4 push di seg010:05D5 push si seg010:05D6 mov si, 36D8h seg010:05D9 lea ax, [bp+format_string+2] seg010:05DC mov [bp+var_4], ax seg010:05DF push si
ich dachte die LEA-Anweisung verstanden zu haben - aber ich habe einfach nicht
die Daten gesehen die ich erwarte - dann ein bisschen rumgespielt und auf den Wert gekommen - in alle bisherigen Testfaellenkann mir jemand erklären wie ich auf die sp+4 komme?
//... if(current_instruction_ptr == _printf_base) { uint16_t format_string_offset = *(uint16_t*)ptr(ss, sp+4); char* format_string = (char*)ptr(ds, format_string_offset); }
-
das ganze läuft unter DOS/16Bit
und ich denke das mich dieser "typedef" verwirrt
format_string = dword ptr 6
-
Memory Model: Medium - also sollte der format_string ein near-pointer sein
erstellt ist das Programm laut IDA mit Quick C 2.x
-
Ich fang mal an zu raten, da ich nicht weiß was ptr(...) macht bzw sein soll. Zu DOS-Zeiten habe ich C gemieden wie der Teufel das Weihwasser :).
IDA teilt dir den Aufrufer mit:
; CODE XREF: sub_2EA2A+7|P ; sub_2EA2A+16|P
Das nennt sich "cross references" und du kannst in IDA einstellen, dass diese Referenzen dir so angezeigt werden, dass du die entsprechenden Code-Teile auch findest.
Wenn Du dir den aufrufenden Code anschaust, kannst Du zweifelsfrei entscheiden, ob der CALL near oder far ist und ob der Zeiger auf format_string near oder far ist. Anhand des geposteten Codes vermute ich, dass CALL und Zeiger far sind.
viele grüße
ralph
-
ndisasm spuckt diesen code fuer die _printf aus - sieht gleich aus
00000000 55 push bp 00000001 8BEC mov bp,sp 00000003 83EC08 sub sp,byte +0x8 00000006 57 push di 00000007 56 push si 00000008 BED836 mov si,0x36d8 0000000B 8D4608 lea ax,[bp+0x8] 0000000E 8946FC mov [bp-0x4],ax 00000011 56 push si
-
Ich fang mal an zu raten, da ich nicht weiß was ptr(...)
liefert mir einfach einen echten Pointer (bei mir VS2010/Win32) auf den Emulator-Speicher (uint8_t RAM[1MB]...), die ss, sp usw. sind Register-Variablen in dem richtigen Zustand (also eben so wie alles beim _printf Aufruf steht Register, RAM, etc.)
void* ptr(uint16_t seg, uint16_t ofs) { return &RAM[seg*16+ofs]; }
Wenn Du dir den aufrufenden Code anschaust, kannst Du zweifelsfrei entscheiden, ob der CALL near oder far ist und ob der Zeiger auf format_string near oder far ist. Anhand des geposteten Codes vermute ich, dass CALL und Zeiger far sind.
es gibt nur 2 Aurufe von printf im Code
B8 E0 3F mov ax, offset seg012:1422 50 push ax 9A CE 05 C5 2C call _printf
und 0x9A ist ein far call
und der Zeiger auf den String wird als ax(16bit) gepusht also nearalso laut https://en.wikipedia.org/wiki/Intel_Memory_Model#Memory_models
ist es dann Medium
-
Hintergrund:
Fake86 ist ein kleiner PC-Emulator (in C) den ich mit(unter) VS2010 kompiliere/laufen lassen
die _printf Routinen ist in einem Programm das in Fake86 unter DOS 6.22 gestartet wird - ich habe Fake86 so erweitert das ich den Start dieses Programmes erkenne und einen Image-Base-Pointer bekommen von diesem aus kann ich dann mit Offset-Informationen aus IDA den Funktionsaufrufe von _printf erkennen und z.B. die Parameter in einem Log-Fenster ausgeben
-
ich hole mir den Wert von SP direkt bei seg010:05CE (am Anfang der Routine) - da kommt dann aber noch ein push bp - und danach wird erst SP in BP geschoben - also schonmal -2 unterschied in SP
damit wird kann ich mir das +2 schon mal erklären
lea ax, [bp+format_string+2]
aber wiso ist es dann +4 und nicht +6 bei mir im Code
*(uint16_t*)ptr(ss, sp+4)
es ist wohl schon zu spät
-
Gast3 schrieb:
ich hole mir den Wert von SP direkt bei seg010:05CE (am Anfang der Routine) ...
... und damit hast du die Variable 'SP' und punkt. Was später kommt, interessiert erst mal nicht. Die Variable 'SP' bildet nur das Register SP zu dem Zeitpunkt ab, zu dem du dir dessen Wert holst. Später wird Register SP geändert, auf Register BP kopiert und mit Register BP weitergearbeitet ... interessiert alles die Variable SP nicht.
Da der CALL mit Sicherheit far ist, liegt auf dem Stack eine Segment:Offset-Rückkehraddresse - also 4 Bytes. 4 Bytes später findest du den format_string, also SP+4.
Ich bin allerdings immer noch der Meinung, dass der Zeiger auf den format_string far ist. Gibt es denn wirklich vor dem Aufruf von _printf kein weiteres 'push ds' oder 'mov [bp+xx],xx' oder ähnliches? Wie sieht denn die Stackbereinigung nach dem _printf aus? Ich bin jetzt aber zu faul QuickC zu installieren. Es ist schon spät :).
viele grüße
ralph
-
Gibt es denn wirklich vor dem Aufruf von _printf kein weiteres 'push ds' oder 'mov [bp+xx],xx' oder ähnliches?
ich schaue morgen nochmal nach - aber denke nicht (auch wenn ich mein Quick C 2.51 Beispiel nicht kompiliert bekomme - aber wohl auch zu spät)
-
Ich hab mal mit Quick C Prof 2.51(2.52) eine kleine Medium-Model-Exe erzeugt
#include <stdio.h> void main(void) { int brk = 0; printf("can load driver"); printf("%s %i\n", "hallo", 10); brk = 1; }
Exe mit "qcl /AM test.c" erzeugt (/AM = Medium Model)
die _main sieht so aus
; int __cdecl main(int argc, const char **argv, const char **envp) _main proc far ; CODE XREF: start+B8P var_2 = word ptr -2 push bp mov bp, sp mov ax, 2 call __aFchkstk push si push di mov [bp+var_2], 0 mov ax, offset format_string ; "can load driver" push ax ; format_string call _printf add sp, 2 mov ax, 10 push ax mov ax, offset aHallo ; "hallo" push ax mov ax, offset aSI ; "%s %i\n" push ax ; format_string call _printf add sp, 6 mov [bp+var_2], 1 pop di pop si mov sp, bp pop bp retf _main endp
im Datensegment
format_string db 'can load driver',0 ; DATA XREF: _main+12o aHallo db 'hallo',0 ; DATA XREF: _main+22o aSI db '%s %i',0Ah,0 ; DATA XREF: _main+26o
und die _printf dazu
leider nicht exakt der gleiche Code (nur eine lokale Variable)
denke es ist eher Quick C 2.01 oder sowas - aber der Aufruf ist ja gleich; int printf(const char *format_string, ...) _printf proc far ; CODE XREF: _main+16P _main+2AP var_4 = word ptr -4 format_string = dword ptr 6 push bp mov bp, sp sub sp, 4 push di push si mov si, 0DEh ; 'Þ' push si call __stbuf add sp, 2 mov di, ax lea ax, [bp+format_string+2] push ax push word ptr [bp+format_string] mov ax, 0DEh ; 'Þ' push ax call __output add sp, 6 mov [bp+var_4], ax mov ax, 0DEh ; 'Þ' push ax ; FILE * push di ; int call __ftbuf add sp, 4 mov ax, [bp+var_4] pop si pop di mov sp, bp pop bp retf _printf endp
-
1. printf mit einem 16bit Wert
call _printf add sp, 2 ; 1*2 byte vom stack aufraeumen
2. printf mit 3 16bit Werten
call _printf add sp, 6 ; 3*2 byte vom stack aufraeumen
liegt IDA dann mit dem dword hier falsch? Das ist der Teil der mich verwirrt
format_string = dword ptr 6
-
Ich bekomme mein IDA (6.1) einfach nicht dazu, mir dasselbe zu zeigen wie dir:
seg001:0664 ; int printf(const char *, ...) seg001:0664 _printf proc far ; CODE XREF: _main+16|P seg001:0664 ; _main+2A|P seg001:0664 seg001:0664 var_4 = word ptr -4 seg001:0664 arg_0 = word ptr 6 seg001:0664 arg_2 = byte ptr 8 seg001:0664
Build:
C:\Compiler\C\QC25>qcl /AM test.c Microsoft (R) QuickC Compiler Version 2.51 Copyright (c) Microsoft Corp 1987-1990. All rights reserved. test.c Microsoft (R) QuickC Linker Version 4.10 Copyright (C) Microsoft Corp 1989-1990. All rights reserved. Object Modules [.OBJ]: /z2 test.obj Run File [test.exe]: "test.exe" /noi List File [NUL.MAP]: NUL Libraries [.LIB]: C:\Compiler\C\QC25>_
Zumindest bin ich jetzt überzeugt davon, dass der Zeiger zum format_string tatsächlich near ist. Dein IDA liegt also falsch.
viele grüße
ralph
-
meine qlc Ausgabe ist 100% identisch - ich habe aber einen IDA 6.5.x
was sagt ndisasm vom nasm (http://www.nasm.us/pub/nasm/releasebuilds/2.11.08/win32/nasm-2.11.08-win32.zip)
einfach ndisasm -e XXX -b 16 test.exe > out.txt
XXX = dezimaler fileoffset in IDA von der _printf routine (bei mir 2092)
bei mir sieht die so aus
00000000 55 push bp 00000001 8BEC mov bp,sp 00000003 83EC04 sub sp,byte +0x4 00000006 57 push di 00000007 56 push si 00000008 BEDE00 mov si,0xde 0000000B 56 push si 0000000C E85701 call word 0x166 0000000F 83C402 add sp,byte +0x2 00000012 8BF8 mov di,ax 00000014 8D4608 lea ax,[bp+0x8] 00000017 50 push ax 00000018 FF7606 push word [bp+0x6] 0000001B B8DE00 mov ax,0xde 0000001E 50 push ax
-
-
Gast3 schrieb:
Deine TEST.EXE und meine TEST.EXE sind absolut identisch. Auch meine ndisasm-Ausgabe (ndisasm -v: NDISASM version 2.11.08 compiled on Feb 21 2015) deckt sich mit deiner Ausgabe.
viele grüße
ralph
-
mit jedem Update sieht alter DOS-Code anders aus - hab mich schon ein bisschen daran gewoehnt
und danke nochmal fuer deine Hilfe
jetzt klappt das Abgreifen der Parameter des 16bit _printfs
meine eigene 32bit _detoured_printf16 spuckt das gleiche auf der Emulator-Log-Console aus