Fråga:
Hur är en struktur placerad i ett demonterat program?
Ken Bellows
2013-03-20 00:24:54 UTC
view on stackexchange narkive permalink

Jag tog en grundläggande 40-timmars kurs för omvänd teknik för några somrar sedan. Samtidigt som vi lärde oss att använda IDAPro, visade instruktören ganska snabbt och utan att förklara mycket hur man kan märka vissa variabler i ASM som medlemmar i en struktur, i princip lika med en gammaldags struktur i C / C ++, och behandla dem som sådana var de än ses i resten av koden. Detta verkar ganska användbart för mig.

Vad han dock inte täckte över var hur man identifierade en struktur. Hur vet du när en grupp variabler faktiskt utgör en struktur och inte bara en uppsättning relaterade variabler? Hur kan du vara säker på att författaren använde en struktur (eller något liknande) där?

Var det min (Rolf Rolles) klass av nyfikenhet?
Nej det var det inte.
Jag tror att jag har undervisat den del av kursen du hänvisar till (men förmodligen inte när du tog den). Vi brukar spendera mer tid på ämnet nu än tidigare. Ledsen för förvirringen!
Det här var en privat kurs internt i mitt företag, så om du inte råkar vara anställd av samma folk och aktivt arbetar inom detta område, är det osannolikt. Jag antar att det finns många av dessa runt.
Sju svar:
Andrew
2013-03-20 00:54:04 UTC
view on stackexchange narkive permalink

Du kan inte. I C finns strukturer för läsarna av C-programmet och deras användning i programbilden är ganska valfri. Det är fullt möjligt att i det ursprungliga programmet bestämde sig några galna jerkar för att göra allt med perfekt stora char * buffertar och casta och lägga till på lämpligt sätt, och du skulle aldrig veta skillnaden.

Märkningen "struct" är helt till din fördel som kodvisare. Det kan mycket väl vara så att strukturetiketter du applicerar på ett program faktiskt är två variabler som alltid lagras bredvid varandra. Detta spelar ingen roll så länge det inte leder dig till falska slutsatser om vad programmet gör dock.

Mycket intressant. Kan du ge ett exempel på ett fall när definiering av en struktur kan leda till fel slutsats?
@KenB Du riskerar att göra falska slutsatser även om programmeraren använde en struct, till exempel om strukturen är i en union så att datastrukturen kan ha olika layouter beroende på kodvägarna eller scenen i objektets livscykel.
ett (slags) konstruerat exempel som jag kan tänka mig är funktionsidentifiering efter argumenttyp. det finns ett samtal eax, och du vet att parametern är en pekare som skickats som ett argument, och eftersom du tror att pekaren pekar på en struktur av typen baz, tror du då att 'eax' kan innehålla en funktion med signaturen 'void (* ) (struct baz *); '. men om din strukturidentifiering är felaktig behöver detta inte vara fallet.
På samma sätt som @Gilles-svaret kan du inte få hela strukturen om det finns grupper av oanvända variabler i strukturen. Annars skulle automatiserade verktyg se den enda stora strukturen som programmeraren definierade som en uppsättning mindre strukturer baserade på användnings- och stackmönster.
Synd att svaret accepterades för snabbt och nu får röst bara på grund av det. Även om det inte alltid är möjligt att göra pålitligt, är det definitivt möjligt att återställa eller åtminstone gissa strukturlayout i * många * fall, vilket framgår av andra svar.
Jag tycker inte att det är konstruktivt att säga att det inte är möjligt. Även om du inte kan säga säkert om det var en struktur, som du säger kan det vara till stor nytta "som kodvisare". Du måste vara försiktig med vilka slutsatser du drar - följ exekveringsvägen först! - men det kan vara ett mycket användbart verktyg.
Igor och Robert - Med tanke på att min yttersta fråga var "Hur kan du * vara säker * på att författaren använde en" struktur "?", Är detta svar helt korrekt. Jag förstår redan att det är möjligt att göra en användbar gissning. Jag undrade helt enkelt om det finns ett sätt att * definitivt * avgöra att författaren använde en "struktur" och "strukturen".
user1354557
2013-03-22 05:00:44 UTC
view on stackexchange narkive permalink

Det finns mycket vanliga mönster som du hittar i kod som betecknar strukturanvändning.

Förskjutningsförskjutningar:

Om du har en pekare som härleds vid någon icke-nollförskjutning , du har förmodligen att göra med en struktur. Leta efter mönster som:

  mov eax, [ebp-8]; Ladda en lokal variabel i eaxmov ecx, [eax + 8]; ** Dereference a dword at eax + 8 **  

I detta exempel har vi en variabel som innehåller en pekare, men vi bryr oss om innehållet i minnet vid någon specifik förskjutning framåt för pekaren. Det här är exakt hur strukturer används: Vi får en pekare till strukturen och därefter refererar pekaren plus en viss förskjutning för att komma åt en viss medlem. I C är syntaxen för detta: pMyStruct->member_at_offset_8.

Sidanot : Förväxla inte referens vid förskjutning av någon variabel med dereferens vid förskjutningar av stackpekaren eller rampekaren ( esp eller ebp ). Naturligtvis kan du tänka på de lokala variablerna och funktionsargumenten som en stor struktur, men i C är de inte uttryckligen definierade som sådana.

Mer subtila pekarförskjutningar:

Du behöver faktiskt inte avläsa något för att upptäcka en strukturmedlem. Till exempel:

  mov eax, [ebp-8]; Ladda en lokal variabel i eaxpush 30h; num = 30hpush aSampleString; src = "Provsträng" lägg till eax, 0Chpush eax; dst = eax + 0xCcall strncpy  

I det här exemplet kopierar vi upp till 0x30 tecken från någon källsträng till eax + 0xC (se strncpy). Detta säger oss att eax troligen pekar på en struktur med en strängbuffert (minst 0x30 byte) vid förskjutning 0xC. Till exempel kan strukturen se ut ungefär så:

  struct _MYSTRUCT {DWORD a; // + 0x0 DWORD b; // + 0x4 DWORD c; // + 0x8 CHAR d [0x30]; // + 0xC ...}  

I vilket fall ser exempelkoden ut:

  strncpy (&pMyStruct->d, "Sample String", sizeof (pMyStruct->d));  

Sidanot: Det är möjligt (men osannolikt) att vi kan kopiera till en stor strängbuffert vid offset + 0xC, men du skulle kunna bestämma detta genom kontext. Om till exempel offset + 0x8 var ett heltal är det definitivt en struktur. Men om vi kopierade en sträng med fast längd 0xC till adressen eax kopierade vi sedan en annan sträng till adressen eax + 0xC , det är förmodligen en gigantisk sträng.

Alla läser / alla skriver:

Låt oss säga att du har en struct ( inte en pekare till en struct) som en lokal variabel i din stack. För det mesta vet IDA inte skillnaden mellan en struktur på stacken eller en massa individuella lokala variabler. Men ett stort tips om att du har att göra med en struktur är om du bara någonsin läser från en variabel utan att skriva till den, eller (mindre) om du bara skriver till en variabel utan att läsa från den. Här är ett exempel på var och en:

  lea eax, [ebp + var_58]; Ladda ADRESSEN för en lokal variabel i eaxpush eaxcall some_functionmov eax, [ebp + var_54]; Låt oss säga att vi aldrig har rört var_54 förut ... test eax, eax; ... Men vi kontrollerar dess värde! Jz någonstans ...  

I det här exemplet läser vi från var_54 utan att någonsin skriva något till det (inom den här funktionen). Detta förmodligen betyder att det är medlem i en struktur som nås från någon annan funktionsanrop. I det här exemplet antyds det att var_58 kan vara början på den strukturen, eftersom dess adress skjuts som argumentet till some_function . Du kan verifiera detta genom att följa logiken för någon_funktion och kontrollera om dess argument någonsin avleds (och ändras) vid offset + 0x4. Naturligtvis behöver detta inte nödvändigtvis hända i någon_funktion - det kan hända i en av dess underfunktioner, eller en av dess underfunktioner, etc.

Ett liknande exempel finns för att skriva:

  xor eax, eaxmov [ebp + var_28], eax; Låt oss säga att det här är den * enda * gången var_28 berörs genom eax, [ebp + var_30] tryck på eaxcall some_other_function ...  

När du ser att lokala variabler är inställda och sedan aldrig hänvisas igen, kan inte bara glömma bort dem, eftersom de mycket sannolikt kan vara medlemmar i en struktur som överförs till en annan funktion. Det här exemplet innebär att en struktur (som börjar vid var_30 ) skrivs till vid förskjutning + 0x8 innan adressen till den strukturen skickas till någon_användarfunktion .

Båda dessa exempel i C kan se ut som:

  some_function (&myStruct); if (myStruct.member_at_offset_4) ...  

och

  myStruct.member_at_offset_8 = 0; some_other_function (&myStruct);  

Sidanot: Även om vart och ett av dessa exempel använde lokala variabler, samma logiken gäller globaler.

Dokumenterade funktioner som förväntar sig strukturer:

Det här är nog uppenbart och IDA kommer att hantera detta åt dig nästan hela tiden, men ett enkelt sätt att veta när du har en struktur i din kod är om du ringer till en dokumenterad funktion som förväntar sig en viss struktur. Till exempel CreateProcessW förväntar sig en pekare till en STARTUPINFOW -struktur. Den här borde inte kräva ett exempel.

Hur vet jag om dessa mönster faktiskt indikerar strukturanvändning?

En sista punkt som jag vill göra är att i alla dessa fall, ja, tekniskt sett, kunde författaren till programmet skriva sin kod utan att använda strukturer. De kunde också ha skrivit sin kod genom att definiera varje funktion som __declspec (naken) med en stor __asm ​​ inline. Du skulle aldrig kunna berätta. Men utan tvekan spelar det ingen roll. Om det finns logiska grupper av värden som lagras sammanhängande i minnet och skickas från funktion till funktion, är det fortfarande meningsfullt att kommentera dem som strukturer. Nästan hela tiden är det så här författaren skrev sin kod ändå.

Om du behöver mig för att utarbeta något, låt mig veta.

Wow. Detta är imponerande noggrant.
** "Naturligtvis kan du tänka på de lokala variablerna och funktionsargumenten som en stor struktur, men i C definieras de inte uttryckligen som sådana." ** Det är dock värt att påpeka att IDAs gränssnitt för att specificera stackvariabler inom en funktion är ungefär samma som för att definiera strukturer. Att slå samman de två i C är inte så bra, men i IDA är det bra att känna igen likheterna.
Yifan
2013-03-20 01:32:14 UTC
view on stackexchange narkive permalink

Att hitta strängar är svårt, men kan hjälpa till att förstå koden mycket. Som Andrew sa är structs bara en C-abstraktion och i montering är det bara ett minnehinne och det finns inget idiotsäkert sätt att identifiera structs. Men för enklare program kan vissa heuristik vara till hjälp. Exempelvis är "små" matriser mer benägna att vara strukturer än gigantiska matriser. Att se till exempel att läsningar från en slinga skulle göra att det verkar vara en matris, medan det att läsa ett par inter vid en konstant förskjutning skulle få det att se mer ut som en struktur. Ett annat sätt är att se samma grupp av referenser som händer i olika delar av koden. Om två olika funktioner tar någon pekare som paramater och båda försöker avvika förskjutning 0x10 följt av 0x18 följt av 0x14 eller något, kan det vara kodinställningsfält för en struktur. Dessutom är all tillgång till data av olika storlek som hänvisas till från en enda inmatad pekare en bra indikator.

Jesper.Reenberg
2013-03-20 01:10:18 UTC
view on stackexchange narkive permalink

Det enklaste sättet att veta när du har att göra med en struktur är när koden anropar funktioner som du vet (eller dokumentation anger) tar en struktur som ett argument.

Till exempel in_addr struktur för inet_ntoa -funktionen.

Med tanke på att IDA inte förstod detta i första hand.

Mycket bra poäng.
Wesley McGrew
2013-03-20 04:45:15 UTC
view on stackexchange narkive permalink

Jag försöker leta efter situationer där en pekare till en bit data skickas till en funktion, då när den används i den funktionen behandlas olika förskjutningar från den som olika datatyper. Det indikerar för mig att 1) ​​det inte är en matris av en enda datatyp, och 2) det refereras från en basadress snarare än att skickas som en separat parameter. Det hjälper också när du ser att den tilldelas dynamiskt (malloc eller ny) och sedan fylls i med data av olika slag.

I IDA rekommenderar jag alltid mina elever att fortsätta och skapa IDA-strukturer för saker som de misstänker är strukturer och fylla i dem med element när de ser att de hänvisas till, även om de senare visar sig att vara fel / vilseledd. Det är väldigt mycket en iterativ process som fyller i den mer detaljerat när du får mer förståelse för programmet och hur det använder dessa data när du går, så det är viktigt att markera saker du räknar ut när du räknar ut dem.

Robert Mason
2013-03-20 17:29:35 UTC
view on stackexchange narkive permalink

Det finns flera metoder du kan använda (de flesta har antydts av tidigare svar), men för fullständighetens skull listar jag några här.

  1. Leta efter samtal till bibliotek som förväntar sig strukturer. Såvida inte den körbara filen gör något som den inte borde göra och kastar pekare och sedan matar dem till biblioteksfunktionen och hoppas att den inte kraschar (osannolikt), så här kan du få en bra känsla för vilka delar av minnet som är strukturer.

  2. Titta på hur delar av minnet skickas / returneras från funktioner. Vissa kompilatorer / anropskonventioner / plattformar skickar och returnerar små strukturer (som kan passa i ett par register) annorlunda än andra typer av data. Om du till exempel ser att data returneras i både eax och edx har du förmodligen att göra med en liten struktur.

  3. Titta på hur minnet adresseras. Om det verkar som att delar av minnet modifieras efter en registerstorlek genom en pekare är det troligtvis (igen, om de inte gör något ovanligt) en matris eller struktur. Som Yifan sa, om det finns många åtkomster via konstant förskjutning tittar du förmodligen på en struktur eller en liten grupp som används på ett sätt som liknar en struktur (som osignerad char rgb [3] ). Och om du tittar på bitar av data i olika storlekar, är det nästan definitivt en struktur.

Men med alla tänkbara scenarier, så länge användningen av data överensstämmer med din modell, det spelar ingen roll om det i den ursprungliga koden bara är en rad byte med några konventioner om vad som går var eller en fullblåst struktur. Använd vilken modell som helst som hjälper dig att resonera om koden.

För att ta itu med fackföreningsfrågan om strukturer som har olika minneslayout i olika kodgrenar, kommer jag att ta itu med de två vanligaste användningarna av fackföreningar (enligt min erfarenhet):

  • Skriv punning: I det här fallet ser du förmodligen inte ens det i den sammanställda koden. Även om det är tekniskt odefinierat beteende AFAIK, kommer de flesta kompilatorer att behandla det som en reinterpret_cast<>()

  • Algebraiska typer: Vanligtvis åtföljs detta av en tagg som gör det enkelt att identifiera. Om du ser kod på flera ställen som kontrollerar något heltal och sedan behandlar minnet annorlunda baserat på det värdet, kan du gissa att det här är vad som händer. Om vissa funktioner inte kontrollerar taggen, berättar det också något om funktionen - förutsatt att författaren inte är lat och vill att deras kod ska brytas, anser de förmodligen att det är en invariant att den funktionen bara kommer att kallas i grenar som redan vet att det är av en viss typ.

FWIW, eax: edx return * betyder vanligtvis * ett 64-bitars heltal, inte en liten struktur.
SW_user2953243
2015-01-19 20:19:29 UTC
view on stackexchange narkive permalink

Som andra har sagt, letar jag vanligtvis efter ett funktionsanrop där en referens skickas - men jag letar också efter tillfällen där en mallocerad buffert returneras (så vet du / bekräftar storleken på strukturen) - och olika medlemmar i "bufferten" är inställda / initierade.



Denna fråga och svar översattes automatiskt från det engelska språket.Det ursprungliga innehållet finns tillgängligt på stackexchange, vilket vi tackar för cc by-sa 3.0-licensen som det distribueras under.
Loading...