先写个简单的 tap10000
程序,代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int count = 10000;
char none[30] = {0};
NSLog(@"[tap1000]pid -> %d", getpid());
NSLog(@"-> %d", count);
while (count>0) {
gets(none);
count--;
NSLog(@"-> %d", count);
}
}
return 0;
}
测试一下功能,完美!
先从 find 和 patch 两个功能入手,核心功能在 ipa-medit/cmd/cmd.go
文件下:
package cmd
import (
...
)
type Found struct {
addrs []int
converter func(string) ([]byte, error)
dataType string
}
func Find(pid string, targetVal string, dataType string) ([]Found, error) {
...
}
...
func Patch(pid string, targetVal string, targetAddrs []Found) error {
...
}
...
函数使用 vmmap --wide ${pid}
查看进程的虚拟内存区域,再调用 GetWritableAddrRanges(vmmapResult)
函数遍历可写区域获取地址范围,以"==== Writable regions for process"
开始的行即为可写的区域;
vmmap 输出示例:
Process: tap10000 [19769]
Path: /Users/USER/Downloads/tap10000
Load Address: 0x100350000
Identifier: tap10000
Version: ???
Code Type: ARM64
Platform: macOS
Parent Process: zsh [11605]
...
Physical footprint: 1297K
Physical footprint (peak): 1297K
----
Virtual Memory Map of process 19769 (tap10000)
Output report format: 2.4 -- 64-bit process
VM page size: 16384 bytes
==== Non-writable regions for process 19769
REGION TYPE START - END [ VSIZE RSDNT DIRTY SWAP] PRT/MAX SHRMOD PURGE REGION DETAIL
__TEXT 100350000-100354000 [ 16K 16K 0K 0K] r-x/r-x SM=COW /Users/USER/Downloads/tap10000
...
==== Writable regions for process 19769
REGION TYPE START - END [ VSIZE RSDNT DIRTY SWAP] PRT/MAX SHRMOD PURGE REGION DETAIL
Kernel Alloc Once 100460000-100468000 [ 32K 16K 16K 0K] rw-/rwx SM=PRV
...
__DATA 100640000-100644000 [ 16K 16K 16K 0K] rw-/rw- SM=COW /usr/lib/dyld
...
解析拿到可写内存区域地址范围后存入 addrRanges [][2]int
,接下来就是读取内存并查找值为 targetVal
的地址:
splitSize = 0x5000000
func FindDataInAddrRanges(pid int, targetBytes []byte, addrRanges [][2]int) ([]int, error) {
foundAddrs := []int{}
searchLength := len(targetBytes)
for _, s := range addrRanges {
beginAddr := s[0]
endAddr := s[1]
memSize := endAddr - beginAddr
for i := 0; i < (memSize/splitSize)+1; i++ {
// target memory is too big to read all of it, so split it and then search in memory
splitIndex := (i + 1) * splitSize
splittedBeginAddr := beginAddr + i*splitSize
splittedEndAddr := endAddr
if splitIndex < memSize {
splittedEndAddr = beginAddr + splitIndex
}
b := bufferPool.Get().([]byte)[:(splittedEndAddr - splittedBeginAddr)]
task := GetTaskForPid(pid)
if err := ReadMemory(task, b, splittedBeginAddr, splittedEndAddr); err == nil {
findDataInSplittedMemory(&b, targetBytes, searchLength, splittedBeginAddr, 0, &foundAddrs)
bufferPool.Put(b)
if len(foundAddrs) > 500000 {
fmt.Println("Too many addresses with target data found...")
return foundAddrs, TooManyErr{&Err{errors.New("Error: Too many addresses")}}
}
} else {
fmt.Printf("0x%x: %s\\\\n", beginAddr, err)
}
}
}
return foundAddrs, nil
}
因为目标内存太大,无法读取所有内存,因此 FindDataInAddrRanges
函数请将其拆分,然后在内存中搜。
task := GetTaskForPid(pid)
获取目标进程任务端口,c代码如下: