Twoje pytanie nie jest trywialne. Zacznijmy od tego że komputery pracują w oparciu o język zrozumiały dla nich. Językiem tym jest język maszynowy składający się z liczb. Dla człowieka zapis poleceń jako liczb nie jest zbyt czytelny dlatego powstał język Asembler zamiast liczb oznaczających instrukcję procesora zawiera napis. Język asembler to język bardzo bliski temu co robi procesor. Pierwsze komputery właśnie programowało się w tym języku. Niestety napisanie bardziej skomplikowanych programów było bardzo trudne i czasochłonne. Jak to mówią potrzeba matką wynalazków. Wymyślono aby stworzyć język bardziej zbliżony do języka naturalnego ludzi. Następnie stworzono program który tłumaczył by ciąg instrukcji na kod maszynowy. Taki program nosi nazwę kompilator. Pierwsze kompilatory tłumaczyły jeden do jedne kod napisany w językach wyższego poziomu na kod maszynowy. Tłumaczenie to nie było zbyt optymalne, na pewno wybitny programista asemblera napisał by taki program lepiej, bardziej optymalnie. Jednak nie było to aż tak złe, przyspieszało pracę nad twożenia oprogramowania że można było to pominąć. Oczywiście twórcy kompilatorów nie próżnują, obecnie kompilatory wykonują magię. Posiadają możliwość różnych ustawień pozwalających na optymalizację. W tym miejscu pragnę zauważyć że skoro kompilatory robią różne sztuczki to kod maszynowy powstały na podstawie kodu języka C czy C++ może się różnić od tego co programista napisał w kodzie źródłowym. Nie jest to tłumaczone jeden do jedne. Owszem działanie jest takie jak zaprogramował programista w C jednak sposób realizacji jest troszkę inny. Przykładowo w języku asembler nie ma czegoś takiego jak pętla, każda pętla zamieniana jest na instrukcje skoku.
Oczywiście że są na rynku dostępne programy które umożliwiają dekompilację. Takie programy to np. IDA, Ghidra, Binary Ninja, Radare2. Jednak programy te nie potrafią z plik wykonywalnego odtworzyć idealnie wyglądający kod źródłowy.
Na przykład ty jako programista stworzyłeś program w którym alokujesz pamięć dynamicznie, napisałeś takie coś:
int *t=malloc(5*sizeof(int));
Odwracając plik wykonywalny dekompilator mógłby zwrócić takie coś:
void *t=malloc(5*sizeof(int));
Kod zwrócony przez dekompilaotr jest inny, ciężko się połapać co jest co. Analiza kodu jest utrudniony. Dodam że istnieją specjalne techniki zaciemniające, pozwalające znacznie utrudnić reverse engineering. Tak właśnie nareszcie padło to słowo :)
Ta technika to nic innego jak Reverse engineering czyli po Polsku Inżynieria Odwrotna. Sztuka badania przedmiotów już powstałych (nie tylko programów) tak aby na podstawie gotowego produktu odtworzyć sposób jego działania, jego stworzenia.
Takie wydobywanie nie jest legalne, większość licencji tego zabrania. Jednak mimo to ludzie robią to do czego nie zachęcam. Myślisz jak powstają Craki, generatory kluczy. Ktoś musiał poznać jak działa zabezpieczenie producenta by móc stworzyć jego obejście. Oczywiście jest to nielegalne.
Reverse engineering to nie tylko brudna robota, firmy antywirusowe używają deasamblerów, aby dowiedzieć się jak dany złośliwy program działa.
Pragnę jeszcze zaznaczyć że to co opisałem nie ima się języków takich jak Java czy C#. Języki te są kompilowane do tak zwanego byte code, nie do pliku binarnego. Byte code jesteśmy w stanie odwrócić prawie jeden do jeden tak jak programista to napisał. Oczywiście i tutaj istnieją specjalna narzędzia zaciemniające kod tak by techniki inżynierii wstecznej były utrudnione.