Problem z czytaniem pamięci (std::string)

0

Witam. Mam problem... Otóż po zakończeniu programu dostaje:

First-chance exception at 0x0FA0CCC8 (msvcp110d.dll) in XXX.exe: 0xC0000005: Access violation reading location 0x65746E49.
Unhandled exception at 0x0FA0CCC8 (msvcp110d.dll) in XXX.exe: 0xC0000005: Access violation reading location 0x65746E49.

Kod main.cpp: http://pastebin.com/X2NSwYDT

Otóż gdy zmienię na "char" (jednobajtowy), to dostaję pierwszą litere.. ale co robię źle z std::string?

//EDIT:
Dobra, udało mi się odczytać wartość przy użyciu "char".. ale wolałbym robić wszystkie przy użyciu std::String... Jak na razie jestem w kropce.. :/

//EDIT2:
Albo, mógłby ktoś mi pomóc w odczytywaniu danych z adresów?

PS: Naprawcie link do rejestracji (aktualnie: http://4programmers.net/Rejetracja i otrzymuję 404) z linku: http://4programmers.net/Forum/C_i_C++?mode=submit

0
  1. robisz new, nie robisz delete
  2. char to jeden znak (jak sama nazwa wskazuje z "angielska")
  3. pokaż plik Memory.h (ponieważ nie wiem czemu na stałe wpisujesz ten adres 0x00ACD6B0)
  4. poczytaj o operatorach to dostaniesz adres danej zmiennej
1

Nie zrobisz tego z std::string, bo ten typ dynamicznie alokuje pamięć pod to co ma przechowywać.

0

Dzięki za wyjaśnienie ;) A jest jakaś inna biblioteka lub klasa przy której mogę to zrobić..? Oprócz "char". Tak żebym mógł odczytać wartość z adresu.... np : "Test"..

4

Bądź mężczyzną i zrób to jak mężczyzna na tablicy char.

0

Dobrze.. Będę mężczyzną i zrobię to jak prawdziwy mężczyzna "na tablicy char" :D

0

Heh, podzielę się z Wami moim kodem ;)

Memory.cpp

#include "Memory.h"

Memory::Memory(const char* ClassName)
{
	this->h_HWND = FindWindow(ClassName, NULL);
	GetWindowThreadProcessId(this->h_HWND, &this->h_PID);
	this->h_Process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, this->h_PID);
}

Memory::~Memory(void)
{
	this->h_HWND = NULL;
	this->h_PID = NULL;

	if (this->h_Process != 0x00000000)
		CloseHandle(this->h_Process);
}

Memory.h

#pragma once

class Memory
{
public:
	Memory(const char* ClassName);
	template <class T> bool Read(unsigned int Offset, T& Value)
	{
		SIZE_T size = 1024;
		SIZE_T sizeRead;
		bool valid;

		valid = ReadProcessMemory(this->h_Process, (LPCVOID)Offset, (LPVOID)&Value, size, &sizeRead) ? true : false;

		if(valid == false || sizeRead != size){
			return false;
		}

		return true;
	}
	virtual ~Memory(void);
private:
	HANDLE h_Process;
	DWORD h_PID;
	HWND h_HWND;
};

//EDIT:
Pracuje jeszcze nad zapisywaniem :D

Pozdro, Mateusz ;)

1
  1. Memory::Memory - brak obsługi błędów, czyżby przez to że obawiasz się wyjątków w konstruktorze?

W wypadku wyjątku w konstruktorze destruktor AFAIK się nie uruchomi, więc masz trzy wyjścia:
a) rozdzielić konstruktor na konstruktor który tylko zapamiętuje parametry i wychodzi oraz na inicjalizator ("init", "attach") który robi resztę.
b) obsłużyć pobrane już w konstruktorze zasoby przez smart pointery:
http://www.parashift.com/c++-faq-lite/selfcleaning-members.html

c) przed rzuceniem wyjątkiem w konstruktorze zwolnić wszystko co było zaalokowane

  1. W wypadku błędu OpenProcess zwraca NULL. Dlaczego w destruktorze używasz jakiejś dziwnej stałej zamiast NULL?

  2. W Memory::Read zamiast:

if(valid == false || sizeRead != size){

napisz:

if((!valid) || sizeRead != size){	
0

Sry... Ucze się jeszcze C++...
Mam kod:

template <class T> bool Write(unsigned int Offset, const T* Value)
	{
		SIZE_T size = strlen(Value);

		SIZE_T sizeRead;
		bool valid;

		valid = WriteProcessMemory(this->h_Process, (LPVOID)Offset, (LPCVOID)&Value, size, &sizeRead);

		if((!valid) || sizeRead != size){
			std::cout << "Błąd" << std::endl;
			return false;
		}

		return true;

Ale chce by był on uniwersalny... na razie mam problem z tym. "Write<int>(0x0000001, 5665)" lub "Write<char>(0x0000001, "costam")". A dokładnie tu: "SIZE_T size = strlen(Value);"
Pomożecie? :)

0

strlen jak sama nazwa wskazuje, zwraca długość ciągu znaków. Nie ma szans żeby to zastosować do liczb.
http://www.java2s.com/Tutorial/Cpp/0100__Development/Usingtypeidwithtemplates.htm
Jeśli typ to ciąg znaków użyj strlen, jeśli nie sizeof.

2

Napisałem też podobną klasę. Używa inteligentnych wskaźników z C++11 oraz boost do przeładowania funkcji na podstawie typu szablonowego. Jeżeli nie chcesz bawić się w C++11 to inteligentne wskaźniki też można zamienić na te z boost.

Klasy używa się bardzo prosto i cała magia to metody Read oraz Write.

Write ma trzy wersje. Pierwsza jest podstawowa, przyjmuje adres docelowy, adres źródłowy oraz ilość bajtów do zapisania.
Druga wersja służy do zapisywania pojedynczych obiektów. Przyjmuje adres docelowy i referencję do obiektu.
Trzecia wersja służy do zapisywania tablic. Przyjmuje adres docelowy i tablicę.
Jeżeli mamy do zapisania wiele obiektów, do których dostęp mamy tylko za pomocą wskaźnika to używamy pierwszej wersji.

Read ma cztery wersje. Pierwsza - podstawowa, przyjmuje adres źródłowy, adres do bufora i jego wielkość.
Druga wersja służy do odczytania jednego obiektu i zwraca go (może zostać skopiowany, jeżeli nie zadziała return value optimization).
Trzecia wersja również służy do odczytania jednego obiektu, ale zwraca inteligentny wskaźnik.
Czwarta wersja odczytuje wiele obiektów (pobiera ich ilość) i również zwraca do nich inteligentny wskaźnik.

Zastosowanie w praktyce:

#include "ProcessMemory.hpp"

#include <memory>
#include <string>

#include <Windows.h>
#include <assert.h>

struct Test
{
	char Values[16];

	Test()
	{
		for(int i = 0; i < 16; i++)
			Values[i] = rand() % 256;
	}

	bool IsEqualTo(const Test& another)
	{
		for(int i = 0; i < 16; i++)
		{
			if(Values[i] != another.Values[i])
				return false;
		}

		return true;
	}
};

int main()
{
	ProcessMemory processMemory(GetCurrentProcessId());

	{
		double original = 3.14;
		double retrieved = processMemory.Read<double>(&original);

		assert(original == retrieved);
	}

	{
		Test original;
		std::unique_ptr<Test> retrieved = processMemory.Read<Test*>(&original);

		assert(retrieved->IsEqualTo(original));
	}

	{
		Test originals[5];
		std::unique_ptr<Test[]> retrieved = processMemory.Read<Test[]>(originals, 5);

		for(int i = 0; i < 5; i++)
			assert(retrieved[i].IsEqualTo(originals[i]));
	}

	{
		double original = 3.14;
		double placeholder;
		
		processMemory.Write(&placeholder, original);

		assert(original == placeholder);
	}

	{
		Test originals[5];
		auto placeholder = std::unique_ptr<Test[]>(new Test[5]);

		processMemory.Write(placeholder.get(), originals);

		for(int i = 0; i < 5; i++)
			assert(placeholder[i].IsEqualTo(originals[i]));
	}

	{
		const char* original = "testowy napis";

		std::unique_ptr<char[]> retrieved = processMemory.Read<char[]>(original, strlen(original) + 1);

		assert(strcmp(original, retrieved.get()) == 0);
	}

	{
		std::string original = "testowy napis";
		std::string retreived = processMemory.Read<char[]>(&original[0], original.size() + 1).get();

		assert(original == retreived);
	}

	return 0;
}

I sama klasa (powinna być leak-free, ale nie testowałem):

#pragma once

#include <Windows.h>

#include <memory>
#include <exception>

#include <boost\type_traits\is_pointer.hpp>
#include <boost\type_traits\is_array.hpp>
#include <boost\type_traits\remove_pointer.hpp>
#include <boost\type_traits\remove_bounds.hpp>
#include <boost\utility\enable_if.hpp>

class ProcessMemory
{
private:
	HANDLE _processHandle;

	bool IsValidHandle(HANDLE handle)
	{
		return handle != NULL && handle != INVALID_HANDLE_VALUE;
	}

public:
	ProcessMemory(int processId)
	{
		_processHandle = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, false, processId);

		if(!IsValidHandle(_processHandle)) throw std::exception();
	}

	~ProcessMemory()
	{
		if(IsValidHandle(_processHandle))
		{
			CloseHandle(_processHandle);
			_processHandle = NULL;
		}
	}

	void Read(LPCVOID address, LPVOID buffer, SIZE_T size)
	{
		SIZE_T bytesRead;

		if(ReadProcessMemory(_processHandle, address, buffer, size, &bytesRead) == 0)
			throw std::exception();

		if(bytesRead != size)
			throw std::exception();
	}

	template<typename T>
	T Read(LPCVOID address, ...)
	{
		T object;

		Read(address, &object, sizeof(T));

		return object;
	}

	template<typename T>
	std::unique_ptr<typename boost::remove_pointer<T>::type>
	Read(LPCVOID address, typename boost::enable_if<boost::is_pointer<T>>::type* dummy = 0)
	{
		typedef boost::remove_pointer<T>::type TType;

		TType* object = new TType;
		
		try
		{
			Read(address, object, sizeof(TType));
		}
		catch(...)
		{
			delete object;

			throw;
		}

		return std::unique_ptr<TType>(object);
	}

	template<typename T>
	std::unique_ptr<T> Read(LPCVOID address, size_t elementCount, typename boost::enable_if<boost::is_array<T>>::type* dummy = 0)
	{
		typedef boost::remove_bounds<T>::type TType;

		TType* objects = new TType[elementCount];
		
		try
		{
			Read(address, objects, sizeof(TType) * elementCount);
		}
		catch(...)
		{
			delete[] objects;

			throw;
		}

		return std::unique_ptr<TType[]>(objects);
	}

	void Write(LPVOID address, LPCVOID buffer, SIZE_T size)
	{
		SIZE_T bytesWritten;

		if(WriteProcessMemory(_processHandle, address, buffer, size, &bytesWritten) == 0)
			throw new std::exception;

		if(bytesWritten != size)
			throw std::exception();
	}

	template<typename T>
	void Write(LPVOID address, const T &object)
	{
		Write(address, &object, sizeof(T));
	}

	template<typename T, size_t elementCount>
	void Write(LPVOID address, T (&objects)[elementCount])
	{
		Write(address, &objects, sizeof(objects));
	}
};
0

Bardzo Ci dziękuję! :)
Mógłby ktoś mi wytłumaczyć działanie smart pointerów..? Bardzo chciałbym sie ich nauczyć.. :) Jakieś przykłady byłyby mile widziane ;)

0
aifam96 napisał(a):

Mógłby ktoś mi wytłumaczyć działanie smart pointerów..? Bardzo chciałbym sie ich nauczyć.. :) Jakieś przykłady byłyby mile widziane ;)

tutaj masz przykładowy rozdział z heliona. Poza tym fraza "inteligenty wskaźnik" w znanej wyszukiwarce powinna zwrócić satysfakcjonujące wyniki.

0

Dziękuję ;) Wgl dziękuję za każdą odpowiedź.. bo każda odp mi pomaga ;) Fajnie forum :D

2

@Rev: masz doświadczenie z językami Java lub C#?

IMHO w linijce tego typu masz dwa błędy:

throw new std::exception;
  1. wywołujesz throw z "new" zamiast z obiektem jak inni
  2. zgłaszasz wyjątek bez podania przyczyny

Lepsze byłoby:

throw std::runtime_error ("ProcessMemory.Write failed");

Ad. 1)
Jak obsłużysz wyjątek zgłoszony z "new"? Chyba inaczej niż normalnie. A jeśli inaczej to znaczy że albo swoje wyjątki obsługujesz zawsze inaczej niż standardowe, albo masz rozjazd w kodzie...

Poniżej przykład:
http://ideone.com/lO493

0

Oczywiście, preferowany sposób rzucania wyjątków w C++ jest poprzez zwykłe stworzenie obiektu i łapanie przez stałą referencję. Wtedy nie trzeba martwić się o pamięć po nim. Mój główny język to C# i tak z rozpędu rzuciłem je przez new ;). Raczej chodziło mi o pokazanie "niecieknącego" RAII niż faktyczne raportowanie błędów. A z tego co kojarzę to ludzie radzą też, żeby nie polegać na domyślnym tekście z runtime_error i what z exception.

0
Rev napisał(a):

A z tego co kojarzę to ludzie radzą też, żeby nie polegać na domyślnym tekście z runtime_error i what z exception.

Podaj powody bo brzmi trochę jak plotka zasłyszana u cioci na imieninach.

http://www.cplusplus.com/reference/std/stdexcept/runtime_error/

0

A jeszcze jedno pytanie... Smart pointery powoli rozumiem. ;) Dzięki, fajna żecz ;) A tak jeszcze jedno ;)

  1. Chciałbym zrobić system wyjątków, może być try...catch? czy lepiej jakiś inny użyć..?

  2. A i może mi ktoś wyjaśnić czemu w template jest czasem <class T> albo <typename T>? Jest jakaś różnica?

  3. Jak sprawdzić typy w template (w IF)... np:

template <class T> bool Sprawdz(T typ)
{
if (T == char) // CHODZI DOKŁADNIE TUTAJ, JEST JAKIŚ SPOSÓB NA TO?
{
std::cout << "Typ: CHAR" << std::endl;
}
else
{
std::cout << "Typ: INNY" << std::endl;
}
}
  1. Jest takie coś (Z powyższego kodu Rev):
        if(bytesWritten != size)
                throw new std::exception;

Jak to: "throw new std::exception;" zamienić na boosta?

0
  1. może być try...catch, a nawet musi jak rozmawiamy o wyjątkach
  2. Nie ma różnicy, tu jest opisane dlaczego są dwa keywordy: http://blogs.msdn.com/b/slippman/archive/2004/08/11/212768.aspx
  3. operator typeid: if(typeid(T) == typeid(char)) ... (nie sprawdzałem, ale powinno działać)
  4. co znaczy zamienić na boost'a?
0
  1. to jest jakiś inny?
  2. podałem ci rozwiązanie już wcześniej
  3. a po co cokolwiek zamieniać?

//@byku_guzio był szybszy

0
  1. A jest jakiś inny?
  2. Wszystko jedno, kwestia umowna
  3. Użyj mechanizmu "explicit template specialization" - możesz zdefiniować osobno funkcję/klasę dla danego typu a osobno dla typu nieznanego:
template <class T> bool Sprawdz() {
    std::cout << "Typ: INNY" << std::endl;
}

template <> bool Sprawdz<char>()
{
    std::cout << "Typ: CHAR" << std::endl;
}
  1. Po prostu zamiast std::exception użyj innego typu.
0

Dzięki ;)
Mam jeszcze pytania :D Jak na razie jedno ;P

Mam w main.cpp "try... catch".. w "catch" jest MessageBox wyświetlający co poszło nie tak.. Niestety nie wyświetla mi tego MessageBox-a tylko przechodzi do debuggera... Wiecie jak to naprawić? ;)

Zamiast "std::exception" użyłem "throw new boost::exception_ptr();". Dobrze zrobiłem?

0

Pewnie źle łapiesz wyjątek.

"Zamiast ..." - nie, exception_ptr to jest wskaźnik http://www.boost.org/doc/libs/1_41_0/libs/exception/doc/exception_ptr.html Może na razie zostaw boosta i ogarnij porządnie wyjątki :)

0

Dobrze.. zostanę przy std::exception ;)

Mam kod (by Rev , po edycji :D):

template<class T> bool Read(unsigned int Address, const T* Buffer)
		{
			SIZE_T bytesRead;
			SIZE_T size = sizeof(T);

			if(typeid(T) == typeid(char))
				size = strlen(*Buffer) + 1;
 
			if(ReadProcessMemory(_processHandle, (LPCVOID)Address, (LPVOID)Buffer, size, &bytesRead) == 0)
                throw new std::exception;
 
			if(bytesRead != size)
                throw new std::exception;
		}

"bool Read(unsigned int Address, const T* Buffer)" użyć "T&" czy zostawić? Chodzi mi o wygode :D

A to mój main.cpp:

// TiBot.cpp: Określa punkt wejścia dla aplikacji konsoli.
//

#include "stdafx.h"
#include "ProcessMemory.h"

/* Moje notatki:
Mając wskaźnik & możemy bezpośrednio odczytać z niego wartość.
Mając wskaźnik * musimy podać mu adres i wtedy można odczytać z niego wartość przed zmienną dodawając *.

	int i = 0;
	int* x = &i;
	int& z = i;
	int& y = *x;
	int* w = &y;

	std::cout << i << std::endl; // Wartość
	std::cout << *x << std::endl; // Wartość
	std::cout << z << std::endl; // Wartość
	std::cout << y << std::endl; // Wartość
	std::cout << *w << std::endl; // Wartość

	std::cout << &i << std::endl; // Adres
	std::cout << x << std::endl; // Adres
	std::cout << &z << std::endl; // Adres
	std::cout << &y << std::endl; // Adres
	std::cout << w << std::endl; // Adres
*/

int main(int argc, char* argv[])
{
	try
	{
		boost::scoped_ptr<ProcessMemory> pm (new ProcessMemory("TibiaClient"));
		char string;	
		pm->Read(0x0064004C, &string);
		std::cout << string << std::endl;
	}
	catch(std::exception& ex)
	{
		MessageBox(NULL, ex.what(), "TEST", 0);
	}
	system("pause");
	return 0;
}

Odczytuje mi tylko jeden wyraz... Wiem że w template:

			SIZE_T size = sizeof(T);

			if(typeid(T) == typeid(char))
				size = strlen(*Buffer) + 1;

To jest niepoprawne ale po wstawieniu w size=128 też odczytuje jeden wyraz i na końcu błąd.. Jak naprawić? :)

0
  1. Dlaczego const skoro zmieniasz tą pamięć?!?!?!?!?!
  2. Czy użyjesz referencji czy wskaźnika to w zasadzie kwestia umowna. Podoba mi się konwencja używania wyłącznie wskaźników gdy przekazujemy coś nie-const. Wtedy jak widzimy kod wywołujący funkcję i w nim operator &:
foo(&x);

to od razu widać, że zmienna x może być zmieniona przez funkcję foo.
3. Ta funkcja miała odczytać wartość pojedynczej zmiennej. Dlaczego czytając jeden znak char ty chcesz czytać dwa znaki? I dlaczego strlen? Chyba nie wiesz jak strlen działa, tą funkcją nie sprawdzisz rozmiaru tablicy. Kompletnie pomieszałeś wszystko. Jeśli chcesz odczytać łańcuch znaków aż do natrafienia na bajt zerowy to zrób osobną funkcję ReadString. A Read pozostaw w spokoju, niech służy do odczytania zmiennej/tablicy zmiennych.

TibiaClient
no i wszystko jasne ;)

0

Kod aktualnie tak wygląda:
main.cpp:

// TiBot.cpp: Określa punkt wejścia dla aplikacji konsoli.
//

#include "stdafx.h"
#include "ProcessMemory.h"

/*
Mając referencje & możemy bezpośrednio odczytać z niego wartość.
Mając wskaźnik * musimy podać mu adres i wtedy można odczytać z niego wartość przed zmienną dodawając *.

	int i = 0;
	int* x = &i;
	int& z = i;
	int& y = *x;
	int* w = &y;

	std::cout << i << std::endl; // Wartość
	std::cout << *x << std::endl; // Wartość
	std::cout << z << std::endl; // Wartość
	std::cout << y << std::endl; // Wartość
	std::cout << *w << std::endl; // Wartość

	std::cout << &i << std::endl; // Adres
	std::cout << x << std::endl; // Adres
	std::cout << &z << std::endl; // Adres
	std::cout << &y << std::endl; // Adres
	std::cout << w << std::endl; // Adres
*/

int main(int argc, char* argv[])
{
	try
	{
		boost::scoped_ptr<ProcessMemory> pm (new ProcessMemory("TibiaClient"));
		char string[256];	
		pm->Read(0x0064004C, string);
		std::cout << string << std::endl;
		pm->Write(0x0064004C, "MateuszX");
	}
	catch(std::exception& ex)
	{
		MessageBox(NULL, ex.what(), "TEST", 0);
	}
	system("pause");
	return 0;
}

ProcessMemory.h:

#pragma once
 
class ProcessMemory
{
private:
        HANDLE _processHandle;
 
        bool IsValidHandle(HANDLE handle);
 
public:
        ProcessMemory(const char* ClassName);
 
        ~ProcessMemory();

		template<class T> bool Read(unsigned int Address, T& Buffer)
		{
			SIZE_T bytesRead;
			SIZE_T size = sizeof(Buffer);
 
			if(ReadProcessMemory(_processHandle, (LPCVOID)Address, (LPVOID)&Buffer, size, &bytesRead) == 0)
                throw std::runtime_error ("ProcessMemory.ReadMemoryProcess failed");
 
			if(bytesRead != size)
                throw std::runtime_error ("ProcessMemory.Read.bytes!=size failed");

			return true;
		}

		template<class T> bool Write(unsigned int Address, const T& Buffer)
		{
			SIZE_T bytesWritten;
			SIZE_T size = sizeof(T);

			if(WriteProcessMemory(_processHandle, (LPVOID)Address, (LPCVOID)&Buffer, size, &bytesWritten) == 0)
                throw std::runtime_error ("ProcessMemory.WriteMemoryProcess failed");
 
			if(bytesWritten != size)
                throw std::runtime_error ("ProcessMemory.Write.bytes!=size failed");

			return true;
		}

		template<> bool Write<char>(unsigned int Address, const char& Buffer)
		{
			SIZE_T bytesWritten;
			SIZE_T size = strlen(&Buffer) + 1;

			if(WriteProcessMemory(_processHandle, (LPVOID)Address, (LPCVOID)&Buffer, size, &bytesWritten) == 0)
                throw std::runtime_error ("ProcessMemory.WriteMemoryProcess failed - CHAR");
 
			if(bytesWritten != size)
                throw std::runtime_error ("ProcessMemory.Write.bytes!=size failed - CHAR");

			return true;
		}
};

Może ktoś przeanalizować to? Tzn czy nie ma błędów.. Oczywiście wszytko działa... a jeśli kod będzie dobry na pewno komuś się przyda ;)

0

Proooosszzze :D

0
  • main wygląda dziwnie: cout + MessageBox - albo konsola albo okienko
  • bool Write(), bool Read() - deklaracja myląca, skoro w przypadku błędu wywalasz exception, lepiej będzie void Write(), void Read()
  • specjalizacja dla char powinna operować na pojedynczym znaku (nagłówek) lub na tablicy znaków (strlen). Musisz się zdecydować co chcesz osiągnąć. jeśli to drugie to raczej chodzi o specjalizację
< char *>
  • nazwa parametru Address jest myląca. W rzeczywistości chodzi o offset. Address nie może być typu unsigned int, offset - tak.
  • zrób test z prawdziwego zdarzenia - czytaj dane procesu w którym się znajdujesz, które jesteś pewien że istnieją - jak w przykładzie u Rev-a
  • Twoje wersje Read i Write nie działają z klasami, dlatego na wzór kodu Rev-a mógłbyś zastosować boost::is_pod podobnie jak on zrobił is_array, ale nie pytaj mnie jak - sam musiałbym to potestować. Dzięki temu Twój kod nie będzie mógł być uruchomiony z obiektem jako parametrem - i dobrze
0

Witam, przepraszam że odświeżam lecz potrzebuje tą samą klasę (Rev-a) pod Linuxa..

1 użytkowników online, w tym zalogowanych: 0, gości: 1