snakeninny大牛手把手教你写内存修改器(翻译)

snakeninny大牛用英语写的,自己翻了一下

Apr '142
Originality: http://bbs.iosre.com/t/write-a-simple-universal-memory-editor-game-trainer-on-osx-ios-from-scratch/115
Author: snakeninny
Please include the above name and link with repost, thank you.

Hi guys!
Memory editor is used as game trainer on iOS/OSX by most power users, but very few App Store developers know how to write one. Today I’ll be here guiding you through the very basic knowledge of writing a memory editor, we’ll review some basic concepts which may be ignored by Objective-C coders, describe how a memory editor works, explain the usage of some memory manipulation functions, and write the code at last. Our target is a working memory editor (game trainer) who successfully cheats “Blood & Glory 2” from OSX App Store and “大富翁4FunLite” from iOS App Store. Feel free to correct me if anything is wrong, and have fun <3

很多菜鸟现在手握内存修改器都能横行天下,但是困在沙盒的开发者却鲜有涉猎内存修改的。今天,我将就内存修改器的基础知识和大家一起探讨一下。我们将回顾一些Obj-c被忽略的知识点,描述一下内存修改器工作原理, 注释一部分常用的内存操作函数,最后写代码来实现。我们的目标是做出一款可以修改OSX平台中Blood & Glory 2"和iOS平台上大富翁的内存修改器。如果想夸奖我一下,就多推荐一下我的书。谢谢

Part I Basic concepts

  1. Process
    We write HelloWorld.c, compile it and get HelloWorld.exe, an executable. On iOS/OSX, executables are all MachO format, containing data and instructions needed for execution. When the executable is run by OS, it becomes a process. So actually executable and process are different status of a same object, i.e. some data and a set of instructions. We can view executable as static, process as dynamic. Executable is stored on hard disk, while process is “stored” in memory. So bare in mind, process contains data and instructions, and they’re in memory.

第一部分,基础概念

1.进程。

曾记否我们写的第一个HelloWorld.c,编译,获得一个执行文件–HelloWorld.exe,在 iOS/OSX,可执行文件是MachO格式的,包含着需要执行的数据和指令。当再操作系统运行的时候,它变身位一个进程。所以可执行文件和进程是一个对象的不同状态。我们可以把可执行文件是静态的,进程是动态的,可执行文件存储再硬盘上,进程被存储再内存中。所以,跟我一起念:“进程包含数据和指令,他们都在内存里”,好,念一百遍。

Virtual memory, memory page and memory region
//虚拟内存,内存页面和内存区域
When OS runs an executable, it loads this executable from hard disk to memory. Where in memory is this process located? This is a very complicated procedure that ATM, the only thing you need to know is that every process has its own separate memory space (i.e. process A and B run in different memory areas), and this is achieved by memory virtualization. Most of the memory operations we do are based on virtual memory, and for a 32-bit processor like ARMv7, the virtual memory size for each process is 2^32 = 4G bytes large. But most processes don’t take 4GB memory to run, they consume only a small part of 4GB. The actual size of memory a process uses is called the process’ virtual memory address space, and the 4GB virtual memory space is divided into many many memory pages for distribution, while memory page is “a fixed-length (4096 bytes on iOS/OSX) contiguous block of virtual memory, and it is the smallest unit of data for memory operation”. The virtual memory address space of a process consists of numerous regions of memory. Each memory region contains a number of virtual memory pages, and pay attention, memory regions may not be contiguous. So the above concepts can be depicted by the following scrawls:
//运行一个可执行文件的时候,他将从硬盘中把可执行文件加载到内存中。这难道是进城的所在地?这是一个十分复杂的过程,你只要知道每个进城有它自己的独立的内存区间就可以了。譬如进程A和进程B在不同的内存区域中运行。他是通过内存虚拟化来实现的。我们几乎所有的内存操作是基于虚拟内存的。对32位的ARMv7处理器来说,虚拟内存的大小是4G,但是大部分的程序不会占有4G内存来跑,他们只消耗了4G中很小的一部分。进程实际占有的内存大小我们叫他进程的虚拟内存空间。4G的内存空间被分成很多很多内存页面来发布。内存页面是一个固定大小(在iOS/OSX中是4096位)连续的虚拟内存块,这事内存操作的最小单位。进程的虚拟内存地址空间包含了很多内存区域。每个区域又包含了很多虚拟页面。需要注意的是,内存区域不一定是连续的。下面的草图描述了上面描述的概念。

1.jpg2274x2562 711 KB

2.jpg2448x2329 678 KB

Part II Modeling
After a brief introduction to the basic concepts, you may wonder, what does that have to do with our goal? Good question. So now let’s try to turn the above theory into a programming model, hence we can write code to realize our thoughts.
Think with me: What’s a memory editor? Sure it edits memory. But how? There’re addresses in memory, we can simply locate the address where our target value resides, and use system APIs to read/write it. But where’s the target value address? How do we locate it? Keep reading.
//第二部分 建模
介绍了基本的概念后,你可能疑惑,为了实现我们的目标我们必须要做的是什么?好问题,下面让我们来把上面的理论变成编程建模,我们可以通过代码来实现我们的思想。
我们来一起思考一下,内存编辑器是什么?废话,当然是编辑内存的啊,但是怎么编辑呢?如果我们有了内存中的地址,我们可以很简单的定位我们需要需要编辑的的位置,并且用系统的API来读写它们。但是我们目标地址在哪里呢?我们怎么定位它,继续读吧,骚年!
I bet you know that, however deep level code we write, it’s human-readable. Humans can’t read machine code, but machines can — it’s their native language! And all machine code is binary format, it’s a mix of 0s and 1s. That’s to say, both data and instructions are translated to a combination of 0s and 1s ultimately, and a process’ virtual memory address space is filled with 0s and 1s. If the target value is, say, “010101”, and the virtual memory address space is like “00000111110101010110011000…”, locating our target value in virtual memory address space is as easy as searching for a string pattern inside a paragraph. In our example, there is a matching pattern in memory, starting at the 11th number “0”, ending at the 16th number “1”.
我打赌你可能知道不管我们写的代码多么底层,它是人可以读的。我们读不了机器码,但是机器可以他们的方言。并且所有的机器码是二进制的,是0和1的组合。这就是说,所有指令最终被翻译成0和1组合,而且进程中的虚拟内存地址空间充满了0和1.假设我们目标值是010101,虚拟内存空间是00000111110101010110011000…这样子,在虚拟内存地址空间中定位我们目标值是很容易的,我们只需要搜索这个字符串就可以了。我们的例子中,在内存中,有个匹配的类型,第十一个数字是0开始的,第十六位位1结束的。
In actual cases, it’s easier to view the binary as hexdecimal. So a segment of memory would be like this:
在实际例子中,十六进制来看二进制是很容易的。内存段可能是如下这样子的:

Note that each block like “0xfeedface” takes 4 bytes (because 0xfe = 11111110 = 8 bits = 1 byte), so it’s like:
注意,每个像 0xfeedface的块,占了4个字节(如果你连这个也需要解释,你可以关闭电脑了)
Address : Byte0x02cb2000 : 0xfe0x02cb2001 : 0xed0x02cb2002 : 0xfa0x02cb2003 : 0xce0x02cb2004 : 0x00…0x02cb200c : 0x000x02cb200d : 0x000x02cb200e : 0x000x02cb200f : 0x08
If our target value is int targetValue = 21592 == 0x5458, we can find it in the memory segment from address 0x02cb2009 to 0x02cb200c, right? However, because instructions and other data are also stored in memory, 0x5458 may be only part of an instruction, or part of a long number (say 0x0000545845545f5f. ARM is little-endian), it may not present our int target value. This situation happens in most of our memory searchs, i.e. more than one match can be found. If that’s the case, we have to search again for a more specific value (we’ll look into this later in the example). Now we know what “memory searching” means smile
我们我们目标值是21592=0x5458(程序员分不清楚圣诞节和复活节,悲哀!)我们可以在 0x02cb2009-0x02cb200c这个内存段中找到我们的目标值,对吧?(大牛也有犯错的时候??,明明在0x02cb2029-0x02cb202c!!)。但是,因为指令和其他数据也存在内存中,0x5458也许仅仅是指令的一部分,或者是一个唱整数的一部分(再ARM小端中,是0x0000545845545f5f,小端和大端自行百度。)所以我们也许找不到我们的目标值。在很多内存搜索中我们常常会遇到这种情况。譬如,可能我们不知找到一个,这种情况下,我们需要一些更特殊一点的值来重新寻找(我么下面的例子会看到这种情况),下载我们大概了解了内存搜索的概念了吧。

With this information, we can try to model our memory editor. It can be divided into 3 steps:
i) Get the virtual memory address space of the target process, by enumerating all its virtual memory regions;
ii) Search our target value in the address space, and get the possible address(es) of our target value (by some tricks, one of which we’ll discuss);
iii) Modify the value at the target address(es).
//有了这些基本概念,我们把我们的内存修改器建模一下。分为三部:
1、通过枚举所有的虚拟内存区域来 获取目标进程的虚拟内存空间,
2、再地址空间中搜索我们目标值,或者包含目标值的所有可能的地址(用了一点小伎俩,下面我们会仔细讨论这个小伎俩)
3、修改目标地址中的值

That’s it. Simpler and clearer than you originally thought, huh?
就是酱紫,比起我们的开始的想法,是不是很清爽啊,赞一个!

Part III Functions
Before coding, let’s take a quick look of the main functions we’re gonna use frequently. They’re all mach_vm functions, which can be found at http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/osfmk/vm/vm_user.c2. They’re: (words between dollar symbols are $arguments$)
第三部分,函数
在编代码之前,我们快速浏览一下我们经常用的主要函数。他们全部是mach_vm函数,我们可以再这个开源地址中找到他们 http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/osfmk/vm/vm_user.c。他们是(在两个$$之间的单词是参数)

kern_return_t mach_vm_read( vm_map_t map, mach_vm_address_t addr, mach_vm_size_t size, pointer_t *data, mach_msg_type_number_t *data_size)
Description: Read/copy a range from one address space and return it to the caller. $map$ is the port for the task whose memory is to be read; $addr$ is the address at which to start the read; $size$ is the number of bytes to read; $data$ is a buffer to store the read bytes; $data_size$ on input, is a pointer to the maximum size of the buffer; on output, points to the size read.

第一个主要函数的表述。 从一个地址空间中读取或者拷贝一个范围并且把这个范围返回给调用者。¥map¥是要读取内存的任务的端口。¥addr¥是开始读的内存地址,¥size¥是要读取多少字节,¥data¥是存储读到的字节的缓存,¥data_size¥分两种情况,再输入的是偶,他是缓存最大值的指针,输出时,指向读取的大小。
实际上这个函数

kern_return_t mach_vm_write( vm_map_t map, mach_vm_address_t address, pointer_t data, __unused mach_msg_type_number_t size);
Description: Write data to the specified address in the target task’s address space. $map$ is the port for the task whose memory is to be written; $address$ is the address at which to start the write; $data$ is a buffer to be written; $size$ is the size of $data$.
描述:往目标任务的内存地址空间中写入数据。 ¥map¥是要写入数据的内存的任务的端口,¥addr¥是开始写的内存地址,¥data¥是写入的缓存,¥size¥是写入数据的大小

kern_return_t mach_vm_region( vm_map_t map, mach_vm_offset_t *address, mach_vm_size_t *size, vm_region_flavor_t flavor, vm_region_info_t info, mach_msg_type_number_t *count, mach_port_t *object_name);
Description: Return description of a virtual memory region. $map$ is the port for the task whose address space contains the region; $addr$ on input, is the address at which to start looking for a region; on output, returns the starting address actually used; $size$ on output is the number of bytes in the located region; $flavor$ is the type of information to be returned, should be VM_REGION_BASIC_INFO; $info$ returns region information, should be of type vm_region_basic_info_data_64_t on both 32-bit and 64-bit OSes; $count$ on input, should be VM_REGION_BASIC_INFO_COUNT_64; on output, the size of the region.
描述: 返回虚拟内存区间的描述。$map$是一个任务端口,它的内存空间包含了要返回的区间。$addr$在输入的时候,是开始找这个区间的开始地址,再输出的时候,返回实际使用的开始地址。$size$再输出的时候,定位区间后,字节的数量,$flavor$是要返回的信息的类型,应当是VM_REGION_BASIC_INFO这个类型。$count$再输入的时候,应当是VM_REGION_BASIC_INFO_COUNT_64这个类型,输出时,是区间的大小。

void *memmem(const void *big, size_t big_len, const void *little, size_t little_len);
Description: Locates the first occurrence of the byte string $little$ in the byte string $big$. If it matches, a pointer to the first character of the first occurrence of $little$ is returned; else it returns NULL.
描述:在¥big¥字节字符串中定位第一个¥little¥,如果找到了符合的,返回¥little¥出现的位置的指针,没有就返回Null

Part IV Coding
There’s only one thing worth mentioning before we get coding: the example we give only works on Apps with no custom memory protection. The coder himself (a.k.a. myself) is a n00b on memory operation, so the code may not be very efficient and elegant. Feel free to make optimization stuck_out_tongue BTW, I’ve tried to comment most of the obscure code, so if you still can’t catch it, just ask!
Click me for the code4

第三部分,编写代码
再开始编写代码钱,再啰嗦几句。给的例子中只工作再没有自定义内存保存的app中,在内存操作中,这些代码是大牛写的,正如 snakeninny大牛,我好崇拜你啊。这些代码都十分的有效率和健壮,不用去优化了。顺便说一下,如果你想挑战我,就放马过来吧!
Part V Testing
(I got inspiration of the name from a mini movie, which is “海马洗头” in Chinese and tells a story about erasing people’s memory. It’s one of 20 mini movie series, all by famous Taiwanese directors. I strongly suggest you watch them because their visions are really original.)
第五部分,测试
我从小片中找到了灵感,海马洗头是洗脑的故事,是一个20分钟的小片,日本人导演的。建议去看看。特别是宅男。

HippocampHairSalon_OSX
Open up Blood & Glory 2 and head to the store at the right down corner directly.
//在mac电脑上玩,打开Blood & Glory 2 ,点那个store

See the silver coins and rubies on top of the screen?
不会没有看到金钱和宝石没有反应吧?

Write down the rubies’ number, mine is 69166 ATM. Then run HippocampHairSalon_OSX as root and find the process “Blood And Glory”. Enter its process ID, mine is 1283
记下来宝石的数量,69166,好,以root打开海马洗脑,找到游戏的进程号,输入进程号,so easy,妈妈再也不担心我打游戏了!

Enter the rubies’ number, i.e. 69166, and view the results
输入刚才看到的宝石数量,譬如69166,看结果

5 results are found and we’ve talked about the reason. Which one is for rubies? We have 2 simple ways to figure it out
i) Change the rubies to another number, and search again to see if we get less results. To that end, let’s buy some Health Potions and re-search
我们看到5个结果,就一个结果我就去睡觉了,还翻译啥啊。那个是宝石呢,两条道找到他。
第一条:去游戏玩一下,使宝石数量变化了,然后再搜索看看是不是结果少了一些,哪知道咋地越来越多!你牛逼是吧,我再让宝石数量变化一下(怎么变化?赚点增加数量,或者买点东西减少数量)

Unfortunately, we’ve gotten 8 more results for the new value 69136 frowning You can repeat this process until you find the only result, or turn to the following trick stuck_out_tongue
ii) Change the rubies and review the address. Again let’s buy some Health Potions to make our rubies 69106 (I forgot to screenshot), but review rather than re-search
不幸的事情天天有,我们还是有8个结果。一直试,直到只有一个结果!(除非你早上没吃药才会这么干)
第二条道:改变宝石数量,用review search results选项。我们还是再用宝石去买点东西使宝石数量减少譬如到69106(不过大牛忘记截图了),这次不是重新搜索,而是选择review search results

See something interesting? The value at 0x1a8fae0c has changed to 69106, and that matches our rubies! So this is very likely the target value, let’s modify it to verify

看到有趣的了吧。0x1a8fae0c这个地址的值改变位69106(没有截图,自己脑补画面),符合我们宝石的数量,所以这就是我们的目标地址。改变它!

Go back to the game, buy one more Health Potion to refresh the UI. Our rubies grew to 99993 now <3
回到游戏,买点东西,刷新一下,哈哈,不说了

Being a rich guy, we can now buy the best weapons and armours to finish our enemies in 1 single round. Isn’t that cool?!
我有钱了有钱了,我都不知道怎么去花!!是不是很酷!

13.png1280x800 427 KB

Quit and re-enter, you’ll find the rubies are still 99993. We’ve successfully cheated Blood & Glory 2, yay!
退出游戏,重新打开游戏,宝石数量荣然是很多,我们成功了
HippocampHairSalon_iOS
(Copy the compiled and entitled HippocampHairSalon_iOS to your iOS now)
2.在手机上用海马洗脑。
把海马洗脑先ldid -Sent.xml HippocampHairSalon_iOS一下,然后拷贝到手机上,拷贝到哪里?你想拷贝到哪里?不嫌麻烦你可以拷贝几十层文件夹里面去。如果想直接用当然是/usr/bin了啊。不知道Ldid?看样子就没有买《逆向工程》赶紧面壁去把。

Open up 大富翁4FunLite and start a new game
打开大富翁,开始一个新游戏。

14.PNG960x640 285 KB

When it’s our turn, write down the money we have, mine is 25000
轮到你的时候,记下来你的钱数和矿

15.PNG960x640 529 KB

Launch HippocampHairSalon_iOS as root and enter PID of “RichManLite”
root启动海马洗脑,输入大富翁的PID

Enter the amount of money, 25000, and we get 14 results. Good
输入钱数,我们得到了14个结果

Whatever, change the money
不理他,改变钱数

18.PNG960x640 525 KB

Review previous search results
选择2项

Modify our target value
改变目标值

We’re RichManReal now <3
我们现在时真的大富翁了

21.PNG960x640 560 KB

Quit and enter to verify our hack.
退出重新进入游戏验证
Part VI Conclusion
第五部分,结论
We’ve walked through the writing of a universal memory editor on iOS/OSX, and it works like a charm as game trainer. This post only demonstrates the possibilty of such a tool on Apple systems, things may get way more complicated in practice, especially on Apps who possess strong memory protection. In that case, you’ll have to combine memory editor with necessary reverse engineering skills to make it. Stick to @iOSAppRE and iosre.com, more interesting stuff are right on their way <3 Thank you for reading!
我们分别再IOS/OSX中测试了内存修改器,其实这是课前小餐点,真实的世界时比较复杂的,尤其是很多强内存保护的app,你必须结合内存修改器和逆向工程的技巧来解决。信 iOSAppRE,得永生!谢谢。,

References:

  1. http://en.wikipedia.org/wiki/Page_(computer_memory)
  2. https://developer.apple.com/library/mac/documentation/performance/conceptual/managingmemory/articles/aboutmemory.html
  3. http://en.wikipedia.org/wiki/Executable
  4. http://stackoverflow.com/questions/12999850/what-are-differentiates-between-a-program-an-executable-and-a-process
  5. http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/osfmk/vm/vm_user.c2
  6. http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/vm_read.html
  7. http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/vm_write.html
  8. http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/vm_region.html
5 个赞

牛比啊!能把缺失的图补上,排版再改进一下就好了:grinning_face_with_smiling_eyes:

得到狗神的鼓励,额的神啊。

你把markdown语法过一遍,把我说的地方完善一下,我就发个微博推一下你的这个帖子:grinning_face_with_smiling_eyes:

mini movie = 小片
taiwanese = 日本
我是不是穿越了什么

貌似翻译成微电影比较合适.taiwanese是台湾

Hi 想问一下怎么编译这样的.h .m文件啊?