Frida 入门

Frida文档:Welcome | Frida • A world-class dynamic instrumentation framework

0x01快速开始

## 用pip安装
pip3 install frida-tools
## 快速体验,hook一下微信,可以在微信首发消息看一下输出
frida-trace -i "recv*" -i "read*" 微信
## 下面是输出
...
...
Started tracing 36 functions. Press Ctrl+C to stop.
           /* TID 0x103 */
 31910 ms  read(d=0xb6, buf=0x6000017cf2c0, nbyte=0xa, offset=0xffffffffffffffff)
           /* TID 0x2c02b */
 31917 ms  read(d=0xee, buf=0x700002e546d4, nbyte=0x4, offset=0x0)
           /* TID 0x38593 */
 31921 ms  read(d=0x9, buf=0x700002ccb170, nbyte=0x400, offset=0x10000000100)
 31921 ms  read(d=0x9, buf=0x700002ccb170, nbyte=0x400, offset=0x7ff81ed153ba)
           /* TID 0xdd67 */
 31953 ms  recv(socket=0xee, buffer=0x7fd604856000, length=0x2000, flags=0x0)
 31953 ms     | recvfrom(socket=0xee, buffer=0x7fd604856000, length=0x2000, flags=0x0, address=0x0, address_len=0x0)
 ...
 ...

0x02官方示例教程

Functions

Functions | Frida • A world-class dynamic instrumentation framework

  • 简单示例

    • 编写和编译测试文件
#include <stdio.h>
#include <unistd.h>

void
f (int n)
{
  printf ("Number: %d\n", n);
}

int
main (int argc,
      char * argv[])
{
  int i = 0;

  printf ("f() is at %p\n", f);

  while (1)
  {
    f (i++);
    sleep (1);
  }
}
gcc -Wall hello.c -o hello
-   hook方法内容
import frida
import sys

session = frida.attach("hello")
script = session.create_script("""
Interceptor.attach(ptr("%s"), {
    onEnter(args) {
        send(args[0].toInt32());
    }
});
""" % int(sys.argv[1], 16))
def on_message(message,data):
    print(message)
script.on("message",on_message)
script.load()
sys.stdin.read()
-   修改参数值
import frida
import sys

session = frida.attach("hello")
script = session.create_script("""
Interceptor.attach(ptr("%s"), {
    onEnter(args) {
        // send(args[0].toInt32());
        args[0] = ptr(10086)
    }
});
""" % int(sys.argv[1], 16))
# def on_message(message,data):
#     print(message)
# script.on("message",on_message)
script.load()
sys.stdin.read()
-   通过js调用程序内的函数
import frida
import sys

session = frida.attach("hello")
script = session.create_script("""
const f = new NativeFunction(ptr(%s),"void",["int"]);
f(10086);
f(10087);
f(10088);
""" % int(sys.argv[1],16))
script.load()
sys.stdin.read()
  • 注入字符串并调用方法

    • 创建测试文件
#include <stdio.h>
#include <unistd.h>

int
f (const char * s)
{
  printf ("String: %s\n", s);
  return 0;
}

int
main (int argc,
      char * argv[])
{
  const char * s = "Testing!";

  printf ("f() is at %p\n", f);
  printf ("s is at %p\n", s);

  while (1)
  {
    f (s);
    sleep (1);
  }
}
gcc -Wall hi.c -o hi
-   编写字符串hook和注入代码
import frida
import sys

session = frida.attach('hi')
script = session.create_script("""
const st = Memory.allocUtf8String("TESTMEPLZ!");
const f = new NativeFunction(ptr("%s"),'int',['pointer']);
f(st);
""" % int(sys.argv[1],16))
def on_message(message,data):
    print(message)
script.on("message",on_message)
script.load()
sys.stdin.read()
-   创建编译socket链接示例
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

int
main (int argc,
      char * argv[])
{
  int sock_fd, i, n;
  struct sockaddr_in serv_addr;
  unsigned char * b;
  const char * message;
  char recv_buf[1024];

  if (argc != 2)
  {
    fprintf (stderr, "Usage: %s <ip of server>\n", argv[0]);
    return 1;
  }

  printf ("connect() is at: %p\n", connect);

  if ((sock_fd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
  {
    perror ("Unable to create socket");
    return 1;
  }

  bzero (&serv_addr, sizeof (serv_addr));

  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons (5000);

  if (inet_pton (AF_INET, argv[1], &serv_addr.sin_addr) <= 0)
  {
    fprintf (stderr, "Unable to parse IP address\n");
    return 1;
  }
  printf ("\nHere's the serv_addr buffer:\n");
  b = (unsigned char *) &serv_addr;
  for (i = 0; i != sizeof (serv_addr); i++)
    printf ("%s%02x", (i != 0) ? " " : "", b[i]);

  printf ("\n\nPress ENTER key to Continue\n");
  while (getchar () == EOF && ferror (stdin) && errno == EINTR)
    ;

  if (connect (sock_fd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0)
  {
    perror ("Unable to connect");
    return 1;
  }

  message = "Hello there!";
  if (send (sock_fd, message, strlen (message), 0) < 0)
  {
    perror ("Unable to send");
    return 1;
  }

  while (1)
  {
    n = recv (sock_fd, recv_buf, sizeof (recv_buf) - 1, 0);
    if (n == -1 && errno == EINTR)
      continue;
    else if (n <= 0)
      break;
    recv_buf[n] = 0;

    fputs (recv_buf, stdout);
  }

  if (n < 0)
  {
    perror ("Unable to read");
  }

  return 0;
}
gcc -Wall client.c -o client

-   编写hook脚本,修改连接地址
import frida
import sys

session = frida.attach("client")
script = session.create_script("""
send('Allocating memory and writing bytes...')
const st = Memory.alloc(16);
// 具体的字节需要自己运行一边client之后,再进行修改,不要和官网一致
st.writeByteArray([0x00,0x02,0x13,0x89,0x7f,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])
Interceptor.attach(Module.getExportByName(null,'connect'),{
    onEnter(args){
        send('Injecting malicious byte array:');
        args[1] = st;
    }
})
""")
def on_message(message,data):
    if message['type'] == 'error':
        print("[!] " + message['stack'])
    elif message['type'] == 'send':
        print("[i] " + message['payload'])
    else:
        print(message)
script.on('message',on_message)
script.load()
sys.stdin.read()

Messages

Messages | Frida • A world-class dynamic instrumentation framework

  • 简单示例
import frida
import sys

session = frida.attach("hello")
script = session.create_script("send('123');send(a)")

def on_message(message,data):
    print(message)

script.on("message",on_message)
script.load()
sys.stdin.read()
  • recv 异步和等待调用
import frida
import sys

session = frida.attach('hello')
script = session.create_script("""
    recv('poke',function onMessage(pokeMessage){
        send('pokeBack');
    });
""")
def on_message(message,data):
    print(message)

script.on("message",on_message)
script.load()
script.post({"type":"poke"})
sys.stdin.read()
import frida
import sys

session = frida.attach('hello')
script = session.create_script("""
Interceptor.attach(ptr(%s),{
    onEnter(args){
        send(args[0].toString());
        const op = recv('input',value => {
            args[0] = ptr(value.payload);
        })
        op.wait();
    }
})
""" % int(sys.argv[1],16))

def on_message(message,data):
    print(message)
    val = int(message.get("payload"),16)
    script.post({"type":"input","payload":str(val * 2)})

script.on("message",on_message)
script.load()
sys.stdin.read()

0x03 知识点

  • MacOS下的frida脚本示例
import frida
import sys

def on_message(message, data):
    print("[{}] => {}".format(message, data))

def main(target_process):
    session = frida.attach(target_process)

    script = session.create_script("""
        // macos下是针对Object-C进行处理,可以通过ObjC.classes.类['- 方法']进行获取
        // 如果不知道具体的方法名称可以通过ObjC.classes.类.$methods列出当前类下所有的方法名
        const appWillFinishLaunching = ObjC.classes.NSApplicationDelegate['- applicationWillFinishLaunching:'];
        Interceptor.attach(appWillFinishLaunching.implementation, {
          onEnter(args) {
            // As this is an Objective-C method, the arguments are as follows:
            // 0. 'self'
            // 1. The selector (applicationWillFinishLaunching:)
            // 2. The first argument to this method
            const notification = new ObjC.Object(args[2]);

            // Convert it to a JS string and log it
            const notificationStr = notification.absoluteString().toString();
            console.log('Will finish launching with notification: ' + notificationStr);
          },
          onLeave(retval) {
            // 替换返回值
            //retval.replace(0x01);
          }
        });
    """)
    script.on("message", on_message)
    script.load()
    print("[!] Ctrl+D or Ctrl+Z to detach from instrumented program.\n\n")
    sys.stdin.read()
    session.detach()


if __name__ == "__main__":
    main("Safari")
  • Hopper

    Hopper (hopperapp.com)

    • Hopper Disassembler是一款强大的反汇编程序,字符串处理效果比较好,免费版不能进行修改,导出文件之类的,每次打开最多维持30分钟,不过够用了
  • class-dump

    class-dump - Steve Nygard

    • class-dump 可以导出mac-app(Object-C)的头文件,可以查看类和方法的定义,辅助完成hook,使用需要将解压目录添加到Path中

0x04 Typora Crack

【Mac】记一次对付费Markdown软件Typora 1.0的逆向 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

  • 确定提示字符串:未激活

  • 应用程序显示包内容,查找汉化字符串对应的内容:UNREGISTERED

  • 用Hopper打开应用程序包→MACOS中的可执行文件

  • 然后在左侧边栏的str中搜索

  • hook LicenseManager 的 hasLicense方法即可。

import frida
import time

session = None

def hook(target):
  while True:
    try:
      session = frida.attach(target)
      break
    except Exception as ex:
      time.sleep(0.5)
  print("目标进程Hook成功!")
  script = session.create_script("""
const hook_hasLicense = eval('ObjC.classes.LicenseManager["- hasLicense"]');
Interceptor.attach(hook_hasLicense.implementation,{
    onEnter(args) {
  },
  onLeave(retval) {
    retval.replace(0x01);
    send("hasLicense replace.")
  }
});""")

  def on_message(message,data):
    if message["type"] == "send":
      print(message["payload"])
    print(message)
      
  script.on("message",on_message)
  script.load()
  while True:
    time.sleep(1)
    if session.is_detached:
      break

while True:
  hook('Typora')

0x05 参考链接