Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > ger.ct > #552329 > unrolled thread
| Started by | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| First post | 2022-04-21 07:36 +0200 |
| Last post | 2022-05-27 19:39 +0200 |
| Articles | 15 — 4 participants |
Back to article view | Back to ger.ct
Onleihe Bonita Montero <Bonita.Montero@gmail.com> - 2022-04-21 07:36 +0200
Re: Onleihe Lothar Kimmeringer <news201705@kimmeringer.de> - 2022-04-21 18:59 +0200
Re: Onleihe Bonita Montero <Bonita.Montero@gmail.com> - 2022-04-22 05:13 +0200
Re: Onleihe "Wendelin Uez" <wuez@online.de> - 2022-04-24 13:03 +0200
Re: Onleihe Bonita Montero <Bonita.Montero@gmail.com> - 2022-04-24 17:24 +0200
Re: Onleihe Dietz Proepper <dietz-usenet@rotfl.franken.de> - 2022-04-24 18:14 +0200
Re: Onleihe Bonita Montero <Bonita.Montero@gmail.com> - 2022-04-24 19:01 +0200
Re: Onleihe "Wendelin Uez" <wuez@online.de> - 2022-04-25 15:43 +0200
Re: Onleihe Bonita Montero <Bonita.Montero@gmail.com> - 2022-04-26 05:02 +0200
Re: Onleihe "Wendelin Uez" <wuez@online.de> - 2022-04-21 15:02 +0200
Re: Onleihe Bonita Montero <Bonita.Montero@gmail.com> - 2022-05-18 08:30 +0200
Es wird noch verrückter ... Bonita Montero <Bonita.Montero@gmail.com> - 2022-05-23 13:07 +0200
Und noch einen oben drauf Bonita Montero <Bonita.Montero@gmail.com> - 2022-05-27 10:11 +0200
Re: Und noch einen oben drauf "Wendelin Uez" <wuez@online.de> - 2022-05-27 12:11 +0200
Re: Und noch einen oben drauf Bonita Montero <Bonita.Montero@gmail.com> - 2022-05-27 19:39 +0200
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2022-04-21 07:36 +0200 |
| Subject | Onleihe |
| Message-ID | <t3qqfg$2lh$1@dont-email.me> |
Ich hab mich gestern hier bei der Stadtbibliothek mal angemeldet, eigentlich nur um Bücher, Zeitschriften und Zeitungen online ausleihen zu können. Die Online-Bücher kann man über eine Seite namens Onleihe ([1]) herunterladen und das ist eine Seite von irgendeiner Firma, die die Technik für Bibliotheken in verschiedenen Ländern Europas managt. Auf jeden Fall sind die heruntergeladenen Bücher und Zeitschriften zeitlich befristet und lassen sich danach nicht mehr mit Adobe Digital Editions (schreckliches Programm) öffnen. Wenn ich versuche, ein PDF mit Calibre zu öffnen, dann scheitert das trotz installiertem De-DRM Plugin. E-Books die ich bei buecher.de gekauft habe lassen sich damit aber einwandfrei entschlüsseln. Ich weiß nicht, ob es am De-DRM-Plugin oder Calibre selbst eine Funktion zum Entschlüsseln von EPUBs hat, aber diese EPUBs aus dem Ordner "My Digital Edidions" lassen sich problemlos mit Calibre öffnen. Hat es hier irgendwie schon mal jemand geschafft, ein Onleihe-PDF zu entschlüsseln ? [1] https://de.wikipedia.org/wiki/Onleihe
[toc] | [next] | [standalone]
| From | Lothar Kimmeringer <news201705@kimmeringer.de> |
|---|---|
| Date | 2022-04-21 18:59 +0200 |
| Message-ID | <j83hmbp0nb5e.dlg@kimmeringer.de> |
| In reply to | #552329 |
Bonita Montero wrote:
> Hat es hier irgendwie schon mal jemand geschafft, ein Onleihe-PDF
> zu entschlüsseln ?
Das zu tun ist genauso legal, wie sich das Zeug von den
einschlaegigen Quellen im Internet zu ziehen. Letzteres
duerfte aber deutlich unkomplizierter sein und du spielst
der Initiative Fair Lesen nicht in die Haende.
Dies ist keine Aufforderung, nur eine Feststellung.
Gruesse, Lothar
--
Lothar Kimmeringer E-Mail: spamfang@kimmeringer.de
PGP-encrypted mails preferred (Key-ID: 0x8BC3CD81)
Always remember: The answer is forty-two, there can only be wrong
questions!
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2022-04-22 05:13 +0200 |
| Message-ID | <t3t6fe$kjm$4@dont-email.me> |
| In reply to | #552363 |
Am 21.04.2022 um 18:59 schrieb Lothar Kimmeringer: > Bonita Montero wrote: > >> Hat es hier irgendwie schon mal jemand geschafft, ein Onleihe-PDF >> zu entschlüsseln ? > > Das zu tun ist genauso legal, wie sich das Zeug von den > einschlaegigen Quellen im Internet zu ziehen. ... Das ist mir komplett egal. > Letzteres duerfte aber deutlich unkomplizierter sein und > du spielst der Initiative Fair Lesen nicht in die Haende. Das geht selten, denn wenn ich z.B. auf boerse.im schaue, dann ist das meiste was dort verlinkt ist schon wieder wg. Copyright Beschwerden offline. > Dies ist keine Aufforderung, nur eine Feststellung. Idiot.
[toc] | [prev] | [next] | [standalone]
| From | "Wendelin Uez" <wuez@online.de> |
|---|---|
| Date | 2022-04-24 13:03 +0200 |
| Message-ID | <t43kuc$bsd$2@dont-email.me> |
| In reply to | #552382 |
> Idiot. Für einen Idioten würde ich angesichts deiner unzureichenden Recherche eher jemanden halten, der öffentlich nach illegalren Praktiken fragt und mit korrekter detaillierter Antwort nicht klar kommt.
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2022-04-24 17:24 +0200 |
| Message-ID | <t43q1s$igu$1@dont-email.me> |
| In reply to | #552595 |
Am 24.04.2022 um 13:03 schrieb Wendelin Uez: >> Idiot. > > Für einen Idioten würde ich angesichts deiner unzureichenden Recherche > eher jemanden halten, der öffentlich nach illegalren Praktiken fragt ... Ach, meinste da kommt die Polizei herbei ? Ich mein Urheberrechtsdelikte sind Antragsdelikte, da müsste irgendein Rechteinhaber kommen und sagen, "der hat was von mir kopiert" oder "der hat meinen Content verteilt". Ist ja so erstmal nicht so ohne weiteres möglich. >... und mit korrekter detaillierter Antwort nicht klar kommt. An der Stelle wo ich mit Idiot geantwortet habe war keine detailierte Antwort.
[toc] | [prev] | [next] | [standalone]
| From | Dietz Proepper <dietz-usenet@rotfl.franken.de> |
|---|---|
| Date | 2022-04-24 18:14 +0200 |
| Message-ID | <20220424181455.6c761db8.dietz-usenet@rotfl.franken.de> |
| In reply to | #552604 |
Bonita Montero <Bonita.Montero@gmail.com> wrote: > Am 24.04.2022 um 13:03 schrieb Wendelin Uez: > >> Idiot. > > > > Für einen Idioten würde ich angesichts deiner unzureichenden > > Recherche eher jemanden halten, der öffentlich nach illegalren > > Praktiken fragt ... > > Ach, meinste da kommt die Polizei herbei ? Nicht deswegen. Aber gegen Beifang hat ggf. niemand was. -- SIC SEMPER +--|=======> TYRANNIS
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2022-04-24 19:01 +0200 |
| Message-ID | <t43vo1$tg$1@dont-email.me> |
| In reply to | #552611 |
Am 24.04.2022 um 18:14 schrieb Dietz Proepper: > Bonita Montero <Bonita.Montero@gmail.com> wrote: > >> Am 24.04.2022 um 13:03 schrieb Wendelin Uez: >>>> Idiot. >>> >>> Für einen Idioten würde ich angesichts deiner unzureichenden >>> Recherche eher jemanden halten, der öffentlich nach illegalren >>> Praktiken fragt ... >> >> Ach, meinste da kommt die Polizei herbei ? > > Nicht deswegen. Aber gegen Beifang hat ggf. niemand was. Wieso Beifang ? Nochmal: das ist ein Antrags-Delikt, d.h. das wird nicht von Staats wegen allein verfolgt und ein Rechteinhaber muss hier seine Rechte geltend machen, und das kann er nicht weil er nicht weiß ob er betroffen ist.
[toc] | [prev] | [next] | [standalone]
| From | "Wendelin Uez" <wuez@online.de> |
|---|---|
| Date | 2022-04-25 15:43 +0200 |
| Message-ID | <t46m89$on5$2@dont-email.me> |
| In reply to | #552615 |
> Nochmal: das ist ein Antrags-Delikt, d.h. das wird nicht > von Staats wegen allein verfolgt und ein Rechteinhaber muss hier seine > Rechte geltend machen, und das kann er nicht weil er nicht weiß ob er > betroffen ist. Deine Art der Argumentation ist generall sozialschädlich aka asozial, denn sie beruht erkennbar nicht darauf, Recht von Unrecht zu unterscheiden, sondern Unrecht durchzusetzen auf Kosten anderer, die keinen solchen Rechtsanspruch geltend machen können. Die fehlende rechtliche Verfolgungsmöglichkeit ist übrigens kein Friebrief, Verbotenes trotzdem tun zu dürfen. Im übrigen gibt es außerdem auch Dinge, die "tut man nicht", auch wenn sie ungestraft bleiben würden. Deine Rechthaberei hat dieselbe Qualität wie die, einem Blinden das Geld aus der hingehaltenen Mütze zu nehmen, der kann sich auch nicht wehren. Trotzdem ist das weder rechtens noch zulässig.
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2022-04-26 05:02 +0200 |
| Message-ID | <t47nc6$aiv$1@dont-email.me> |
| In reply to | #552692 |
> Deine Art der Argumentation ist generall sozialschädlich aka asozial, > denn sie beruht erkennbar nicht darauf, Recht von Unrecht zu > unterscheiden, sondern Unrecht durchzusetzen auf Kosten anderer, die > keinen solchen Rechtsanspruch geltend machen können. Dir geht es nicht um Moral, sondern um anderen ein Stigma anzuhängen. Wer hier asozial ist, das bist Du.
[toc] | [prev] | [next] | [standalone]
| From | "Wendelin Uez" <wuez@online.de> |
|---|---|
| Date | 2022-04-21 15:02 +0200 |
| Message-ID | <t3sapc$s14$1@dont-email.me> |
| In reply to | #552329 |
Das DRM-AddOn eines Drittherstellers zur Entfernung der Entschlüsselung funktioniert mit der neueren PDF-Verschlüsselung nicht mehr, fürher konnte man damit eine unverschlüsselte Kopie erzeugen.
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2022-05-18 08:30 +0200 |
| Message-ID | <t623pc$45g$1@dont-email.me> |
| In reply to | #552329 |
Auf jeden Fall habe ich mal angenommen, dass Adobe Digital Editions
das PDF im Prozess-Adressraum liegen hat und ich es aus diesem aus-
lesen könnte. Da hab ich mir mal ein kleines Windows-Programm in
C++20 geschrieben das den Prozess-Adressraum nach einem Speicherblock
durchsucht der einen PDF-Header ("%PDF-*.*" - * steht für Versions-
nummer-Anteile) sowie einen PDF-Tailer ("%%EOF") enthält. Eigentlich
ganz einfach und hier ist der Code:
#include <Windows.h>
#include <iostream>
#include <vector>
#include <charconv>
#include <cstring>
#include <vector>
#include <stdexcept>
#include <sstream>
#include <cctype>
#include <fstream>
using namespace std;
template<typename FilterFn>
requires requires( FilterFn fn, MEMORY_BASIC_INFORMATION &mbi ) { { fn(
mbi ) } -> std::convertible_to<bool>; }
vector<vector<MEMORY_BASIC_INFORMATION>> pageTree( HANDLE hProcess,
FilterFn filterFn );
void savePdf( char const *header, size_t nBytes );
using XHANDLE = unique_ptr<void, decltype([]( HANDLE h ) { h && h !=
INVALID_HANDLE_VALUE && CloseHandle( h ); })>;
int main( int argc, char **argv )
{
if( argc < 2 )
return EXIT_FAILURE;
try
{
DWORD dwProcessId = [&]() -> DWORD
{
DWORD dwRet;
if( from_chars_result fcr = from_chars( argv[1], argv[1] + strlen(
argv[1] ), dwRet ); fcr.ec != errc() || *fcr.ptr )
throw invalid_argument( "process-id unparseable" );
return dwRet;
}();
XHANDLE hProcess( [&]() -> HANDLE
{
HANDLE hRet = OpenProcess( PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ, FALSE, dwProcessId );
if( !hRet )
throw system_error( (int)GetLastError(), system_category(), "can't
open process" );
return hRet;
}() );
vector<vector<MEMORY_BASIC_INFORMATION>> vvmbi = pageTree(
hProcess.get(), []( MEMORY_BASIC_INFORMATION &mbi ) -> bool
{
return mbi.State == MEM_COMMIT;
} );
vector<char> processRegion;
for( vector<MEMORY_BASIC_INFORMATION> const &vmbi : vvmbi )
for( MEMORY_BASIC_INFORMATION const &vmbi : vmbi )
{
processRegion.resize( vmbi.RegionSize );
if( !ReadProcessMemory( hProcess.get(), vmbi.BaseAddress,
to_address( processRegion.begin() ), vmbi.RegionSize, nullptr ) )
continue;
char const *end;
auto detectBegin = [&]( char const *p ) -> char const *
{
if( end - p < 8 )
return nullptr;
if( memcmp( p, "%PDF-", 5 ) != 0 )
return p;
if( !isdigit( p[5] ) || p[6] != '.' || !isdigit( p[7] ) )
return p;
return p + 8;
};
auto detectEnd = [&]( char const *p ) -> char const *
{
if( end - p < 5 )
return nullptr;
if( memcmp( p, "%%EOF", 5 ) != 0 )
return p;
return p + 5;
};
char const
*scn = to_address( processRegion.begin() ),
*next, *header;
end = scn + processRegion.size();
while( scn != end )
{
next = detectBegin( scn );
if( !next )
goto nextRegion;
if( next == scn )
{
++scn;
continue;
}
header = scn;
for( scn = next; scn != end; ++scn )
{
next = detectEnd( scn );
if( !next )
goto nextRegion;
if( next != scn )
{
savePdf( header, next - header );
scn = next;
break;
}
}
}
nextRegion:;
}
}
catch( exception const &exc )
{
cout << exc.what() << endl;
}
}
void savePdf( char const *header, size_t nBytes )
{
string strFileName;
for( unsigned iFileNo = 0; ; )
{
strFileName = (ostringstream() << iFileNo << ".pdf").str();
using FIND_HANDLE = unique_ptr<void, decltype([]( HANDLE h ) { h && h
!= INVALID_HANDLE_VALUE && FindClose( h ); })>;
WIN32_FIND_DATAA fd;
FIND_HANDLE fh( FindFirstFileA( strFileName.c_str(), &fd ) );
if( fh.get() == INVALID_HANDLE_VALUE )
if( DWORD dwErr = GetLastError(); dwErr == ERROR_FILE_NOT_FOUND )
break;
else
throw system_error( (int)dwErr, system_category(), "error
enumerating files to write" );
if( !++iFileNo )
return;
}
ofstream ofs;
ofs.exceptions( ofstream::failbit | ofstream::badbit );
ofs.open( strFileName.c_str(), ofstream::out | ofstream::trunc |
ofstream::binary );
ofs.write( header, nBytes );
}
template<typename Fn>
requires requires( Fn fn, MEMORY_BASIC_INFORMATION &mbi ) { { fn( mbi )
} -> std::convertible_to<bool>; }
void enumProcessMemory( HANDLE hProcess, Fn fn );
template<typename FilterFn>
requires requires( FilterFn fn, MEMORY_BASIC_INFORMATION &mbi ) { { fn(
mbi ) } -> std::convertible_to<bool>; }
vector<vector<MEMORY_BASIC_INFORMATION>> pageTree( HANDLE hProcess,
FilterFn filterFn )
{
vector<vector<MEMORY_BASIC_INFORMATION>> vvmbis;
enumProcessMemory( hProcess, [&]( MEMORY_BASIC_INFORMATION &mbi ) -> bool
{
if( !filterFn( mbi ) )
return true;
if( !vvmbis.size() || vvmbis.back().back().BaseAddress !=
mbi.BaseAddress )
vvmbis.emplace_back( vector<MEMORY_BASIC_INFORMATION>() );
vvmbis.back().emplace_back( mbi );
return true;
} );
return vvmbis;
}
template<typename Fn>
requires requires( Fn fn, MEMORY_BASIC_INFORMATION &mbi ) { { fn( mbi )
} -> std::convertible_to<bool>; }
void enumProcessMemory( HANDLE hProcess, Fn fn )
{
MEMORY_BASIC_INFORMATION mbi;
for( char *last = nullptr; ; last = (char *)mbi.BaseAddress +
mbi.RegionSize )
{
size_t nBytes = VirtualQueryEx( hProcess, last, &mbi, sizeof mbi );
if( nBytes != sizeof mbi )
if( DWORD dwErr = GetLastError(); dwErr == ERROR_INVALID_PARAMETER )
break;
else
throw system_error( (int)dwErr, system_category(), "can't query
process pages" );
if( !fn( mbi ) )
break;
}
}
Mit verschiedenen PDF-Readern funktioniert das auch einwandfrei und
der Code speichert alle in dem Prozess-Adressraum gefundenen PDF ab
0.pdf durchnummeriert und überschreibt dabei keine exisitierenden
Dateien.
Dummerweise funktoniert das nicht mit Adobe Digital Editions bzw.
ich vermute, dass die das PDF verschlüsselt im RAM halten und on
the fly entschlüsseln.
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2022-05-23 13:07 +0200 |
| Subject | Es wird noch verrückter ... |
| Message-ID | <t6fpt3$hq0$1@dont-email.me> |
| In reply to | #555049 |
Da die letzte idee mit dem Durchscannen der Prozesse nicht funktioniert
hat ist mir nochwas durch den Kopf gegangen:
Speicherverwaltung bei heutiger Software funktioniert so, dass Speicher-
anforderungen wie malloc() in C oder new in C++ aus einem Pool von Spei-
cher im Userland bedient wird und wenn der keine passende Blockgröße
abgibt, dann wird der Kernel nach einem größeren Block gefragt der den
Pool dann erweitert. Das gilt dann für kleinere oder mittelgroße Alloka-
tionen. Große Allokationen gehen immer an den Kernel und werden nie
direkt an den Pool, sondern auch an den Kernel, zurückgegeben damit
sich der Verbrauch an Speicher in Grenzen hält.
Meine Idee war jetzt: wenn Digital Editions (DE) das PDF in den Speicher
lädt, dann sicher erstmal in einen größeren Block, der bei der Freigabe
garantiert an den Kernel zurückgeht. Das Dumme wäre, wenn DE den Block
vor der Freigabe beliebig überschreibt. So ein simples memset() kann
man im Binary nämlich oft gar nicht so leicht ausmachen unter vielen
anderen memset()-Aufrufen, und da die Funktion bei eigentlich allen
Compilern intrinsisch ist kann die gar komlpet ge-inline-t sein.
Ich hatte halt angenommen, das vielleicht eine Chance besteht, dass das
nicht so ist. Dann müsste ich nur den Aufruf von VirtualFree(), das ist
die Funktion um unter Windows virtuellen Speicher wieder freizugeben,
also sowas wie ähnlichesmunmap(), extern trappen.
Es gibt unter Windows eine recht komfortable Debugging API mit dem man
den Ablauf einer Anwendung nachverfolgen kann. Man kann Events darüber
empfangen wenn der Prozess kreiert oder beendet wurde, wenn ein Thread
gestartet oder beendet wurde, wenn eine DLL geladen oder entladen wurde,
wenn eine sog SEH-Exception generiert wurde - SEH Exceptions ersetzen
sind das selbe wie Signale unter Unix, aber nur die synchronen davon,
also die die selbst aus dem Instruktions-Strom kommen wie SIGSEGV oder
SIGFPE (das aber 10 mal geiler als unter Unix) oder auch für für mich
so unwichtige Dinge wie, wenn ein Debug-String ausgegeben werden soll.
Das Trappen eines DLL-Einstiegspunkts muss man selbst übernehmen indem
man beim Laden einer DLL einfach einen Breakpoint auf den Einstiegs-
punkt setzt. Für den Breakpoint gibt es bei x86 eine spezielle Instruk-
tion, den Interrupt 3 (INT3), der nur einen Byte lang ist und den Opcode
$CC hat.
Das Trappen ist dann aber nicht so einfach, denn wenn ich das trappe,
dann muss ich das Programm dann wieder weiterlaufen lassen, also den
Trap wegnehmen. Wenn ich das dann aber mache, dann können andere Threads
unbemerkt die vormals getrappte Funktion nutzen. Also mache ich folgen-
des: ich protokolliere das Kreieren aller Threads in einer Hash-Map
die die Thread-ID - die später Teil jeder der Debug-Benachrichtigung,
auch der über den Trap - und lege alle Threads bis auf den wo der Trap
passiert ist nach dem Wegnehmen des Traps schlafen und setze, bevor
ich den Prozess mit nur einem laufenden Thread wieder losrennen lasse
einen Trap (INT3) auf die Rücksprungsadrese des der Funktion. Teil
des ersten Trap ist nämlich eine Kontext-Datenstruktur wo ich die
Register sehe, also unter anderem auch den Stack-Pointer, dass ich
die Rücksprung-Adresse auf dem Stack auslesen kann. Habe ich einen
Trap auf der RÜcksprungadresse, dann schalte ich diesen aus, den
ersten wieder an, mache wieder alle anderen Threads lauffähig und
warte auf den nächsten Trap auf den Einstiegspunkt auf die überwachte
Funktion.
Aus dem Stack kann ich beim ersten der beiden Traps dann noch die
Paramter für VirtualFree() auslesen, unter anderem auch die Adresse
des Speicher-Blocks. Anders als bei munmap() kriegt diese Funktion
keine Länge mit bzw. der Kernel weiß selbst, wie lange ein reservierter
Bereich ist. Innerhalb eines solchen reservierten Bereichs können die
Pages unterschiedlich belegt sein. Die können auf gar keinen zugreif-
baren Speicher gemappt sein, auf nur lesbaren, nur les und schreibbaren,
ausführbaren und was weiß ich noch alles (mehr steht in der Doku zu
VirtualAlloc()). Daher muss ich mit VirtualQueryEx() diese reservierte
Range nach lesbaren Bereichen durchlaufen.
Jedes dieser reservierten Bereiche durchsuche ich dann nach etwas, das
nach einem PDF aussieht, d.h. einen Header von %PDF*.* (* wg. Version)
und einen Tailer von %%EOF hat.
Das Ganze würde ich ja speichern, aber ich hab den Debugger mit mit
meinem kleinen eigenen "Debugger" angeworfen und der Suchlauf hat
leider bei DE rein gar nichts gefunden, dass ich dann erst gar keine
Speicher-Funktion geschrieben habe.
Also ein recht trickreiches Programm für _NIX_.
Eine kleine Sache war auch noch nicht unkompliziert: ich bekomme ja
Benachrichtigungen für das Laden und Entladen von DLLs, und was ich
dann benötige ist der relative Offset des Einstiegspunkts in der DLL.
Da mein Programm die DLL aber nicht laden kann weil es selbst ein
64 Bit programm ist, DE aber ein 32 Bit Programm, habe ich mir ein
kleines 32-bittiges Hilfs-Programm geschrieben, dass die selbe DLL
(kernel32.dll in dem Fall) lädt und den relativen Offset des getrapp-
ten Einstiegspunkt als Rückgaberwer ausgibt.
Ich fühl mich in letzter Zeit irgendwi mental immer fitter, dass mir
sowas mega Spaß macht und ich keinerlei Probleme mit der Kondition
habe. Ich habe gestern 14 Stunden am Stück programmiert und war
danach noch super-fit.
Hier der Code ohne das besafte Hilfs-Programm:
#include <Windows.h>
#include <tlhelp32.h>
#include <iostream>
#include <cstring>
#include <stdexcept>
#include <system_error>
#include <thread>
#include <unordered_map>
#include <algorithm>
#include <atomic>
#include <utility>
#include <cassert>
#include <iomanip>
#if defined(_MSC_VER)
#pragma warning(disable: 6335)
// disable pointer narrowing / widening warnings
#pragma warning(disable: 4302)
#pragma warning(disable: 4312)
#pragma warning(disable: 4311)
#endif
using namespace std;
using XHANDLE = unique_ptr<void, decltype([]( void *h ) { h && h !=
INVALID_HANDLE_VALUE && CloseHandle( (HANDLE)h ); })>;
[[noreturn]]
void throwSysErr( char const *str );
size_t getProcOffset( char const *dll, char const *exp );
using rpm_it = typename vector<char>::const_iterator;
size_t getPageSize();
template<typename Pointer>
requires is_pointer_v<Pointer>
Pointer curPage( Pointer pointer, size_t pageSize );
void hookVirtualFree( HANDLE hProcess, LPVOID lpAddress, DWORD dwSize,
DWORD dwFreeType );
int wmain( int argc, wchar_t *argv[] )
{
if( argc < 2 )
return EXIT_FAILURE;
PROCESS_INFORMATION pi;
auto terminate = [&]()
{
TerminateProcess( pi.hProcess, 0 );
ExitProcess( EXIT_FAILURE );
};
try
{
size_t
offsetFree( getProcOffset( "msvcrt.dll", "free" ) ),
offsetVirtualFree( getProcOffset( "kernel32.dll", "VirtualFree" ) );
wstring commandLine;
for( size_t i = 1; i != (unsigned)argc; ++i )
commandLine += i >= 2 ? L" \"" : L"\"",
commandLine += argv[i],
commandLine += L"\"";
STARTUPINFOW si;
memset( &si, 0, sizeof si );
si.cb = sizeof si;
if( !CreateProcess( nullptr, (LPWSTR)commandLine.c_str(), nullptr,
nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi) )
throwSysErr( "can't create process" );
DWORD dwProcessId = GetProcessId( pi.hProcess );
if( !dwProcessId )
throw exception( "can't get process id" );
if( !DebugActiveProcess( dwProcessId ) )
throwSysErr( "can't enable debugging for process" );
if( ResumeThread( pi.hThread ) == -1 )
throwSysErr( "can't resume main thread" );
using dll_map = unordered_map<LPVOID, HANDLE>;
using dll_map_it = typename dll_map::iterator;
using threads_map = unordered_map<DWORD, HANDLE>;
dll_map dllMap;
threads_map threadMap;
DEBUG_EVENT de;
auto resume = [&]( DWORD dwContinueStatus = DBG_CONTINUE )
{
if( !ContinueDebugEvent( de.dwProcessId, de.dwThreadId,
dwContinueStatus ) )
terminate();
};
DWORD dwUniqueThreadId;
auto stopExceptUnique = [&]( DWORD dwUniqueId )
{
for( pair<DWORD const, HANDLE> const &thr : threadMap )
if( thr.first != dwUniqueId )
if( SuspendThread( thr.second ) == -1 )
terminate();
dwUniqueThreadId = dwUniqueId;
};
auto resumeExceptUnique = [&]()
{
for( pair<DWORD const, HANDLE> const &thr : threadMap )
if( thr.first != dwUniqueThreadId )
if( ResumeThread( thr.second ) == -1 )
terminate();
};
auto addThread = [&]()
{
constexpr DWORD THREAD_ACCESS = THREAD_GET_CONTEXT |
THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME;
HANDLE hThread = OpenThread( THREAD_ACCESS, FALSE, de.dwThreadId );
if( !hThread || !threadMap.emplace( de.dwThreadId, hThread ).second )
terminate();
};
char breakpointBeforeContent;
void *breakpointAddr = nullptr;
auto trap = [&]( void *int3Addr )
{
static char const int3 = (char)0xCC;
SIZE_T bytes;
if( !ReadProcessMemory( pi.hProcess, int3Addr,
&breakpointBeforeContent, 1, &bytes ) || bytes != 1 )
terminate();
breakpointAddr = int3Addr;
if( !WriteProcessMemory( pi.hProcess, int3Addr, &int3, 1, &bytes ) ||
bytes != 1 )
terminate();
};
auto untrap = [&]()
{
SIZE_T actually;
if( !WriteProcessMemory( pi.hProcess, breakpointAddr,
&breakpointBeforeContent, 1, &actually ) || actually != 1 )
terminate();
breakpointAddr = nullptr;
};
char *virtualFreeAddr = nullptr;
while( WaitForDebugEventEx( &de, INFINITE ) )
switch( de.dwDebugEventCode )
{
case CREATE_PROCESS_DEBUG_EVENT:
{
cout << "process created (PID: " << de.dwProcessId << ", TID: " <<
de.dwThreadId << ")" << endl;
addThread();
resume();
break;
}
case CREATE_THREAD_DEBUG_EVENT:
{
cout << "thread created (TID: " << dec << de.dwThreadId << ")" << endl;
addThread();
resume();
break;
}
case EXIT_THREAD_DEBUG_EVENT:
{
cout << "thread terminated: (TID: " << dec << de.dwThreadId << ")"
<< endl;
threads_map::iterator itThread = threadMap.find( de.dwThreadId );
if( itThread == threadMap.end() || !CloseHandle( itThread->second ) )
terminate;
threadMap.erase( itThread );
resume();
break;
}
case EXCEPTION_DEBUG_EVENT:
{
typename threads_map::iterator itThread = threadMap.find(
de.dwThreadId );
if( itThread == threadMap.end() )
terminate();
WOW64_CONTEXT context;
memset( &context, 0, sizeof context );
context.ContextFlags = WOW64_CONTEXT_ALL;
if( !Wow64GetThreadContext( itThread->second, &context ) )
terminate();
cout << "exception (code: " << hex << setw( 8 ) << setfill( '0' )
<< de.u.Exception.ExceptionRecord.ExceptionCode << ")" << endl;
if( context.Eip - 1 == (DWORD)breakpointAddr )
{
auto untrapAndBefore = [&]()
{
untrap();
--context.Eip;
if( !Wow64SetThreadContext( itThread->second, &context ) )
terminate();
};
if( context.Eip - 1 == (DWORD)virtualFreeAddr )
{
cout << "\tVirtualFree() trapped" << endl;
untrapAndBefore();
DWORD aDwStack[4];
if( SIZE_T read; !ReadProcessMemory( pi.hProcess, (void
*)context.Esp, aDwStack, sizeof aDwStack, &read ) || read != sizeof
aDwStack )
terminate();
void *returnAddr = (void *)aDwStack[0];
LPVOID lpAddress = (LPVOID)aDwStack[1];
DWORD dwSize = aDwStack[2], dwFreeType = aDwStack[3];
trap( returnAddr );
stopExceptUnique( de.dwThreadId );
hookVirtualFree( pi.hProcess, lpAddress, dwSize, dwFreeType );
}
else
{
cout << "\treturn of VirtualFree() trapped" << endl;
untrapAndBefore();
trap( virtualFreeAddr );
resumeExceptUnique();
}
resume( DBG_EXCEPTION_HANDLED );
}
else
resume( DBG_EXCEPTION_NOT_HANDLED );
break;
}
case EXIT_PROCESS_DEBUG_EVENT:
cout << "process exited" << endl;
terminate();
break;
case LOAD_DLL_DEBUG_EVENT:
{
constexpr size_t PATH_LENGTH = 256;
wchar_t dllPath[PATH_LENGTH];
DWORD dwRet = GetFinalPathNameByHandleW( de.u.LoadDll.hFile,
dllPath, PATH_LENGTH, 0 );
if( !dwRet || dwRet > PATH_LENGTH - 1 )
terminate();
wcout << L"DLL loaded: " << dllPath << endl;
char *dllBase = (char *)de.u.LoadDll.lpBaseOfDll;
if( !dllMap.emplace( dllBase, de.u.LoadDll.hFile ).second )
terminate();
constexpr wchar_t const *KERNEL32_PATH =
L"\\\\?\\C:\\Windows\\SysWOW64\\kernel32.dll";
if( _wcsicmp( dllPath, KERNEL32_PATH ) == 0 )
{
virtualFreeAddr = dllBase + offsetVirtualFree;
trap( virtualFreeAddr );
}
resume();
break;
}
case UNLOAD_DLL_DEBUG_EVENT:
{
cout << "DLL unloaded " << endl;
dll_map_it itDll = dllMap.find( de.u.UnloadDll.lpBaseOfDll );
if( itDll == dllMap.end() || !CloseHandle( itDll->second ) )
terminate();
dllMap.erase( itDll );
resume();
break;
}
default:
resume();
break;
}
terminate();
}
catch( exception const &exc )
{
cout << exc.what() << endl;
terminate();
}
}
using region_vector = string;
using region_it = typename region_vector::const_iterator;
vector<string_view> scanRegion( string_view const &sv );
void hookVirtualFree( HANDLE hProcess, LPVOID lpAddress, DWORD dwSize,
DWORD dwFreeType )
{
try
{
size_t
pageSize = getPageSize(),
alignHead = curPage( (char *)lpAddress, pageSize ) - (char *)lpAddress;
lpAddress = (char *)lpAddress - alignHead;
auto query = [&]( LPCVOID lpAddress ) -> MEMORY_BASIC_INFORMATION
{
MEMORY_BASIC_INFORMATION mbi;
if( VirtualQueryEx( hProcess, lpAddress, &mbi, sizeof mbi ) != sizeof
mbi )
throwSysErr( "can't get process memory region attributes" );
return mbi;
};
char *regionBegin = (char *)query( lpAddress ).AllocationBase;
auto scan = [&]<typename Callback>( Callback callback )
requires requires( Callback callback, char *p ) { { callback( p, p ) }; }
{
MEMORY_BASIC_INFORMATION mbi;
auto validProtect = []( DWORD dwProtect ) ->bool { return dwProtect
== PAGE_READONLY || dwProtect == PAGE_READWRITE || dwProtect ==
PAGE_WRITECOPY; };
for( char *scn = regionBegin; ; scn += mbi.RegionSize )
if( (mbi = query( scn )).BaseAddress == scn )
{
if( mbi.AllocationBase != regionBegin || !mbi.RegionSize )
return;
if( mbi.State == MEM_COMMIT && validProtect( mbi.Protect ) )
callback( scn, scn + mbi.RegionSize );
}
else
throw invalid_argument( "VirtualQuey() returned wrong base address" );
};
region_vector processRegion;
size_t maxSize = 0;
scan( [&]( char *regionBegin, char *regionEnd )
{
maxSize = maxSize >= (size_t)(regionEnd - regionBegin) ? maxSize :
regionEnd - regionBegin;
} );
processRegion.reserve( maxSize );
scan( [&]( char *regionBegin, char *regionEnd )
{
processRegion.resize( regionEnd - regionBegin );
SIZE_T read;
if( !ReadProcessMemory( hProcess, regionBegin, processRegion.data(),
regionEnd - regionBegin, &read ) || read != regionEnd - regionBegin )
return;
scanRegion( string_view( processRegion.cbegin(),
processRegion.cend() ) );
} );
}
catch( exception const & )
{
}
}
vector<string_view> scanRegion( string_view const &sv )
{
constexpr size_t
HEADER_SIZE = 8,
TAIL_SIZE = 5,
HEADER_AND_TAIL = HEADER_SIZE + TAIL_SIZE;
if( sv.cend() - sv.cbegin() <= HEADER_AND_TAIL )
return vector<string_view>();
using region_it = typename string_view::const_iterator;
auto detectBegin = [&]( region_it it ) -> bool
{
(void)(it + HEADER_SIZE);
if( memcmp( to_address( it ), "%PDF-", 5 ) != 0 )
return false;
if( !isdigit( it[5] ) )
return false;
if( it[6] != '.' )
return false;
if( !isdigit( it[7] ) )
return false;
return true;
};
auto detectEnd = [&]( region_it it ) -> bool
{
(void)(it + TAIL_SIZE);
if( memcmp( to_address( it ), "%%EOF", TAIL_SIZE ) != 0 )
return false;
return true;
};
region_it scn = sv.cbegin();
vector<string_view> results;
do
{
if( !detectBegin( scn ) )
{
++scn;
continue;
}
region_it header = scn;
for( scn += HEADER_AND_TAIL; scn <= sv.end() - TAIL_SIZE; )
if( !detectEnd( scn ) )
++scn;
else
{
results.emplace_back( header, scn + TAIL_SIZE );
scn += TAIL_SIZE;
}
} while( scn <= sv.end() - HEADER_AND_TAIL );
return results;
}
[[noreturn]]
void throwSysErr( char const *str )
{
throw system_error( (int)GetLastError(), system_category(), str );
}
size_t getProcOffset( char const *dll, char const *exp )
{
string strCommandLine;
strCommandLine += "DllProcBase \"";
strCommandLine += dll;
strCommandLine += "\" \"";
strCommandLine += exp;
strCommandLine += "\"";
STARTUPINFOA si;
memset( &si, 0, sizeof si );
si.cb = sizeof si;
PROCESS_INFORMATION pi;
static char const strErr[] = "can't get 32 bit procedure address";
if( !CreateProcessA( nullptr, (LPSTR)strCommandLine.c_str(), nullptr,
nullptr, FALSE, DETACHED_PROCESS, nullptr, nullptr, &si, &pi ) )
throwSysErr( strErr );
XHANDLE
xhProcess( pi.hProcess ),
xhThread( pi.hThread );
while( WaitForSingleObject( pi.hProcess, INFINITE ) == WAIT_FAILED );
DWORD dwOffset;
if( !GetExitCodeProcess( pi.hProcess, &dwOffset ) )
throwSysErr( strErr );
return dwOffset;
}
template<typename Pointer>
requires is_pointer_v<Pointer>
Pointer curPage( Pointer pointer, size_t pageSize )
{
return (Pointer)((size_t)pointer & -(ptrdiff_t)pageSize);
}
size_t getPageSize()
{
static atomic<size_t> aDwPageSize( 0 );
size_t pageSize;
if( !(pageSize = aDwPageSize.load( memory_order_relaxed )) )
{
SYSTEM_INFO si;
GetSystemInfo( &si ),
aDwPageSize.store( pageSize = si.dwPageSize, memory_order_relaxed );
}
return pageSize;
}
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2022-05-27 10:11 +0200 |
| Subject | Und noch einen oben drauf |
| Message-ID | <t6q13n$47k$1@dont-email.me> |
| In reply to | #555690 |
Meine weitere Vermutung war, dass die die PDFs zwar im Speicher am Stück decodieren, dass dann irgendwie parsen und in eine hierarchische Datenstrutur bringen, die einfacher zu handeln ist als ein lineares PDF im Speicher, und am Ende vor der Freigaben des Speicherblocks in dem das PDF liegt Sicherheits-halber einfach ein memset() drüberlaufen lassen der das entschlüsselte PDF löscht. Also könnte ich eigentlich genauso wie in der vorangegangenen Situation aus Debugger-Sicht das memset() hooken. Das wäre aber von der Perfor- mance her nicht möglich, denn memset ist eine mit recht hoher Frequenz aufgerufene Funktion. Ein Debug-Event führt ja erstmal dazu, dass alle Threads in Ziel Prozess angehalten werden und mein Debugger dann weiter entscheiden kann. Meine ursprüngliche Vorgehensweise war ja, dass ich im Folgenden dann den Breakpoint auf dem Einstiegspunkt den ich getrappt habe wegnehme, nur den Thread der diese Funktion gerade aufgerufen hat bis zu einem Breakpoint an der Rücksprungs-Adresse weiterlaufen lasse, dann den ersten Breakpoint wieder einschalte, den zweiten ausschalte und dann alle anderen Threads wieder aktiviere. Würde ich diesen ganzen Aufwand für jedes popelige memset() machen, dann wäre Digital Editions von der Performace her sicher nicht mehr benutzbar. Also dachte ich mir was anderes aus: wie wäre es denn, wenn ich das memset() direkt im Ziel-Prozess patche ? Das führte aber dann doch zu einem enormen Aufwand: Ich musste erst eine eigenen DLL in den Ziel-Prozess bringen. Das Problem ist dann aber: es gibt einen Aufruf LoadLibrary(), aber mit dem kann ich nur DLLs in den eigenen Prozess laden. Meine Lösung war dann einfach folgende: ich lade die DLL in meinen eigenen Prozess und lasse die vom Loader im Kernel implizit auf ihre Lade-Adresse relokie- ren. Dann ermittle ich den von der DLL benötigten Adressraum und ver- suche im Ziel-Prozess mit VirtualAllocEx() genau an der selben Adresse Speicher zu allokieren. Gelingt das nicht, dann entlade ich die DLL wieder und reserviere im injezierenden Prozess den Adressraum den die DLL zuvor belegt hat. Dann versuche ich die DLL wieder neu zu laden und weil ich zuvor ihre alte Lade-Adresse reserviert habe muss das an einer anderen Adresse geschehen. Das mache ich dann eben so lange bis ich eine Lade-Adresse erwischt habe die auch im Ziel-Prozess allokierbar ist. Die DLL kopiere ich dann einfach mit WriteProcesMemory() in den Ziel- prozess und starte an der Adresse eines Exports dieser DLL mit Create RemoteThread() einen eigenen Thread. Das Problem dabei ist: das was ich im Ziel-Prozess dann habe ist keine normal geladene DLL. Wenn ich eine DLL normal in einen Prozess lade, dann lädt der Kernel auch ihre ganzen abhängigen DLLs und verküpft Pointer in Daten und Code dieser DLLs untereinander, das das Ganze dann auf die ganzen Lade-Adressen der DLL und der abhängigen DLLs passt. D.h. ich kann mich nicht darauf verlassen, dass mein Code dann beliebige Library-Abhängigkeiten nutzen kann. Die Lösung ist aber gar nicht so komplex: denn ist gibt bestimtme Abhängigkeiten die der injezierende und injezierte Prozess miteinan- der teilen die immer an der selben Adresse liegen. Die Library kernel32.dll wird nämlich ganz früh beim initialisieren des Exe- cutable-Image geladen, noch bevor da irgendwelcher Code läuft. Und die wird nicht an eine beliebige Adresse geladen, sondern an eine bevorzugte Adresse die im Header der DLL steht. Und die wird so lange bevorzugt so lange keine vor ihr geladene DLL da schon stehen wollte oder zufällig da geladen ist. Die Sache ist aber: kernel32.dll steht noch mit ganz vorne in der Linker-Reihenfolge und die anderen System-spezifischen DLLs die noch vor kernel32.dll geladen werden haben von Microsoft so ausge- legt andere bevorzugte Ladeadressen. Das soll ermöglichen, dass häufig benutzte DLLs gleichermaßen in mehrere Prozesse gemappt werden können und nur einmal Speicher belegen. Würden solche DLLs in jeden Prozess an unterschiedliche Adressen relokiert werden, dann würden die mehrfach Speicher verbrauchen. Auf jeden Fall bin ich damit schon mal auf der sicheren Seite beim Aufruf von Fuktionen von kernel32.dll. Und da gibt es eine für den Fall nutzliche Funktion, das bekannte LoadLibrary(), das eine DLL nachlädt. Wenn ich jetzt dann im Ziel-Prozess eine DLL nachelade, dann zieht die ihre ganzen DLL-Abhängigkeiten hinterher und ich kann beliebigen Code im Ziel-Prozess zur Ausführung bringen. Das habe ich dann auch so gemacht bzw. der injezierende Prozess reserviert im Ziel-Prozess Speicher für einen String, der dann den Namen der nachzuladenden DLL enthält und der im Ziel-Prozess kreierte Remote-Thread kriegt als Parameter die Adresse des Strings, damit er dann eine beliebige DLL nachladen kann. Die nachgeladnene DLL hat keinerlei spezifische Einstiegspunkte die von der injezierten DLL angesprochen werden, sondern muss ihre ganze Arbeit in DllMain() erledigen. Da man da nur kurt verweilen darf wird aus DllMain() dann eben schnell ein Thread keriert der den Rest erledigt. So, und jetzt kommt die Sache mit dem Hooken von memset(): Ich habe mir testweise ein kleines Programm geschrieben, dass alle von ADE geladenen DLLs listet und nachhsaut, welche davon einen Einstiegs- punkt haben der memset() heißt. Dann habe ich geschaut, ob denn alle Einstigspunkte die selbe Aufrufs-Konvention haben und das Gleiche tun, also Speicher löschen. Alle Einstiegspunkte benutzten die __cdecl Konvention, d.h. alle Prameter wurden auf dem Stack übergeben, und taten das Gleiche, also Speicher löschen. Das Hooken ansich ist recht einfach: Ich überschreibe einfach die ersten fünf Byte der insgesamt dann fünf memset() Varianten mit einem relativen Sprung auf meine memset() Implementation, die das selbe tut, aber mit ein wenig Monitoring dazu. Das Hooken war auch selbst im Ziel-Prozess nicht ganz einfach, denn ich kann ja nicht so einfach Code überschreiben der vielleicht gerade ausgeführt wird. Also habe ich im besagt kreierten Thread dann einfach alle Thread außer diesen Thread an. Dann gehe ich jeden der angehalte- nen Threads durch und schaue, ob der sich in den ersten fünf Byte von memset() befindet. Wenn ja, dann lasse ich ihn weiterlaufen mache selbst ein Sleep( 0 ), dass ich einen Timeslice verstreichen lasse, halte den Thread dann wieder an und checke nochmal, ob der sich immer noch oder wieder in diesem Bereich befindet. Wenn ja, dann versuche ich das so lange nochmal bis dann halt nicht mehr. Das eigene memset() hat dann einfach geschaut, ob der zu überschrei- bende Block >= 64kB ist und wenn ja, dann hat er darin ein PDF gesucht. Ich habe diese Grenze gesetzt weil wenn ich das nicht getan hätte, dann würde ich jedes Popels-memset() mit hocher Frequenz mit analysieren, dass ADE von der Performance her nicht mehr benutzbar wäre. Es ist auch so mit integrierter Monitoring-Funktion bei >= 64kB Block- größe so, dass ADE an machen Stellen recht langsam wird, da möchte ich erst gar nicht wissen was passiert, wenn ich diese Grenze weglasse. Und was hat's gebracht ? Zwei Tage mit je 14h Arbeit und gefunden habe ich: NIX. Aber dazugelernt hab ich echt viel. Ich denke so eine smarte Art, beliebigen Code in einen fremden Windows Prozess einzuschleusen hat vor mir noch keiner programmiert. Vielleicht hat da noch jemand anders eine Idee, die ich umsetzen könnte.
[toc] | [prev] | [next] | [standalone]
| From | "Wendelin Uez" <wuez@online.de> |
|---|---|
| Date | 2022-05-27 12:11 +0200 |
| Subject | Re: Und noch einen oben drauf |
| Message-ID | <t6qb9f$7ug$2@dont-email.me> |
| In reply to | #555957 |
Sehr iInteressant, aber vielleicht statt Perlen unter Säue zu werfen lieber gleich in fachlich qualifizierten Foren präsentieren?
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2022-05-27 19:39 +0200 |
| Subject | Re: Und noch einen oben drauf |
| Message-ID | <t6r2ca$geq$2@dont-email.me> |
| In reply to | #555968 |
Am 27.05.2022 um 12:11 schrieb Wendelin Uez: > Sehr iInteressant, aber vielleicht statt Perlen unter Säue zu werfen > lieber gleich in fachlich qualifizierten Foren präsentieren? Ich kenne hier mindestens einen fast stillen Mitleser der das versteht und auch mindestens spanned findet.
[toc] | [prev] | [standalone]
Back to top | Article view | ger.ct
csiph-web