Yerel makine kodu neden kolayca çözülemiyor?


16

Java, VB.NET, C #, ActionScript 3.0 vb.Gibi bayt kodu tabanlı sanal makine dilleri ile, bazen İnternet'ten bazı decompiler indirmenin, bayt kodunu iyi bir zaman geçirmenin ne kadar kolay olduğunu ve Çoğu zaman, saniyeler içinde orijinal kaynak kodundan çok uzak olmayan bir şey ortaya çıkarın. Sözde bu tür bir dil buna karşı özellikle savunmasızdır.

Son zamanlarda, en azından orijinal olarak hangi dilde yazıldığını (ve böylece hangi dilde içine ayrılmaya çalışacağınızı) bildiğinizde, neden yerel ikili kodla ilgili daha fazla şey duymadığınızı merak etmeye başladım. Uzun zamandır, bunun yerel makine dilinin tipik bayt kodundan çok daha çılgın ve daha karmaşık olması olduğunu düşündüm.

Peki bayt kodu neye benziyor? Şöyle görünüyor:

1000: 2A 40 F0 14
1001: 2A 50 F1 27
1002: 4F 00 F0 F1
1003: C9 00 00 F2

Yerel makine kodu neye benziyor (onaltılı olarak)? Elbette şöyle görünüyor:

1000: 2A 40 F0 14
1001: 2A 50 F1 27
1002: 4F 00 F0 F1
1003: C9 00 00 F2

Ve talimatlar biraz benzer bir zihin çerçevesinden geliyor:

1000: mov EAX, 20
1001: mov EBX, loc1
1002: mul EAX, EBX
1003: push ECX

Diyelim ki, C ++ diyelim, bazı yerel ikili dosyaları ayrıştırmaya çalışacak dil göz önüne alındığında, bu kadar zor olan ne? Hemen akla gelen iki fikir şunlardır: 1) gerçekten bytecode'dan çok daha karmaşık veya 2) işletim sistemlerinin programları sayfalandırmaya ve parçalarını dağıtmaya meyilli olduğu hakkında bir şey çok fazla soruna neden oluyor. Bu olasılıklardan biri doğruysa, lütfen açıklayın. Ama her iki durumda da, bunu neden hiç duymuyorsunuz?

NOT

Cevaplardan birini kabul etmek üzereyim, ama önce bir şeyden bahsetmek istiyorum. Hemen hemen herkes, farklı orijinal kaynak kodu parçalarının aynı makine koduyla eşleşebileceğinden bahsetmektedir; yerel değişken adları kaybolur, başlangıçta ne tür bir döngü kullanıldığını bilmezsiniz.

Ancak bahsettiğim ikisi gibi örnekler gözlerimde önemsiz. Bazı cevaplar, makine kodu ile orijinal kaynak arasındaki farkın, bu önemsiz bir şeyden çok daha fazla olduğunu ifade etme eğilimindedir.

Ancak, örneğin, yerel değişken adları ve döngü türleri gibi şeyler söz konusu olduğunda, bytecode bu bilgileri de kaybeder (en azından ActionScript 3.0 için). Bunun decompiler aracılığıyla şeyler geri öncesi çekti ettik ve gerçekten bir değişken olarak adlandırılan umursamıyordum strMyLocalString:Stringveya loc1. Hala bu küçük, yerel kapsamı inceleyebilir ve çok fazla sorun olmadan nasıl kullanıldığını görebilirim. Ve bir fordöngü hemen hemen aynı şeydir.whileEğer düşünürseniz döngü. Ayrıca, kaynağı irrFuscator aracılığıyla çalıştırdığımda bile (secureSWF'den farklı olarak, üye değişkenini ve işlev adlarını rastgele sıralamaktan çok daha fazlasını yapmaz), yine de daha küçük sınıflarda belirli değişkenleri ve işlevleri izole etmeye başlayabileceğiniz gibi görünüyordu, şekil nasıl kullanıldıklarını öğrenin, onlara kendi adlarınızı atayın ve oradan çalışın.

Bunun çok önemli olması için, makine kodunun bundan çok daha fazla bilgi kaybetmesi gerekir ve bazı cevaplar buna girer.


35
Hamburgerden inek yapmak zor.
Kaz Dragon

4
Ana sorun, yerel bir ikili program hakkında çok az meta veri tutuyor olmasıdır. Sınıflar hakkında hiçbir bilgi tutmaz (C ++ 'ın ayrıştırılmasını özellikle zorlaştırır) ve işlevler hakkında her zaman bile bir şey tutmaz - bir CPU her seferinde bir komut olmak üzere kodu oldukça doğrusal bir şekilde yürüttüğü için gerekli değildir. Ayrıca, kod ve veri ( bağlantı ) arasında ayrım yapmak da imkansızdır . Daha fazla bilgi için RE.SE'de arama yapmayı veya yeniden sormayı düşünebilirsiniz .
ntoskrnl

Yanıtlar:


39

Derlemenin her adımında geri alınamayan bilgileri kaybedersiniz. Orijinal kaynaktan ne kadar çok bilgi kaybederseniz, bu koda dönüştürmek o kadar zor olur.

Son hedef makine kodu üretilirken orijinal kaynaktan korunandan çok daha fazla bilgi korunduğundan, bayt kodu için yararlı bir derleyici oluşturabilirsiniz.

Bir derleyicinin ilk adımı, genellikle ağaç olarak temsil edilen ara gösterim için kaynağı birime dönüştürmektir. Geleneksel olarak, bu ağaç yorumlar, boşluk, vb. Anlamsız bilgiler içermez. Bu atıldıktan sonra orijinal kaynağı o ağaçtan kurtaramazsınız.

Bir sonraki adım, ağacı optimizasyonları kolaylaştıran bir ara ara dile dönüştürmektir. Burada epeyce seçenek var ve her derleyici altyapısının kendine ait seçenekleri var. Bununla birlikte, tipik olarak, yerel değişken adları, büyük kontrol akış yapıları (bir for veya while döngüsü kullanıp kullanmadığınız gibi) bilgiler kaybolur. Burada tipik olarak bazı önemli optimizasyonlar, sabit yayılım, değişmez kod hareketi, işlev satır içi, vb. Gerçekleşir.

Bundan sonraki adım, ortak talimat desenlerinin optimize edilmiş sürümünü üreten "gözetleme deliği" optimizasyonu olarak adlandırılabilecek gerçek makine talimatlarını oluşturmaktır.

Her adımda, sonunda, orijinal koda benzeyen herhangi bir şeyi kurtarmanın imkansız hale gelmesine kadar, daha fazla bilgi kaybedersiniz.

Diğer yandan bayt kodu, ilginç ve dönüştürücü optimizasyonları, hedef makine kodu üretildiğinde JIT aşamasına (tam zamanında derleyici) kadar kaydeder. Bayt kodu, aynı bayt kodunun birden çok hedef makine koduna derlenmesini sağlamak için yerel değişken türleri, sınıf yapısı gibi birçok meta veri içerir. Tüm bu bilgiler bir C ++ programında gerekli değildir ve derleme işleminde atılır.

Çeşitli hedef makine kodları için kod çözücüler vardır, ancak orijinal kaynağın çok fazla kaybolması nedeniyle genellikle yararlı sonuçlar üretmezler (değiştirebileceğiniz ve daha sonra yeniden derleyebileceğiniz bir şey). Yürütülebilir dosya için hata ayıklama bilgileriniz varsa daha iyi bir iş yapabilirsiniz; ancak, hata ayıklama bilgileriniz varsa, muhtemelen orijinal kaynağınız da vardır.


5
JIT'in daha iyi çalışabilmesi için bilgilerin tutulması önemlidir.
btilly

C ++ DLL'leri o zaman kolayca ayrıştırılabilir mi?
Panzercrisis

1
Yararlı olduğunu düşündüğüm hiçbir şeye değil.
chuckj

1
Meta veriler "aynı bayt kodunun birden çok hedefe derlenmesine izin vermek için" değil, yansıma için oradadır. Geciktirilebilir ara gösterimin bu meta verilere sahip olması gerekmez.
SK-logic

2
Bu doğru değil. Verilerin çoğu yansıma için vardır, ancak yansıma tek kullanım değildir. Örneğin, arabirim ve sınıf tanımları, hedef makine üzerinde alan ofseti tanımlamak, sanal tablolar oluşturmak, vb. Oluşturmak için kullanılır. Bu tablolar, yerel kod üretilirken derleyici ve / veya bağlayıcı tarafından oluşturulur. Bu yapıldıktan sonra, bunları oluşturmak için kullanılan veriler atılır.
chuckj

11

Diğer cevapların işaret ettiği gibi bilgi kaybı bir noktadır, ancak anlaşma kırıcı değildir. Sonuçta, size sadece istediğiniz orijinal programı geri beklemeyin herhangi bir üst düzey dilde temsil. Kod satır içine alınmışsa, sadece izin verebilir veya ortak hesaplamaları otomatik olarak çarpanlarına ayırabilirsiniz. Prensipte birçok optimizasyonu geri alabilirsiniz. Ancak prensipte geri dönüşü olmayan bazı işlemler vardır (en azından sonsuz miktarda hesaplama olmadan).

Örneğin, dallar bilgisayarlı atlamalara dönüşebilir. Bunun gibi kod:

select (x) {
case 1:
    // foo
    break;
case 2:
    // bar
    break;
}

derlenebilir (üzgünüm, bu gerçek montajcı değildir):

0x1000:   jump to 0x1000 + 4*x
0x1004:   // foo
0x1008:   // bar
0x1012:   // qux

Şimdi, x'in 1 veya 2 olabileceğini biliyorsanız, atlamalara bakabilir ve bunu kolayca tersine çevirebilirsiniz. Peki ya 0x1012 adresi? Bunun için bir case 3de mi oluşturmalısınız ? Hangi değerlere izin verildiğini anlamak için tüm programı en kötü durumda izlemeniz gerekir. Daha da kötüsü, olası tüm kullanıcı girdilerini dikkate almanız gerekebilir! Sorunun temelinde veri ve talimatları birbirinden ayıramazsınız.

Bununla birlikte, tamamen karamsar olmazdım. Yukarıdaki 'assembler' fark etmiş olabileceğin gibi x dışarıdan gelir ve eğer, değil 1 veya 2 olması garanti, aslında sen her yerde atlamak için izin veren bir kötü hata var. Ancak programınız bu tür hatalardan arınmışsa, akıl yürütmek çok daha kolaydır. (CLR IL veya Java bayt kodu gibi "güvenli" ara dillerin, meta verileri bir kenara bıraksa bile koda etmenin çok daha kolay olması bir tesadüf değildir.) Bu yüzden, pratikte, belirli, iyi davranılmış bir koda dönüştürmek mümkün olmalıdırprogramları. Yan etkileri ve iyi tanımlanmış girdileri olmayan bireysel, fonksiyonel stil rutinlerini düşünüyorum. Bence basit fonksiyonlar için sahte kod verebilen bir çift dekompresör var, ama bu tür araçlarla çok fazla deneyimim yok.


9

Makine kodunun orijinal kaynak koduna kolayca dönüştürülememesinin nedeni, derleme sırasında çok fazla bilginin kaybolmasıdır. Yöntemler ve dışa aktarılmayan sınıflar satır içine alınabilir, yerel değişken adları kaybolur, dosya adları ve yapıları tamamen kaybolur, derleyiciler açık olmayan optimizasyonlar yapabilir. Başka bir neden, birden çok farklı kaynak dosyasının aynı derlemeyi üretebilmesidir.

Örneğin:

int DoSomething()
{
    return Add(5, 2);
}

int Add(int x, int y)
{
    return x + y;
}

int main()
{
    return DoSomething();
}

Derlenebilir:

main:
mov eax, 7;
ret;

Montajım oldukça paslı, ancak derleyici bir optimizasyonun doğru bir şekilde yapılabileceğini doğrulayabiliyorsa, bunu yapacak. Bu derlenmiş ikili adlarını bilmek gerek yok kaynaklanmaktadır DoSomethingve Addyanı sıra gerçeği Addyöntem iki adlandırılmış parametresi vardır, derleyici de bilir DoSomethingyöntemi aslında bir sabit döndürür ve yöntem çağrısı hem satır içi olabilir yöntemin kendisi.

Derleyicinin amacı kaynak dosyaları bir araya getirmek için bir yol değil, bir derleme oluşturmaktır.


Son talimatı retC çağırma kuralını kabul ettiğinizi söyleyin.
chuckj

3

Buradaki genel ilkeler, bire bir eşlemeler ve kanonik temsilcilerin eksikliğidir.

Çoktan bire fenomenin basit bir örneği için, bazı yerel değişkenlerle bir işlev alıp onu makine koduna derlediğinizde ne olacağını düşünebilirsiniz. Değişkenlerle ilgili tüm bilgiler kaybolur çünkü bunlar sadece bellek adresleri haline gelir. Döngüler için benzer bir şey olur. Bir forveya whiledöngü alabilir ve doğru yapılandırılmışsa, jumptalimatlarla aynı makine kodunu alabilirsiniz .

Bu ayrıca makine kodu talimatları için orijinal kaynak kodundan kanonik temsilcilerin eksikliğini de beraberinde getirir. Döngüler oluşturmaya çalıştığınızda, jumpyönergeleri döngü yapılarına nasıl eşlersiniz? Onları fordöngüler veya whiledöngüler yapıyor musunuz?

Mesele, modern derleyicilerin çeşitli katlama ve satır içi formları gerçekleştirmesi gerçeğiyle daha da yorulmaktadır. Bu nedenle, makine koduna ulaştığınızda, düşük seviye makine kodunun hangi yüksek seviyeli yapıların geldiğini söylemek neredeyse imkansızdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.