agosto 29, 2006

pydbg: Debugear aplicaciones en win32 con python

Echando un vistazo a los blogs de openrce.org me he encontrado con esta perla: QTFairUse6, se trata de un script python que hace uso del debbuger pydbg para extraer las canciones del iTunes, me ha maravillado el gracil uso de las funciones y como necesitaba probarlo como sea... pues he hecho unas pruebas con un juegecillo que ya estube modificando tiempo atrás :D

Como ya conocía bien donde andan las rutinas de tiradas de dados solo había que volver a revisarlas y localizar los registros:
CODE:00475204 ObtieneNumeroDados:                     ; CODE XREF: sub_475184+C3j
CODE:00475204 mov eax, 6
CODE:00475209 call XXX_Random
CODE:0047520E mov ebx, eax
CODE:00475210 inc ebx
CODE:00475211 mov eax, 6
CODE:00475216 call XXX_Random
CODE:0047521B mov esi, eax
CODE:0047521D inc esi
CODE:0047521E mov eax, 6
CODE:00475223 call XXX_Random
CODE:00475228 inc eax
CODE:00475229 mov [ebp+var_C], eax
CODE:0047522C lea eax, [esi+ebx]
CODE:0047522F cmp eax, 7
CODE:00475232 jnz short loc_475249
CODE:00475234 mov eax, ds:off_4A9C90
CODE:00475239 cmp byte ptr [eax+37h], 0
CODE:0047523D jz short loc_475249
CODE:0047523F mov eax, ds:off_4A9EE8
CODE:00475244 cmp dword ptr [eax], 3
CODE:00475247 jle short ObtieneNumeroDados

Si observamos hay tres llamadas a XXX_Random que son las tres tiradas de dados, la primera para el tipo de turno en el modo de juego de caballeros y las 2 siguientes los dos dados de juego. Los valores tras hacer unas tiradas y revisar los valores de los registros con el ollydbg se almacenan respectivamente en los registros EAX, EBX y ESI. Si lo que queremos es modificar los dados hemos de modificar los registros anteriores en la posición CODE:00475229.

Para ello preparamos un script que hará uso del PyDbg para engancharse al proceso wancatan.exe y colocar un breakpoint en lap posición 0x475229:

def modificaDados(dbg):
global dado1,dado2,dado3,modificar

if modificar:
print 'Modificando dados (%d,%d,%d)' % (dado1,dado2,dado3)

dbg.set_register('EAX', dado1)
dbg.set_register('EBX', dado2)
dbg.set_register('ESI', dado3)

return pydbg.DBG_CONTINUE
...
dbg.attach(pid)
dbg.bp_set(0x475229, handler=modificaDados)
dbg.run()

El funcionamiento como se ve es muy simple y extremadamente eficaz, le he hechado un ratillo y lo he montado sobre las wxPython para modificar a placer mediante una ventanita ;)


El código completo (requiere pydbg, ctypes y wxpython):
#C:/Python24/python.exe
# -*- coding: utf-8 -*-

import pydbg, sys, struct, wx
from threading import Thread

modificar=0
dado1,dado2,dado3=(1,1,1)

#------------------------------------------------------------------------------
# Interfaz gráfico

class selector(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
rango=['1','2','3','4','5','6']

self.cb = wx.CheckBox(self, -1, "Activar trucaje")
self.ch1 = wx.Choice(self, 1, choices = rango)
self.ch2 = wx.Choice(self, 2, choices = rango)
self.ch3 = wx.Choice(self, 3, choices = rango)
self.Bind(wx.EVT_CHOICE, self.EvtChoice, self.ch1)
self.Bind(wx.EVT_CHOICE, self.EvtChoice, self.ch2)
self.Bind(wx.EVT_CHOICE, self.EvtChoice, self.ch3)
self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.cb)

border = wx.BoxSizer(wx.VERTICAL)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.AddMany( [ self.ch1, self.ch2, self.ch3 ] )
border.Add(self.cb, 0, wx.ALL, 15)
border.Add(sizer, 0, wx.LEFT, 50)
self.SetSizer(border)

def EvtChoice(self, event):
global dado1,dado2,dado3
if event.GetId() == 1: dado1=int(event.GetString())
elif event.GetId() == 2: dado2=int(event.GetString())
elif event.GetId() == 3: dado3=int(event.GetString())

def EvtCheckBox(self, event):
global modificar
modificar=event.IsChecked()

class MyFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title, size=(200, 100))
self.selector=selector(self,-1)

class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, "WanCatan Dados")
self.SetTopWindow(frame)
frame.Show(True)
return True

def OnExit(self):
global dbg
dbg.detach()

class Hebra(Thread):
def run(self):
app = MyApp(0)
app.MainLoop()

#------------------------------------------------------------------------------
# Funciones del debbuger

def busca_pid(dbg, nombre):
for pid,proc in dbg.enumerate_processes():
if proc.lower() == nombre.lower(): return pid
return -1

def modificaDados(dbg):
global dado1,dado2,dado3,modificar
if modificar:
print 'Modificando dados (%d,%d,%d)' % (dado1,dado2,dado3)
dbg.set_register('EAX', dado1)
dbg.set_register('EBX', dado2)
dbg.set_register('ESI', dado3)
return pydbg.DBG_CONTINUE

#------------------------------------------------------------------------------
# Principal

if __name__ == '__main__':
global dbg
dbg=pydbg.pydbg()

proceso = "wancatan.exe"
pid = busca_pid(dbg, proceso)

if pid < 0:
print 'Proceso %s no encontrado' % proceso
sys.exit(0)

# Arrancamos el interfaz gráfico
w = Hebra()
w.start()

print "Enganchando %s(%d)" % (proceso, pid)
dbg.attach(pid)
dbg.bp_set(0x475229, handler=modificaDados)
dbg.run()

Tags:

comentarios: