fridump와 foremost로 iOS 메모리 분석하기

shintwl·2024년 6월 4일
0
post-thumbnail

Requirements

Fridump3

pip install frida
pip install frida-tools

Fridump3를 깃허브에서 다운/클론 받아 로컬에 저장합니다

foremost

foremost를 homebrew 등의 방법으로 zsh에서 실행할 수 있게 설정합니다.

탈옥 (iOS 실제 기기로 하는 경우)

checkra1n (unc0ver, dopamine 등을 사용하셔도 무방합니다)

C-Lightning 케이블로 DFU 모드 진입이 불가능하다는 이슈가 있습니다
A-Lightning 케이블로 진행해주세요

참고 사항

웹뷰 혹은 사파리 브라우저의 내용을 보기 위해서는 com.apple.WebKit.WebContent를 덤프 떠야 합니다

12.5.7에서는 성공하는 것을 확인하였으나 14.2, 15.0에서는 injection 오류 혹은 unexpected early end-of-stream 가 발생합니다.
애플 측에서 막아 놓은 것으로 추정하고 있습니다.
(https://github.com/frida/frida/issues/1865)

Full Source Code

파이썬으로 작성되었습니다

foremost를 실행하면서 temp 디렉토리에서 옮기는 이유는 와일드카드(/*) 형식으로 파일을 전체 지정하면 output 디렉토리가 제대로 지정되지 않아서 파일 하나하나 읽어와야 되기 때문입니다
foremost는 output 디렉토리가 비어있지 않으면 에러가 발생합니다

fridump3.py 가 저장된 경로를 수정해주세요

import os
import subprocess
import argparse
from datetime import datetime
import sys
import shutil

def dumpUSBMemory(pid, outputDirectory):
    options = [
        '-u',
        '-o', outputDirectory,
        '-s',
    ]

    dumpMemory(pid, options)

def dumpPCMemory(pid, outputDirectory):
    options = [
        '-o', outputDirectory,
        '-s',
    ]

    dumpMemory(pid, options)

def dumpMemory(pid, options):
    print("\n<Dump out memory RUNNING>\n")
    
    command = [
        sys.executable, '-u',
        './fridump3-master/fridump3.py' # ⛑️ 경로체크!!
        ] + options + [pid]

    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

    for line in process.stdout:
        print(line, end='')

    for line in process.stderr:
        print(line, end='')

    return_code = process.wait()

    if return_code != 0:
        print(f"Fridump3 exited with error code {return_code}")
    
    print("\n<Dump out memory DONE>\n")

def extractFilesFromBinary(binaryFileDirectory):
    print("\n<Extract files from dump data RUNNING>\n")

    finalOutputDirectory = f'{binaryFileDirectory}/foremost_output'
    tempOutputDirectory = f'{binaryFileDirectory}/foremost_temp_output'

    fileNames = os.listdir(binaryFileDirectory)

    os.mkdir(finalOutputDirectory)

    for fileName in fileNames:
        command = [
            'foremost',
            '-i', f'{binaryFileDirectory}/{fileName}',
            '-o', tempOutputDirectory,
        ]
        process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

        for line in process.stdout:
            print(line, end='')

        for line in process.stderr:
            print(line, end='')

        return_code = process.wait()

        if return_code != 0:
            print(f"foremost exited with error code: {return_code}, file name: {fileName}")

        filePathsInTempDirectory = filePathsInDirectory(tempOutputDirectory)
        for filePath in filePathsInTempDirectory:
            if 'audit.txt' in filePath: continue
            moveFile(filePath, finalOutputDirectory)

        shutil.rmtree(tempOutputDirectory)
    
    print("\n<Extract files from dump data DONE>\n")

def moveFile(path, destination):
    fileName = os.path.basename(path)
    shutil.move(path, f'{destination}/{fileName}')

def filePathsInDirectory(path):
    filePaths = []
    for entry in os.listdir(path):
        fullPath = os.path.join(path, entry)
        if os.path.isdir(fullPath):
            filePaths = filePaths + filePathsInDirectory(fullPath)
        else:
            filePaths.append(fullPath)
    return filePaths

def getPIDsFromUSB(appName):
    return getPIDs(appName, ['-U'])

def getPIDsFromPC(appName):
    return getPIDs(appName, [])

def getPIDs(appName, options):
    proc1 = subprocess.Popen(['frida-ps'] + options, stdout=subprocess.PIPE)
    proc2 = subprocess.Popen(['grep', appName], stdin=proc1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    proc1.stdout.close()
    out, _ = proc2.communicate()
    pids = list(map(lambda x: x.split()[0], out.decode().splitlines()))
    return pids
    
def getFormattedDate():
    now = datetime.now()
    return now.strftime("%Y%m%d%H%M%S")

def parseArguments():
    parser = argparse.ArgumentParser(description="Process some arguments.")
    parser.add_argument('args', nargs='+', help='Arguments passed from the shell script')
    
    # Parse the arguments
    args = parser.parse_args()
    
    # Print the arguments
    print("Arguments received from shell script:", args.args)
    return args.args

# ⛑️⛑️⛑️⛑️ MAIN ⛑️⛑️⛑️⛑️
if __name__ == "__main__":
    [appNameToDump, dumpOutputBaseDirectory, platform] = parseArguments()

    if platform not in ["usb", "pc"]:
        print("platform is only allowed for usb & pc. Case sensitive")
        exit()

    if os.path.isdir(dumpOutputBaseDirectory) == False:
        print(dumpOutputBaseDirectory," is not directory")
        exit()

    print("appNameToDump: ", appNameToDump)
    print("dumpOutputBaseDirectory: ", dumpOutputBaseDirectory)
    print("platform: ", platform)

    if platform == "usb":
        pids = getPIDsFromUSB(appNameToDump)
    elif platform == "pc":
        pids = getPIDsFromPC(appNameToDump)

    if len(pids) == 0:
        print(f"NO pid that matches to {appNameToDump}")

    for pid in pids:
        print("PID for {0}: {1}".format(appNameToDump, pid))

        dumpOutputDirectory = f"{dumpOutputBaseDirectory}/{appNameToDump}_{getFormattedDate()}_{pid}_dumpOutput"

        os.mkdir(dumpOutputDirectory)

        if platform == "usb":
            dumpUSBMemory(pid, dumpOutputDirectory)
        elif platform == "pc":
            dumpPCMemory(pid, dumpOutputDirectory)

        extractFilesFromBinary(dumpOutputDirectory)

    print("### PROCESS DONE ###")

    exit()

사용법

python {파일이름}.py {앱이름} {덤프파일_저장할_디렉토리} usb
python {파일이름}.py {앱이름} {덤프파일_저장할_디렉토리} pc

0개의 댓글