Let’s create our own Host!
Kita akan membuat host baru yang khusus membaca versi dari komponen. Host menggunakan Python 2.7 dengan module ctypes dan harus berjalan di sistem operasi Windows. Cara yang digunakan adalah ABI bukan API karena kita tidak menggunakan C/C++.
Sebagai bahan eksperimen komponen yang digunakan: foo_rgscan.dll.
import ctypes
import os
foobar_path = "C:\\Program Files (x86)\\foobar2000"
os.environ['PATH'] = foobar_path + ';' + os.environ['PATH']
dllname = foobar + "\\components\\foo_rgscan.dll"
component = ctypes.cdll.LoadLibrary(dllname)
Sebelum kita me-load komponen, kita harus daftarkan path dari host asli-nya ke dalam PATH environment variable. Tujuannya adalah agar module dependency dari komponen bisa ditemukan oleh loader.
Untuk LoadLibrary kita menggunakan CDLL, karena function foobar2000_get_interface yang akan diakses menggunakan cdecl calling convention.
What is Calling Convention ?
Ketika kita memanggil fungsi, secara low-level nilai dari argument/parameter dan return-nya disimpan di suatu tempat. Calling convention inilah yang mengatur tempatnya. Sehingga ada kesepahaman antara pemanggil fungsi caller dan fungsi yang dipanggil callee. Ada tiga konvensi yang sering ditemukan yaitu: cdecl, stdcall, dan thiscall. Sayangnya ctypes tidak mendukung thiscall, namun untuk saat ini bukan masalah. Detailnya: X86_calling_conventions
How Python access C/C++ Classes ?
Sebelum kita dapat menggunakan fitur yang ada pada komponen, kita harus memetakan (mapping) class dari C/C++ ke Python. Caranya menggunakan ctypes.Struct.
Pada executable (exe) atau shared library (dll) file, terdapat minimal dua section yaitu code dan data. Semua function dan method akan disimpan pada section code. Untuk static variable dan const akan disimpan di section data. Ada dua tempat lagi yaitu heap untuk obyek yang dicreate menggunakan new dan stack untuk local variable dan parameter.
Ketika dll ini di-load maka semua section akan masuk ke dalam memory sehingga siap untuk dipakai. Agar dapat menggunakan function ataupun variable yang ada pada dll, kita harus mengetahui alamatnya (address). Export function adalah function yang hanya dapat kita akses karena alamatnya diketahui .
Ketika kita mendapatkan pointer yang di-return dari suatu function makan pointer tersebut mengacu kepada suatu obyek yang ada pada memory. Obyek pada memory tersebut memiliki struktur sesuai class-nya. Struktur dapat diketahui melalui SDK.
Pada C/C++ struktur obyek dari suatu class/struct hanya berisi member variable saja (yang non static) dan virtual function. Sedangkan method tidak disimpan pada obyek tersebut sehingga kita tidak dapat memanggil method dari class tersebut karena tidak diketahui alamatnya. Tetapi virtual function dapat kita panggil (call) karena diketahui alamatnya.
Untuk aturan pembuatan strukturnya adalah sebagai berikut:
- Struktur class dimulai dengan sebuah pointer ke table virtual function jika ada.
- Diikuti dengan member variable yang non-static sesuai urutan-nya.
- Jika class punya parent, maka susunan dimulai dari parent ter atas (virtual function pointer – member-member, virtual function pointer – member-member) dengan pola yang sama. Jika multiple parent struktur sesuai urutan.
- Table virtual function berisi alamat ke virtual function sesuai urutan, dan hanya yang di-definisikan pada class tersebut.
- Jika virtual function di-override maka yang digunakan adalah alamat override-nya.
- Aksesibilitas tidak mempengaruhi urutan (public, private, protected). Urutan yang dimaksud adalah urutan pengetikan pada source code.
Detailnya: Reversing Microsoft Visual C++ Part II: Classes, Methods and RTTI
Let’s break out the structure
Kita mulai dari foobar2000_client_impl yang merupakan interface terhadap komponen.
| Offset |
Member |
Virtual |
Offset |
|
foobar2000_client |
|
|
| 0 |
foobar2000_client_vfptr |
get_version |
0 |
|
|
get_service_list |
+4 |
|
|
… |
|
| +4 |
pservice_factory_base |
|
|
|
foobar2000_component_globals |
|
|
|
foobar2000_client_impl |
|
|
Kemudian foobar2000_client_impl::get_service_list akan me-return (head) service_factory_base atau lebih tepatnya service_factory_base_t. Karena service_factory_base_t::g_instance bukan pointer maka harus dijabarkan sesuai class-nya service_impl_single_t yang merupakan turunan dari T (dalam hal ini T yang dipakai turunan dari service_base).
| Offset |
Member |
Virtual |
Offset |
|
service_factory_base |
|
|
| 0 |
service_factory_base_vfptr |
instance_create |
0 |
| +4 |
*__internal__next |
|
|
| +8 |
&m_guid |
|
|
|
service_factory_base_t |
|
|
|
service_factory_single_t |
|
|
| +12 |
g_instance |
|
|
| +12 |
service_base_vfptr |
service_release |
0 |
|
|
service_add_ref |
+4 |
|
|
service_query |
+8 |
|
T_service_virtual |
… |
+12 |
| +16 |
T_service_member |
|
|
| +16 |
… |
|
|
Jika T adalah componentversion_myimpl maka g_instance adalah:
| Offset |
Member |
Virtual |
Offset |
| +12 |
g_instance |
|
|
| +12 |
service_base_vfptr |
service_release |
0 |
|
|
service_add_ref |
+4 |
|
|
service_query |
+8 |
|
componentversion_vf |
get_file_name |
+12 |
|
|
get_component_name |
+16 |
|
|
get_component_version |
+20 |
|
|
get_about_message |
+24 |
| +16 |
componentversion_member |
|
|
| +16 |
componentversion_myimpl_vf |
|
|
| +16 |
componentversion_myimpl_member |
|
|
Karena service_base dan componentversion dideklarasikan menggunakan __declspec(novtable) maka virtual function table hanya dibuat tergantung child class yang menggunakannya.
Struktur data yang diperlukan untuk get_component_version adalah pfc::string_base yang menjadi parameter. Alamat function (constructor) tidak diketahui, sehingga kita harus membuat obyek pfc::string_base versi kita. Tidak semua implementasi pfc::string_base harus dibuat tetapi disesuaikan dengan kebutuhan. Jika dilihat pada:
void get_component_version(pfc::string_base & out) {
out = VERSION;
}
Maka kita perlu melihat cara kerja operator= pada class pfc::string_base.
inline const string_base & operator= (const char * src) {
set_string_(src);
return *this;
}
void set_string_(const char * str) {
set_string(str, _strParamLen(str));
}
virtual void set_string(const char * p_string,t_size p_length = ~0) {
...
}
Ternyata kita perlu mendefinisikan virtual method set_string. Namun karena ctypes tidak mendukung thiscall sehingga nilai string yang didapat terpaksa disimpan ke global variable di Python bukan pada member obyek pfc::string_base.
Struktur data terakhir adalah GUID yang sebenarnya adalah 16-bytes, cukup sederhana.
There are Python structures
class GUID(ctypes.Structure):
_fields_ = [("Data1", ctypes.c_uint32),
("Data2", ctypes.c_uint16),
("Data3", ctypes.c_uint16),
("Data4", ctypes.c_uint8 * 8)]
class service_factory(ctypes.Structure):
pass
service_factory \
._fields_ = [("vfptr_base", ctypes.c_void_p),
("next", ctypes.POINTER(service_factory)),
("guid", ctypes.POINTER(GUID)),
("vfptr_service", ctypes.c_void_p)]
class foobar_client_vfptr(ctypes.Structure):
_fields_ = [("get_version", ctypes.CFUNCTYPE(ctypes.c_int)),
("get_service_list", ctypes.CFUNCTYPE(ctypes.POINTER(service_factory)))]
class foobar_client(ctypes.Structure):
_fields_ = [("vfptr", ctypes.POINTER(foobar_client_vfptr))]
g_strings = []
pfc_set_string_t = ctypes.WINFUNCTYPE(None, ctypes.c_char_p, ctypes.c_uint)
def pfc_set_string(p_string, p_length):
g_strings.append( p_string[:p_length] )
class pfc_string_vfptr(ctypes.Structure):
_fields_ = [("add_string", ctypes.c_void_p),
("get_ptr", ctypes.c_void_p),
("set_string", pfc_set_string_t)]
class pfc_string(ctypes.Structure):
_fields_ = [("vfptr", ctypes.POINTER(pfc_string_vfptr))]
def __init__(self):
self._vfptr = pfc_string_vfptr()
self.vfptr = ctypes.pointer(self._vfptr)
self.vfptr.contents.set_string = pfc_set_string_t(pfc_set_string)
class componentversion_vfptr(ctypes.Structure):
_fields_ = [("service_release", ctypes.CFUNCTYPE(ctypes.c_int)),
("service_add_ref", ctypes.CFUNCTYPE(ctypes.c_int)),
("service_query", ctypes.CFUNCTYPE(ctypes.c_bool)),
("get_file_name", ctypes.WINFUNCTYPE(None, ctypes.POINTER(pfc_string))),
("get_component_name", ctypes.WINFUNCTYPE(None, ctypes.POINTER(pfc_string))),
("get_component_version", ctypes.WINFUNCTYPE(None, ctypes.POINTER(pfc_string))),
("get_about_message", ctypes.WINFUNCTYPE(None, ctypes.POINTER(pfc_string)))
]
Let’s Rock !
Ada dua parameter yang dibutuhkan oleh foobar2000_get_interface yaitu foobar2000_api dan HINSTANCE. Umumnya HINSTANCE adalah handle dari host yang didapat dari kernel32.GetModuleHandleA.
Sedangkan foobar2000_api implementasinya tidak akan ditemukan di SDK. Sehingga kita beri None. Semua function yang kita gunakan juga tidak ada yang menggunakannya (lihat SDK).
h_inst = ctypes.windll.kernel32.GetModuleHandleA(None)
component.foobar2000_get_interface.restype = ctypes.POINTER(foobar_client)
g_client = component.foobar2000_get_interface(None, h_inst)
print 'client-version', g_client.contents.vfptr.contents.get_version()
def hexbuffer(buf):
return ''.join('{:02x}'.format(ord(x)) for x in buf)
componentversion_guid = 'bd3ebb10f7dd7549a3cc23084829453e'
s_now = g_client.contents.vfptr.contents.get_service_list()
while s_now:
guid = s_now.contents.guid.contents
if componentversion_guid == hexbuffer(buffer(guid)):
pfc = pfc_string()
cv = ctypes.cast(s_now.contents.vfptr_service, ctypes.POINTER(componentversion_vfptr))
cv.contents.get_component_name(ctypes.byref(pfc))
cv.contents.get_component_version(ctypes.byref(pfc))
cv.contents.get_about_message(ctypes.byref(pfc))
print g_strings
break
s_now = s_now.contents.next