Memory Debugging or a “Universal Game Trainer” with Python and ptrace

Reading and writing other process memory can have many different purposes: besides debugging, this technique is probably most frequently known to be used by game trainers, to alter different values of a running game (like the value of health or money).

Most (if not all) of the game trainers available, are developed for Windows and only for a specific game, but there are some universal trainers, that could be used to locate and change values in memory of any game or application and with kcheat and Tursiops there are some solutions for Linux.
I’ve developed a proof of concept to locate and change any byte value using python and the ptrace bindings. ptrace is a Unix system call for controlling other processes, it is for instance used by the gdb debugger.

The following code creates a debugging instance and attaches the other process by PID:

from ptrace.debugger.debugger import PtraceDebugger
 
dbg = PtraceDebugger()
process = dbg.addProcess(1234, False)

Now the process instance can be used to inspect and manipulate the process. To manipulate the memory it is important to know the virtual memory mappings (‘areas’) of the process:

from ptrace.debugger.memory_mapping import readProcessMappings
 
memory_mapping = readProcessMappings(process)

Now the list contains MemoryMapping instances, describing the virtual memory areas of the process. We are interested in their location and permission (Quickref: mapping.start, mapping.end, mapping.permissions). The trainer should only be searching within memory mappings with “rw” permissions.

Last but not least, the reading and writing from/to memory methods:

value = process.readBytes(0x11223344, 1) # this reads 1 byte
process.writeBytes(0x11223344, value) # writes len(value) bytes

After that you can close the debugger instance by:

dbg.quit()

I encountered some difficulties with closing/detaching the debugging process, it seems that the process is only really detached if the script is terminated, as a workaround I forked the memory search process, to circumvent that in my proof of concept trainer.

That’s it! If you’re interested, you can checkout my proof of concept for a universal game trainer: pycheat.py (GPLv3) It can locate and change byte values (char) in other processes memory:

% python pycheat.py 8169
pycheat: universal game trainer v0.1
process id: 8169
highest address: 0xffffff
 
searching for address by byte value: 23
search memory area[0x00DB8000-0x00DC0000] address[0x00DBD989] value[0x17 (023)]    
 
found 5926 occurrences, change value to: 33
search memory area[0x00DB8000-0x00DC0000] address[0x00DBFD88] value[0x21 (033)]    
 
found 1 occurrence! correct address for this value is 0x00C4335A
change value in memory at 0x00C4335A to: 99
done.

First you enter the known value, the trainer is searching it inside the memory, chances are high, that there are many occurrences of it, so you change the value inside the game and repeat the search with the new one. After ~2-4 iterations the correct location should be found and you can manipulate it.

edit: smaller changes

JavaScript: onMouseMove Google Translation

I wanted to implement a JavaScript onMouseMove Event, that translates the word under the mouse pointer with the Google Translator. I want to share the following solution.

I split this into the following two separate problems:

First: Detect word for an onMouseMove Event

The onMouseMove Event can listen on any Element for mouse movements. To detect the word under the mouse pointers position, I utilize the W3C(DOM-2) specified Range Object.

Most modern browsers support the Range object, but the event.rangeParent Attribute seems to be only supported by Firefox. Currently I’ve no cross-browser solution for this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
 * Apply offset to range and extract a single character at position.
 *
 * range -- the range object effected
 * originalOffsetStart, originalOffsetEnd -- Start offsetStart and offsetEnd
 * offset -- the relative offset to apply
 */
function getRangeCharacter(range, originalOffsetStart, originalOffsetEnd, offset) {
    var character = ' ';
    try {
        range.setStart(range.startContainer, originalOffsetStart + offset);
        range.setEnd(range.startContainer, originalOffsetEnd + offset);
        character = range.toString()[0];
        range.setStart(range.startContainer, originalOffsetStart - offset);
        range.setEnd(range.startContainer, originalOffsetEnd - offset);
    } catch (e) {}    
 
    return character;
}
 
/**
 * Returns a single word for the given event.
 */
function getEventWord(evt) {
    if (!evt.rangeParent || !document.createRange) {
        return '';
    }
 
    var range = document.createRange();
 
    range.setStart(evt.rangeParent, evt.rangeOffset);
    range.setEnd(evt.rangeParent, evt.rangeOffset);
 
    // the word ends when this characters appears
    var stop_character_pattern = /^[\W]$/m;
 
    // this "overwrites" some characters from the above pattern
    var ignore_stop_character_pattern = /^['|\-]$/;
 
    // I assume startOffset == endOffset so set 1 character selection ...
    var originalOffsetStart = range.startOffset;
    var originalOffsetEnd = range.startOffset + 1;
    try {
        // test:
        range.setStart(range.startContainer, originalOffsetStart);
        range.setEnd(range.startContainer, originalOffsetEnd);
    } catch (e) {
        // out of bounds
        originalOffsetStart -= 1;
        originalOffsetEnd -= 1;
        range.setStart(range.startContainer, originalOffsetStart);
        range.setEnd(range.startContainer, originalOffsetEnd);
    }
 
    // First Step: Find left end of word:
    var leftCharacterPos = 0;
    var iChar = '';
    for (var iOffStart = 0; iOffStart <= originalOffsetStart; iOffStart++) {
        iChar = getRangeCharacter(range, originalOffsetStart, originalOffsetEnd, -1 * iOffStart);
 
        leftCharacterPos = iOffStart;
 
        if (stop_character_pattern.test(iChar) && !ignore_stop_character_pattern.test(iChar)) {
            // remove the stop character!
            leftCharacterPos -= 1;
            break;
        }
    } leftCharacterPos = originalOffsetStart - leftCharacterPos;
 
    // Last Step: Find right end of word:
    var rightCharacterPos = 0;
    for (iOffStart = 0; true; iOffStart++) {
        iChar = getRangeCharacter(range, originalOffsetStart, originalOffsetEnd, iOffStart);
 
        if (stop_character_pattern.test(iChar) && !ignore_stop_character_pattern.test(iChar)) {
            rightCharacterPos = iOffStart - 1;
            break;
        }
    } rightCharacterPos = originalOffsetEnd + rightCharacterPos;
 
    try {
        range.setStart(range.startContainer, leftCharacterPos);
        range.setEnd(range.startContainer, rightCharacterPos);
    } catch (e1) {
        range.detach();
        return '';
    }
 
    var retWord = range.toString();
 
    range.detach();
 
    return retWord;
}

Second: Translate Text with Google Translator

This is very easy because Google provides an very easy to use Ajax API for this (attend the possible google user tracking!):

1
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// read onMoveTranslate()
var last_word = null;
var timeout = null;
var word_cache = [];
 
/**
 * Translate text For onmousemove events.
 */
function onMoveTranslate(event) {
    // get element the translation should appear in:
    var translation = document.getElementById('translation');
 
    var word = getEventWord(event);
    if (!word || word == '') {
        return;
    }
 
    if (typeof(word_cache[word]) !== 'undefined') {
        translation.innerHTML = word_cache[word]; //+" (cached)";
        return;
    }
 
    if (word != last_word) {
        if (timeout) {
            window.clearTimeout(timeout);
        }
 
        translation.innerHTML = 'Translating ... ' + word;
        timeout = window.setTimeout(function(){
            google.language.translate(word, 'en', 'de', function(result) {
                if (!result.error) {
                    translation.innerHTML = result.translation;
                    word_cache[word] = result.translation;
                }
            });
        }, 800);
    }
    last_word = word;
}
 
google.load("language", "1");

At last I assign the onMouseMove Event to the onMoveTranslate() function:

1
<div id="content" onmousemove="onMoveTranslate(event);">

Here is an full Example for this.

implementing bittorrent – DHT (teil 3)

Eigentlich wollte ich ja was über den BitTorrent Message-Flow schreiben, aber aus aktuellem Anlass gibt es heute einen “kleinen” Artikel über DHT.

DHT

Aus gegebenem Anlass schreibe ich nun etwas früher als geplant über DHT. Die ThePirateBay-Tracker sind nun schon etwas länger Down, es sind zwar etliche Ersatz-Tracker aus dem Boden gesprossen, aber meiner Meinung nach ist das dezentrale Tracking nun an der Reihe. Die Tracker waren bis jetzt einer der grössten Angriffs/Schwachpunkte der BitTorrent Technologie/ des BitTorrent Protokolls.
Doch das muss nicht so sein, denn es gibt ja DHT (BEP005). Ich will mich hier mal etwas näher mit DHT beschäftigen und das DHT Protokoll näher erklären.

Die Technologie hinter DHT im BitTorrent-Protokoll basiert auf Kademlia. Kademlia wurde von Petar Maymounkov und David Mazières entwickelt und “implementiert” bzw spezifiziert Distributed Hashtables in P2P-Netzen. Verschaffen wir uns erstmal einen kleinen Überblick über Kademlia.

BEP005 spezifiziert das die Daten per UDP übertragen werden. Zuerst generiert der Client eine Node ID, diese wird genauso wie der BitTorrent Infohash errechnet, bzw nutzt den selben Bit-raum (160Bit). Danach beginnt das Bootstrapping, der Client muss sich ja erstmal im DHT zurechtfinden und einen anderen Client finden dieser Teilt dann weitere IPs mit. Meist ist die erste Node “router.bittorrent.com” da ein Scannen durch die eigene IP-Range oder willkürliches suchen nach anderen Nodes nicht so erfolgversprechend ist.
Zur Indizierung von BitTorrent-Hashes wie es ein Tracker macht trägt bei DHT jeder Peer/Node bei.
Wenn wir nun einen bestimmten Hash suchen kann die Entfernung zu diesem Hash ausgerechnet werden. Dafür werden entweder Node IDs und Node IDs verglichen, oder Infohashes und Node IDs, durch den “Algorithmus” lässt sich die “nähe” der Node ID zur gesuchten ID (Infohash oder Node ID) errechnen. Die Routing Tabelle die der Client nun erstellt wird immer detaillierter je näher wir an den gesuchten ID-Wert kommen und die Node mit der gewünschten Information gefunden haben. Jede Node hat “Kontaktdaten” zu den Nodes mit “nahen” IDs.

Die Distanz zwischen 2 IDs wird per XOR berechnet:

distanz(A,B) = |A xor B|

Je kleiner das Ergebnis desto näher Liegen sich A und B.

Wenn nun eine Node (im DHT) Peers (für BitTorrent) finden will passiert folgendes:
Ich gehe davon aus das das Bootstrapping schon fertig ist, wir also ein Paar Nodes schon in unserer Routing-Tabelle haben. Unser Client nimmt nun den Infohash aus der Torrent-File. Wir nehmen Beispielsweise den Hash
362b0a1151013916896a9c98b52e0ead803c4ac3 (ubuntu-7.10-alternate-hppa.iso). Unser Client wird die Node mit dem geringstem Abstand zum Infohash nun Kontaktieren und diese Node nach Peer-Kontaktdaten Fragen. Wenn diese Node Peer-Kontaktdaten besitzt werden diese zu unserem Client übermittelt und dieser baut dann über das BitTorrent-Protokoll eine Verbindung zum Peer auf. Wenn die Node keine Peer-Kontaktdaten hat wird sie uns Kontaktdaten zu den Nodes aus ihrer Routing-Tabelle geben die am Nächsten an unserer ID liegen, und so weiter und so fort! :D

Die Antwort der Nodes auf die Anfrage nach Peers zu einem Infohash beinhaltet immer einen “token”. Wenn wir nun dem Node von dem wir die Peer-Kontaktdaten bekommen haben mitteilen wollen (wie beim Tracker ein sogenannter “announce”), müssen wir dem Node neben dem Announce auch den Token schicken den wir zuvor
von ihm erhalten haben.
Es liegt auf der Hand das dies eine Sicherheitsmaßnahme ist, damit nicht irgendwelche unbekannten Hosts (Bots von der MI) den DHT verseuchen. Die BEP005 spezifiziert das die Tokens alle 5 Minuten neu generiert werden, sie bestehen aus einem SHA1-Hashes auf die IP Adresse verknüpft mit einem Salt (welches alle 5 Minuten wechselt).

Routing
Kommen wir nun zum Aufbau der Routing Tabelle unseres Nodes. Die Routing Tabelle ist beinhaltet alle Bekannten Nodes die noch errechbar sind (das wird in einem Zeitraum von 15 Minuten getan) und diese Nodes dienen als Startpunkte für neue anfragen im DHT.

Die Nodes in unserer Routing Tabelle werden je nach ihrer Erreichbarkeit eingestuft. Die Erreichbarkeit jeder Node wird alle 15 Minuten überprüft. Eine Node wird als “good” eingestuft wenn sie uns eine Anfrage geschickt oder beantwortet hat innerhalb der letzten 15 Minuten. Wenn eine Node innerhalb von 15 Minuten nicht aktiv war wird sie  “questionable” also Fragwürdig eingestuft. Ein Node wird als “bad” eingestuft wenn er auf mehrere Anfragen nicht geantwortet hat. Unsere Routing-Tabelle sollte nur “good”-Einträge enthalten.

Die Routing Tabelle deckt den kompletten Raum an möglichen IDs ab (von 0 bis zu 2^160). Die Routing-Tabelle ist in “Buckets” (Eimer) unterteilt. Eine leere Routing-Tabelle enthält einen Bucket mit dem ID-Raum von 0 (min) bis 2^169 (max). Wenn wir nun eine Node mit der hypothetischen ID “A” (normal ist die ID natürlich 20byte lang wie beim Infohash) dann wird diese in einen Bucket eingefügt der min < A < max ist. Also in einem Bereich bei dem
min kleiner als A ist und A kleiner als max :D
In einer leeren Routing-Tabelle ist nur ein Bucket also passen in diesen Bucket alle möglichen IDs rein. Allerdings kann ein Bucket nur K verschiedene Einträge enthalten, im Moment ist K=8. Wenn der Bucket voll mit “good”-Nodes ist, werden keine Nodes mehr hinzugefügt – AUßER die eigene Node ID fällt in den Bereich, in diesem Fall wird der Bucket durch zwei neue Buckets ersetzt und die Nodes aus dem Vorherigem Bucket werden auf die zwei neuen Buckets aufgeteilt. Diese zwei Buckets könnten bei einer Routing-Tabelle mit nur einem Bucket dann jeweils die Bereiche vor und nach unserer ID abdecken (min/max werte).

Jeder Bucket sollte (das ist natürlich jedem selbst überlassen, wenn man es denn Implementiert) eine “last changed” Eigenschaft haben um die “frische” des Buckets ablesen zu können. Auch hier sollte alle 15 Minuten die Erreichbarkeit der enthaltenen Nodes überprüft werden. Laut BEP005 wird dies folgendermaßen gemacht:
Eine zufällig ausgewählte ID aus dem Bereich des Buckets (min/max) wird mit einer “find_nodes” (dazu weiter unten im Protokoll-Bereich mehr) Anfrage kontaktiert – bei Antwort ist die Node noch zu gebrauchen, wenn
nicht sollte die Node aus dem Bucket entfernt werden. Wenn eine Node Anfragen von anderen Nodes nicht beantworten kann, z.B. wegen schlechten Router-Settings, dann sollte Sie ihre Buckets öfter Auffrischen als eine Node die auf Anfragen antworten kann, den eine solche Node erhält ja eh immer wieder neue Node IDs.
Die Node sollte zudem versuchen ihr selbst nahe liegende Nodes zu finden.

BitTorrent Protokoll

Wenn unser BitTorrent-Client DHT unterstützt, wird bei den Protokoll-Flags im Handshake das letzte Byte gesetzt. Wenn nun ein anderer Peer uns beim Handshake das er DHT Supported, senden wir ihm mit der PORT-Message des BitTorrent-Protokolls unseren UDP-Port auf dem unsere DHT-Implementierung lauscht. Die DHT-Implementierung Pingt dann die neue Node und wenn eine Antwort kommt wird diese nach den oben definierten Regeln in einen Bucket eingefügt oder auch nicht (bei keiner Antwort).

Torrent File

Die Torrent-File enthält keine “announce”-URL mehr, sondern einen “nodes”-Key. Dieses Nodes Dictionary enthält die K (K=8) nähsten bekannten Nodes von dem Client der die .torrent-File erstellt hat. Oder halt eine Liste mit K bekannten “good”-Nodes. Das Nodes-Dictionary ist wie folgt aufgebaut:

nodes = [["",], ["",], ...]
nodes = [["127.0.0.1", 6881], ["your.router.node", 4804]]

Protokolle

Kademlia RPC Protololl

KRPC ist relativ schnelle und einfach erklärt. Die Einzelnen Messages sind wie bei BitTorrent üblich BEncoded. Auf einen Request kommt eine Antwort/Response. Es gibt drei Message-Typen: query, reponse und error.
Beim DHT-Protokoll sind es vier: ping, find_node, get_peers und announce_peer. Eine KPRC-Message besteht aus einem Dictionary mit zwei Keys, sowie zusätzlich je nach Message-Typ zusätzliche Felder.
Der Key “t” beinhaltet die “transaction id”, die “tansaction id” wird beim Query errechnet und dient dazu Anfragen und Antworten zuzuordnen. Sie besteht meistens nur aus 2 Buchstaben, da diese vollkommen ausreichen um eine große Anzahl von Anfragen abzudecken.
Der andere Key ist “y” und beinhaltet als Wert nur einen einzigen Buchstaben:

  • q: query
  • r: response
  • e: error

Um die Kontaktdaten von Peers zu übertragen werden diese Kodiert. Hier kommt die selbe Kodierung wie beim Compact-Encoding bei BitTorrent zum Einsatz, ein 6-Byte String – wobei die ersten 4 Byte die IP repräsentieren und die letzten 2 den Port.
Um die Kontaktdaten von anderen Nodes zu übertragen werden diese ebenfalls Kodiert, dies geschieht jedoch mit dem “Compact Node Encoding”. Hier ist es ein 26-Byte String. Die ersten 20byte enthalten die Node ID die restlichen 6 sind die Compact-Encodierte IP und der Port wie oben.

Queries/Anfragen
Der “y” Key enthält als Wert “q” wie weiter oben schon erklärt. Zusätzlich noch die die zwei Keys “a” und “q”.

  • q: enthält den Methodennamen des Queries (DHT Protokoll z.B. Ping)
  • a: enthält die Argumente

Responses/Antworten
Der “y” Key enthält hier den Wert “r” für Response.
Zusätzlich enthält diese Message den Key “r”:

  • r: ist ein Dictionary mit den Ergebnissen der Anfrage

Errors/Fehler
Der “y” Key enthält den Key “e” für Error.

  • e: enthält eine Liste:
    • das erste Element der Liste enthält den Fehler Code
    • das zweite Element die Fehlermeldung

Mögliche Fehler und ihre Entsprechenden Messages:

Code Beschreibung
201 Generic Error
202 Server Error
203 Protocol Error, such as a malformed packet, invalid arguments, or bad token
204 Method Unknown

Beispielhaftes Error Packet aus BEP005:

generic error = {"t":"aa", "y":"e", "e":[201, "A Generic Error Ocurred"]}
bencoded = d1:eli201e23:A Generic Error Ocurrede1:t2:aa1:y1:ee

DHT Queries

Bei den DHT Anfragen enthält jeder Query einen “id”-Dictionary als Argument, neben etwaigen anderen Argumenten, welcher die Node ID der anfragenden Node beinhaltet (20-Byte String in Network Byte
Order) die Antworten enthalten natürlich die Node ID der antwortenden Node.

Hier die Möglichen Queries:

ping
Der Key “q” für den Query-Typ beinhaltet hier den Wert “ping”. Der Key “a” für die Argumente enthält einen Dictionary mit dem Key “id” und als Wert wie oben schon erklärt die Node ID in Network Byte Order.

Example Packets aus BEP005:

ping Query = {"t":"aa", "y":"q", "q":"ping", "a":{"id":"abcdefghij0123456789"}}
bencoded = d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe
 
Response = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}
bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re

find_node:
Dieser Query wird benutzt um die Kontaktinformationen zu einer bestimmten Node anhand ihrer ID herauszufinden. Der “q”-Key enthält hier logischerweise “find_node”.
Die Argumente im “a”-Key enthalten wie immer die eigene Node ID und hier noch das Target-Dictionary welches folgende Keys enthält:

  • “target”:  ID der gesuchten Node

Als Antwort auf einen “find_node”-Query sollte die Antwort einen Key namens “nodes” enthalten der die Compact-Encoded-Node Informationen der gesuchten Node oder 8 (K) der nähsten bekannten “good”-Nodes enthalten.

Beispiel aus BEP005:

find_node Query ={"t":"aa", "y":"q", "q":"find_node","a":{"id":"abcdefghij0123456789","target":"mnopqrstuvwxyz123456"}}
bencoded = d1:ad2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe
 
Response = {"t":"aa", "y":"r", "r": {"id":"0123456789abcdefghij", "nodes": "def456..."}}
bencoded = d1:rd2:id20:0123456789abcdefghij5:nodes9:def456...e1:t2:aa1:y1:re

get_peers:
Mit diesem Query erhält man Peers für den Torrent Download. “q” entspricht hier “get_peers”. Die Argumente sind “id” mit der Node ID und “info_hash” mit dem Infohash des Torrents. Die Antwort enthält den Key “values” der eine Liste von Strings enthält die entweder Compact-Encoded IP der Peers enthält, oder wenn keine Peers bekannt sind: einen Key “nodes” der eine Liste mit 8 (K) Nodes im Compact-Encoded Format enthält. In jedem Fall wird bei der Antwort ein Key “token” übertragen (der Token wird bei einem “announce” gebraucht).

Beispiele aus BEP005:

get_peers Query = {"t":"aa", "y":"q", "q":"get_peers", "a": {"id":"abcdefghij0123456789", "info_hash":"mnopqrstuvwxyz123456"}}
bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe
 
Response with peers = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "values": ["axje.u", "idhtnm"]}}
bencoded = d1:rd2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee1:t2:aa1:y1:re
 
Response with closest nodes = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "nodes": "def456..."}}
bencoded = d1:rd2:id20:abcdefghij01234567895:nodes9:def456...5:token8:aoeusnthe1:t2:aa1:y1:re

announce_peer:
Der “q” Key enthält logischerweise den String “announce_peer”. Dazu kommen noch 4 Argumente in “a”, die da wären:

  • id: Node ID
  • info_hash: Infohash des Torrents
  • port: BitTorrent Client Port (zb 6881)
  • token: der token der beim vorherigem query nach peers vomentsprechendem Node gesendet wurde (dem Node welches usn die Peers geschickt hat)

Beispiel aus BEP005:

announce_peers Query = {"t":"aa", "y":"q", "q":"announce_peer", "a": {"id":"abcdefghij0123456789", "info_hash":"mnopqrstuvwxyz123456", "port": 6881, "token": "aoeusnth"}}
bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe1:q13:announce_peer1:t2:aa1:y1:qe
 
Response = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}
bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re

Ich hoffe das war ein Verständlicher “kleiner” Überblick uber DHT im BitTorrent-Protokoll. Und im naechstem teil gehts dann ueber den Message-Flow im BitTorrent-Protokoll xxD Bevor wir dann auch zu anfassbarem Code kommen, bzw einem Release :D

Links:

Trotzallem noch tolle Ersatz-Indexer :D

  • torrentbox Hier finde ich vor allem das Global-Search Feature ganz nettm da dort auch Torrentbox-Verified Torrents angezeigt werden
  • 1337x Noch etwas kleines aber sympathisches Projekt
  • h33t Die Oberfläche ist gewöhnungsbedürftig, ansonsten auch sehr nice xD

Web scraping mit Ruby/Mechanize

Praktisch jede Interaktion mit einer Website oder Webapplikation kann gescriptet, d.h. automatisiert werden. Das Abgrasen von Webseiten nach bestimmten Informationen wird auch als Scraping bezeichnet(für die nicht menschlichen Besucher dieser Seite sei das erwähnt *g*) Scripte können einem eine ganze Menge Arbeit abnehmen und sogar Dinge tun, die manuell unmöglich wären. Ich beschäftige mich mit dem Thema schon seit einer ganzen Weile und möchte hier nun die von mir favorisierte Methode dafür vorstellen, die Bibliothek Mechanize für die Scriptsprache Ruby.

Mechanize hat seinen Ursprung in Perl, mittlerweile gibt es jedoch auch Implementierungen der API für Python und eben Ruby. Für PHP gibt es mit Snoopy ein ähnliches Projekt, wenn es auch bei weitem nicht so fortgeschritten ist. Mechanize bietet die Möglichkeit mit einfachen Methoden eine art Webbrowser zu simulieren. Alle Beispiele wurden mit Mechanize Version 0.9.2 und Ruby 1.8.7 getestet.

Installation / Initialisierung

Mechanize kann mit Gems(ähnlich CPAN oder PEAR) installiert werden(# gem install mechanize --remote), einige Distributionen bieten aber auch eigene Pakete an. Ein Ruby-Script kann daraufhin Mechanize inkludieren und ein Objekt erstellen:

require 'rubygems' # ist unter Umständen notwendig
require 'mechanize'
agent = Mechanize.new

Jetzt ist Mechanize einsatzbereit, die folgenden Beispiele bauen darauf auf. Außerdem können mit dem agent nun noch grundlegende Einstellungen vorgenommen werden:

agent.set_proxy('localhost', '8000')
agent.user_agent = 'Individueller User-Agent'
agent.user_agent_alias = 'Linux Mozilla'

Die Einstellung ‘user_agent_alias‘ wählt einen User-Agent String aus dem folgenden Satz von Beispielen aus: Windows IE 6, Windows IE 7, Windows Mozilla, Mac Safari, Mac FireFox, Mac Mozilla, Linux Mozilla, Linux Konqueror, iPhone und Mechanize. Die Timing Einstellungen können ebenfalls sehr wichtig sein:

agent.open_timeout = 3 # setzt timeouts
agent.read_timeout = 4
agent.keep_alive = false # default ist true

Hier folgen nun einige Beispiele, vielleicht werde ich mit der Zeit auch noch ein paar ergänzen, falls jemand Vorschläge hat, immer her damit. Ich habe auf http://apoc.sixserv.org/requestinfo ein kleines Skript am laufen das nützliche Informationen zum HTTP-Request liefert, das kann zum Experimentieren mit Mechanize sehr nützlich sein. Einige Beispiele findet man auch in den GUIDE und EXAMPLES Dateien des Mechanize Pakets. Continue reading

implementing bittorrent (teil 2)

Was macht ein BitTorrent-Tracker? In diesem Teil gehe ich auf die Funktion eines BitTorrent-Trackers ein. Zuerst sollte man sich den Grundsätzlichen Ablauf eines Torrent Downloads anschauen.

1. Download der .torrent-Datei

Der User sucht sich aus seiner Lieblings-Torrent-Quelle (zb TPB, Demonoid, dnbtracker.org ) einen den gewünschten Torrent heraus und lädt ihn auf Seine Festplatte oder öffnet ihn gleich mit seinem BitTorrent-Client.

2. Der Client parst die .torrent-Datei

Für die Kommunikation mit dem Tracker sind nur ein Paar Informationen aus der Metafile interessant (Metafile Infos im ersten Teil)

  • der info hash
  • die announce urls

3. Der Client kontaktiert den Tracker und sendet ihm den SHA Hash

Die Tracker-Kommunikation läuft über HTTP.
Der Client Schickt einen GET-Request an den Tracker.
Dieser Request besteht aus:

  • announce url
  • info hash – der SHA1 Hash des Info Dictionaries
  • peer id
  • port
  • uploaded
  • downloaded
  • left
  • compact
  • event

Optional aber auch interessant ist:

  • key (damit der Tracker einen Client auch nach einem reconnect mit neuer IP wiedererkennt)

Ein Request an einen Tracker könnte dann wie folgt aussehen ( der Tracker laeuft auf example.com):

  • http://example.com/announceUrl+”?info_hash=”+urlencode(info)+”&peer_id=-ST-0001%29%7B%D6%40%F4%21%7B%5C%DA%05%DE&port=6881&uploaded=0&downloaded=0&left=0&compact=1&event=started”

Der Tracker erfährt damit von uns folgende Informationen:

  • den Hash von der Info-Map (info_hash)
  • nsere Peer-ID (peer_id) [zur ID bald mehr]
  • den Port auf dem unser Client lauscht (port)
  • wir haben noch 0 Pieces hochgeladen (uploaded=0)
  • wir haben 0 Pieces heruntergeladen (downloaded=0)
  • wir möchten eine Peer-Liste im Kompakten Format (compact=1), das ist auch üblich so. Das alte Format wird praktisch kaum noch genutzt.
  • mit dem Event (event=started) teilen wir dem Tracker mit das wir gerade erst mit dem Download beginnen.

4. Tracker Antwort
Der Tracker antwortet mit einer Liste von Peers. Aus dieser Liste kann der Client entnehmen welche Peers die File besitzen. Ob Sie die ganze File besitzen oder nur Fragmente (aus dieser Information wird die obligatorische anzeige der Seeder [complete] bzw Peers [incomplete] realisiert).
Zu den Einzelnen Peers sind die Informationen die dieses bei ihrem Request an den Tracker übertragen haben enthalten, also:

  • peer_id
  • port
  • ip

Zusätzlich teilt einem der Tracker den Intervall mit in dem man diese Informationen auffrischen sollte, bzw einen erneuten Request an den Tracker schicken sollte. Clients sollten sich daran halten um ein hämmern bzw Sinnlosen Overhead zu unterbinden.

Das war es für diesen Teil im großen und ganzen. Ein kleiner überblick auf die Tracker Kommunikation.
Ganz Informativ zu diesem Thema ist auch der Vortrag “Tracker fahrn” (@24C3 – download via http://chaosradio.ccc.de/24c3_m4v_2355.html) von Erdgeist, Denis und Cristian Yxen.

Im nächsten Teil wird es um die Kommunikation unter den Peers gehen.

vBrowseFile 0.0.08-rc2 first file release

Habe gestern das erste testing Release von vBrowseFile veröffentlicht.

Installation(auszug aus der README-Datei):

- install HTTP_Download with pear
    For Debian you could do this with:
        # aptitude install php-pear
        # pear install --alldeps HTTP_Download

- Make sure .htaccess Files are used
    Change VHost setting: "AllowOverride All"

- Download vBroweFile Files to an webroot/vhost-root:
    Download an packaged vbf version.
    - or -
    Checkout the latest SVN version: (will create vbrowsefile/ directory)
    $ svn co svn://mount.at/geekosphere/vbrowsefile/trunk vbrowsefile/

- Make sure the following Directories are writeable by Webserver:
    /flatfiles
    /flatfiles/cache
    /flatfiles/cache/tracks
    /flatfiles/userquery
    /smarty/templates_c

- Go with your browser to the setup.php script:
    e.g. http://example.com/vbrowsefile/setup.php

- Now you could login, change configuration, add some virtual directories 
  and create some normal users