Excuse the ads! We need some help to keep our site up.
List
Memory cheat(iOS)
- 해당 내용은 2년전에 분석하고 개발한 Cheat tool에 대한 설명입니다.
- 해당 Tool은 보안 점검시 Memory cheat에 대한 검증용으로 필요해 개발하였습니다.
- 해당 정보와 Tool을 이용하여 부정 행위시 부정 행위를 한 사용자에게 책임이 있으며, 개발자에게는 책임이 없습니다.
Development Tools
- 해당 Cheat tool을 개발하기 위해 Theos를 사용합니다.
Structure of Memory Cheat
- Memory cheats tool은 다음과 같은 형태로 동작합니다.
The default behavior of the memory cheat tool.
1 | Process attach | 사용자 레벨에서 프로세스 주소 공간에 접근하기 위해 task_for_pid()를 사용해 대상 프로세스를 연결합니다. |
---|---|---|
2 | Check the process memory area | 효율적인 Memory 분석을 위해 대상 프로세스가 사용하는 Memory 정보(Memory map)를 확인합니다. |
3 | Memory access | 대상 프로세스의 Memory를 분석 및 변경하기 위해 vm_read_overwrite, vm_write를 사용해 메모리의 값을 읽고 변경합니다. |
Process list
SYNOPSIS - sysctl()
- sysctl() 함수 개요는 다음과 같습니다.
sysctl SYNOPSIS
#include <sys/types.h> #include <sys/sysctl.h> int sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen);
Management Information Base(MIB)
CTL_KERN |
|
---|---|
KERN_PROC |
|
Example
- 다음과 같이 sysctl() 함수를 사용해 Process list를 확인 할 수 있습니다.
RunningProcess.h
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; size_t miblen = 4; size_t size = 0; int st = sysctl(mib, miblen, NULL, &size, NULL, 0); struct kinfo_proc *process = NULL; struct kinfo_proc *newprocess = NULL; do { size += size / 10; newprocess = (kinfo_proc * )realloc(process, size); if (!newprocess){ if (process){ free(process); } return nil; } process = newprocess; st = sysctl(mib, miblen, process, &size, NULL, 0); } while (st == -1 && errno == ENOMEM); for (int i = nprocess - 1; i >= 0; i--){ NSString *processID = [[NSString alloc] initWithFormat:@"%d", process[i].kp_proc.p_pid]; NSString *processName = [[NSString alloc] initWithFormat:@"%s", process[i].kp_proc.p_comm]; [processID release]; [processName release]; } free(process);
BSD Library Functions Manual - SYSCTL(3)
Process attach
SYNOPSIS - task_for_pid()
- task_for_pid() 함수 개요는 다음과 같습니다.
- 해당 함수는 프로세스 ID이름을 지정하여 다른 프로세스에 대한 작업 포트를 가져옵니다.
- 권한이 있는 프로세스 또는 사용자 ID가 동일한 프로세스만 허용됩니다.
SYNOPSIS - task_for_pid()
#include <mach/mach.h> kern_return_t task_for_pid(struct task_for_pid_args *args);
kern_return_t task_for_pid(struct task_for_pid_args *args)
Example
Process attach
int attach(){ kern_return_t kret; tmp_target_task = 0; kret = task_for_pid(mach_task_self(),pid,&tmp_target_task); if (kret) { printf("task_for_pid() failed with message %s!\n",mach_error_string(kret)); }else{ printf("attach - target_task : %d, tmp_target_task : %d\n",target_task, tmp_target_task); kret = task_suspend(target_task); if (kret != KERN_SUCCESS) { printf("task_suspend() failed with message %s!\n",mach_error_string(kret)); }else{ printf("task_suspend - Success\n"); return 1; } } return 0; }
Check the process memory area
SYNOPSIS - vm_region_recurse()
- vm_region_recurse() 함수 개요는 다음과 같습니다.
- 해당 함수는 타겟 프로세스의 메모리 매핑 정보를 추출합니다.
- 1번째 인자 값에는 task_for_pid()함수로 부터 전달받은 작업 포트를 전달 합니다.
- 2번째 인자 값에는 서브맵의 주소 값을 저장 할 공간을 전달 합니다.
- 3번째 인자 값에는 서브맵의 크기 값을 저장 할 공간을 전달 합니다.
- 4번째 인자 값에는 서브맵의 개수 값을 저장 할 공간을 전달 합니다.
- 5번째 인자 값에는 서브맵에 대한 정보를 저장 할 공간을 전달 합니다.
- 해당 구조체의 protection, max_protection 값을 이용해 해당 서브맵의 권한을 확인 할 수 있습니다.
- protection : 현재 설정되어 있는 접근 보호 권한
- max_protection :
- 해당 구조체의 protection, max_protection 값을 이용해 해당 서브맵의 권한을 확인 할 수 있습니다.
- 6번째 인자 값에는 "VM_REGION_SUBMAP_INFO_COUNT" 값을 전달 합니다.
- 해당 함수는 타겟 프로세스의 메모리 매핑 정보를 추출합니다.
SYNOPSIS - vm_region_recurse()
#include <mach/mach.h> kern_return_t vm_region_recurse( vm_map_t map, vm_offset_t *address, vm_size_t *size, natural_t *depth, vm_region_recurse_info_t info32, mach_msg_type_number_t *count)
- 하지만 해당 함수는 최신 버전에서 64bit로 마이그레이션 되었습니다.
SYNOPSIS - vm_map_region_recurse_64()
kern_return_t vm_map_region_recurse_64( vm_map_t map, vm_map_offset_t *address, vm_map_size_t *size, natural_t *nesting_depth, vm_region_submap_info_64_t submap_info, mach_msg_type_number_t *count)
vm_region_recurse() and vm_map_region_recurse_64()
struct vm_region_submap_info
struct vm_region_submap_info
struct vm_region_submap_info { vm_prot_t protection; /* present access protection */ vm_prot_t max_protection; /* max avail through vm_prot */ vm_inherit_t inheritance;/* behavior of map/obj on fork */ uint32_t offset; /* offset into object/map */ unsigned int user_tag; /* user tag on map entry */ unsigned int pages_resident; /* only valid for objects */ unsigned int pages_shared_now_private; /* only for objects */ unsigned int pages_swapped_out; /* only for objects */ unsigned int pages_dirtied; /* only for objects */ unsigned int ref_count; /* obj/map mappers, etc */ unsigned short shadow_depth; /* only for obj */ unsigned char external_pager; /* only for obj */ unsigned char share_mode; /* see enumeration */ boolean_t is_submap; /* submap vs obj */ vm_behavior_t behavior; /* access behavior hint */ vm32_object_id_t object_id; /* obj/map name, not a handle */ unsigned short user_wired_count; };
struct vm_region_submap_info
Example
- 다음과 같이 vm_region_recurse()함수를 이용해 서브맵 정보를 추출 할 수 있습니다.
- info.protection, info.max_protection 값을 이용해 읽기, 쓰기 권한을 확인합니다.(VM_PROT_WRITE | VM_PROT_READ)
- 읽기, 쓰기 권한이 있는 서브맵을 대상으로 메모리 검색을 진행합니다.
int findWriteableRegions()
int findWriteableRegions(){ vm_size_t size; vm_address_t address; natural_t nesting_depth; mach_msg_type_number_t infoCnt; regionList.clear(); size = 0; address = 0; struct vm_region_submap_info info; infoCnt = VM_REGION_SUBMAP_INFO_COUNT; for (; !vm_region_recurse(target_task,&address,&size,&nesting_depth,(vm_region_recurse_info_t)&info,&infoCnt);) { if (info.is_submap) { ++nesting_depth; }else{ if ((info.protection & (VM_PROT_WRITE | VM_PROT_READ)) == 3 && (info.max_protection & (VM_PROT_WRITE | VM_PROT_READ)) == 3) { regionStruct.startAddr = address; regionStruct.endAddr = size + address; regionStruct.size = size; regionList.push_back(regionStruct); printf("region: %016x-%016x\n",regionStruct.startAddr,regionStruct.endAddr); } address += size; } } return 1; }
Memory read - vm_read_overwrite(), vm_read()
해당 함수들은 지정된 대상 작업의 주소 공간 범위를 읽어 들입니다.
SYNOPSIS - vm_read_overwrite()
vm_read_overwrite() 함수 개요는 다음과 같습니다.
- 1번째 인자 값에는 task_for_pid()함수로 부터 전달받은 작업 포트를 전달 합니다.
- 2번째 인자 값에는 읽기를 시작할 메모리 영역의 주소를 전달 합니다.
- 3번째 인자 값에는 읽을 바이트 수를 전달 합니다.
4번째 인자 값에는 읽어 들인 메모리 영역의 값을 저장 할 공간을 전달합니다.
5번째 인자 값에는 읽어 들인 메모리 영역의 크기를 저장 할 공간을 전달합니다.
SYNOPSIS - vm_read_overwrite()
#include <mach/mach.h> kern_return_t vm_read_overwrite( vm_map_t map, vm_address_t address, vm_size_t size, vm_address_t data, vm_size_t *data_size);
- vm_read 함수는 동적으로 할당된 바이트 배열의 데이터를 반환합니다.
- vm_read_overwrite 함수는 데이터를 호출자가 지정한 퍼버(data_in 인자)에 저장합니다.
SYNOPSIS - vm_read()
kern_return_t vm_read( vm_task_t target_task, vm_address_t address, vm_size_t size, size data_out, target_task data_count);
vm_read_overwrite and vm_read
Example
- 다음과 같은 코드를 이용해 프로세스의 메모리 값을 추출 할 수 있습니다.
- vm_read_overwrite() 함수를 이용해 전달된 프로세스의 메모리 값을 읽어 옵니다.
- 읽을 영역은 startAddress 에서 endAddress 까지 읽어 옵니다.
- 읽은 메모리 값은 4096byte 씩 buffer에 저장됩니다.
- vm_read_overwrite() 함수를 이용해 전달된 프로세스의 메모리 값을 읽어 옵니다.
void getValueArea(vm_address_t startAddress,vm_address_t endAddress, void* buffer,long number)
void getValueArea(vm_address_t startAddress,vm_address_t endAddress, void* buffer,long number){ kern_return_t result; long readArea = 0; vm_size_t outsize; while(endAddress > startAddress){ if (readArea != (startAddress & 0xFFFFFFFFFFFFF000)) { readArea = startAddress & 0xFFFFFFFFFFFFF000; outsize = 0; result = vm_read_overwrite(target_task, readArea, 4096, (vm_address_t)buffer, &outsize); if(!outsize){ printf("stardAddress 64 : %lx, %lx\n",startAddress,endAddress); fprintf(stderr,"vm_read_overwrite failed: %lu\n",startAddress & 0xFFFFFFFFFFFFF000); } } if (result == KERN_SUCCESS) { for (int i=0; i<512; i++) { memInfoStruct.address = startAddress; memInfoStruct.value = *(long*)((char*)buffer + ((startAddress - (startAddress & 0xFFFFFFFFFFFFF000)) & 0xFFFFFFFFFFFFFFF8)); memDataList.push_back(memInfoStruct); startAddress += 8; } }else{ startAddress += 8; } } }
Memory write - vm_write()
- 해당 함수는 대상 프로세스의 메모리 영역에 지정된 주소에 데이터를 씁니다.
SYNOPSIS - vm_write()
- vm_write() 함수 개요는 다음과 같습니다.
- 1번째 인자 값에는 task_for_pid()함수로 부터 전달받은 작업 포트를 전달 합니다.
- 2번째 인자 값에는 데이터를 쓸 메모리 주소를 전달합니다.
- 3번째 인자 값에는 메모리에 쓸 데이터가 저장된 메모리 주소를 전달합니다.
- 4번째 인자 값에는 데이터의 크기를 전달 합니다.
SYNOPSIS - vm_write()
#include <mach/mach.h> kern_return_t vm_write( vm_map_t map, vm_address_t address, pointer_t data, mach_msg_type_number_t size)
Example
void MemoryWrite(vm_address_t address,long value)
void MemoryWrite(vm_address_t address,long value){ vm_size_t outsize; vm_address_t startAddress = 0; unsigned int data; vm_read_overwrite(target_task, startAddress & 0xFFFFFFFFFFFFFFF8, 8, (vm_address_t)&data, &outsize); if (!outsize) { printf("vm_read_overwrite(%11lx) failed 1.",startAddress & 0xFFFFFFFFFFFFFFF8); } unsigned int write_data; write_data = value; kern_return_t kr; kr = vm_write(target_task, address, (vm_address_t)&write_data, 8); if(kr){ printf("Fail %x\n", kr); }else{ printf("Sucess!\n"); } }
Memory fuzzing
- 치트 툴에서 제공하는 Memory fuzz 기능은 다음과 같습니다.
- 값이 이전과 같음
- 값이 이전과 같지 않음
- 값이 이전보다 증가
- 값이 이전보다 감소
- 해당 기능들은 다음과 같이 처리 됩니다.
- 우선 Fuzz기능을 실행하면 대상 프로세스에서 사용중인 모든 메모리 값을 메모리 영역 또는 파일에 저장합니다.
- 사용자가 치트 기능을 선택하면, 치트 툴은 앞에서 저장해둔 정보를 이용하여 값을 비교합니다.
- 사용자가 입력한 조건에 만족하는 값만 메모리 영역 또는 파일에 저장합니다.
- 이러한 과정을 반복합니다.