frdia源码,分析的没什么太大的难度,分析过程中,主要是对vala语言的一些库,和语言不是很熟悉,和对一些编程语言的高级设计理念不是很熟悉,frida本身源码写的还是很不错的,但是可能是复用性太高了,各种代码都黏在在一起,实现的功能又比较多,开始分析起来比较麻烦。我分析的是15.1.3版本。
c 和vala 用valac编译到一起
frdia使用c 和vala写的,配合meson编译系统。假如vala 想要引用c库,frida中的方法是*.vapi的方式。
hellotest.vala
helloworld.c
helloworld.vapi
frida还专门写了个工具,用来自动生成文件 tools\resource-compiler
vala web,soup库
直接看代码吧
vala dbus
gdbus-demo-client.vala
gdbus-demo-server.vala
1、[DBus (name = "org.example.Demo")]
2、获取一个DBusConnection对象
3、register_object
这两个进程就可以互相调用了
meson编译工具
这里不讲太多,只讲custom_target函数,不懂的看文档吧
https://mesonbuild.com/Getting-meson_zh.html
meson.build
writec.py
meson.build的custom_target会直接writec.py文件,command后面的数组,第一个是命令,后面的是参数,@OUTPUT@ 表示output ,最后会在build目录中生成一个retval.c文件
vala语法相关
不懂vala语法的可以读一读 https://wiki.gnome.org/Projects/Vala
yeid 看这个 http://blog.rastersoft.com/?p=1013
源码结构
frida-clr 好像不太重要的样子,没怎么看
frida-core 大部分主要源码都在这里
portal
https://frida.re/news/2021/07/18/frida-15-0-released/
inject
frdia-inject ,可以单独执行,在相应的平台上,frida中下载的 frida-inject-15.1.10-android-arm.xz,可以不必在pc上执行,可以在端执行
src
针对不同的平台实现了不同的进程注入,通讯等服务(不同平台实现不同的 help 和 inject)
server
frida-server 入口函数,初始化环境,调用各种服务
frida.vala
这个文件,好像是有些设备管理,但是编译core也需要他。
control-service.vala
入口服务,对接外部各种功能的接口,记住这个类,挺重要的
frida-gum 这个是frida 和js交互的库,依赖v8或者quickjs,在js的库的基础上有封装了一套自动的东西,
frida-node 这个好像也不太重要的样子
frida-python 这个是python 绑定到frida上的库
frida-qml 这个应该很多人都不知道是干嘛的,这个其实qt的qml,qt有一套可以用js写界面的技术,叫做qml,frida实现了这个借口,可以用这个做界面,做界面推荐用这个,速度挺不错的
frida-swift 这个不太清楚
frida-tools 这个就是安装到电脑上的工具,用来连接frida-server
frida构成
frida-server
在手机上启动的server,和pc端连接,启动frida-helper,与frida-helper保持dbus连接
frida-server启动的时候,会在/data/local/tmp想生成一个目录,释放Frida-helper 和frida-agent
frida-helper
frida-helper保持dbus连接,主要负责进程附加,so文件注入
通过
frida-agent-<arch>.so
注入到进程中的文件,内含v8,执行各种hook操作等等
frida-gadget
估计和frida-agent差不多,不过,明显多了和pc端连接的功能,这个没具体分析
frida启动,和pc通讯分析
server\server.vala
src\control-service.vala
frida-core\lib\base\socket.vala
```
public async void start (Cancellable? cancellable) throws Error, IOError {
pc端会访问27042端口,去连接frida-server,我们接着分析一下 on_websocket_opened
WebService的incoming信号在WebService初始化函数中注册了,调用了on_server_connection
这两个调用位置都会去注册dbus函数,我们分析setup_control_channel
pc端先通过web连接到frida-server的端口,然后frida通过web的数据流,注册了一个dbus服务,供pc端调用,这样就实现了远程通讯
看一下ControlChannel,调用的函数
这个函数parent就是ControlService,最终还是调用的ControlService的host_session,这个host_session,我们后面说
control-service 跨进程attach,调用frida-agent-main
前面我们分析了,pc端通过dbus可以远程调用ControlChannel,而ControlChannel,实际上调用的是ControlService。
frida\frida-core\src\control-service.vala
不同的平台实现了不同的host_session,通过BaseDBusHostSession来抽象实现相同功能,不同架构实现不同功能
我们分析linux平台,在LinuxHostSession初始化函数中,释放了frida-agent文件
前面的host_session.attach,调用的是BaseDBusHostSession的attach
BaseDBusHostSession类 establish 函数 401行,调用 perform_attach_to,调用的是各个平台的实现的perform_attach_to
src\linux\linux-host-session.vala
调用的是linjector的inject_library_file和inject_library_resource函数
linjector 也是分架构的,这里不细讲了,这里最终调用的是LinuxHelperProcess 的inject_library_file
src\linux\frida-helper-process.vala
frida-server 释放文件,启动frida-helper
frida-help 调用inject_library_file分析
start函数,这里注册了Frida.ObjectPath.HELPER,这个服务,供frida-server调用
所以inject_library_file调用的是 LinuxHelperService 的 inject_library_file
这个backend是LinuxHelperBackend
最后调用的是_do_inject,这个_do_inject是c函数,从外部中导入到vala中的
看到这里也不容易,可能你对源码分析不关心,所以,我针对上面的源码分析的结果,讲一下如何防御sdk,检测frida。
frida是使用vala编写的,并且内置了v8(或quickjs),也就是说,也就是说我们检测frida,也可以检测vala特征,和v8特征
v8搜索显示
还可以搜索quickjs 这个引擎的符号或者字符串,如果想要完全改掉这些字符,这个真的有点麻烦了。
2、检测frida的线程
vala有个Gmain,也会开启这个线程的,包括js引擎,也是有单独线程的,另外进程通讯用的是dbus,也是有线程的,而且由于他们都是现场的第三方库,创建线程的名字也好找。
遍历 /porc/self/task/*/status
gum js 引擎的线程名字: Name: gum-js-loop
vala 引擎的线程名字: Name: gmain
dbus 线程名字: Name: gdbus
关于frida检测这个,我只是提供了一个思路,关于v8 vala 相关的特征还有很多,可以自行寻找。
using MyHello;
namespace Frida {
public
class
LinuxHelperbackend {
public static void main() {
myplusone(
1
);
int
a
=
add123(
5
,
6
);
int
c
=
add(a,
10
);
print
(
"fefew = %d"
,c);
}
public static
int
add(
int
a,
int
b){
return
a
+
b;
}
}
}
using MyHello;
namespace Frida {
public
class
LinuxHelperbackend {
public static void main() {
myplusone(
1
);
int
a
=
add123(
5
,
6
);
int
c
=
add(a,
10
);
print
(
"fefew = %d"
,c);
}
public static
int
add(
int
a,
int
b){
return
a
+
b;
}
}
}
void myplusone(
int
a)
{
printf(
"%d"
,a
+
1
);
}
int
add(
int
a,
int
b){
return
a
+
b;
}
void myplusone(
int
a)
{
printf(
"%d"
,a
+
1
);
}
int
add(
int
a,
int
b){
return
a
+
b;
}
[CCode (cheader_filename
=
"helloworld.h"
,lower_case_cprefix
=
"")]
namespace MyHello {
public void myplusone(
int
a);
[CCode (cname
=
"add"
)]
public
int
add123(
int
a,
int
b);
}
[CCode (cheader_filename
=
"helloworld.h"
,lower_case_cprefix
=
"")]
namespace MyHello {
public void myplusone(
int
a);
[CCode (cname
=
"add"
)]
public
int
add123(
int
a,
int
b);
}
gcc
-
c helloworld.c
ar
-
rc libhelloworld.a helloworld.o
valac hellotest.vala
-
-
pkg helloworld
-
-
vapidir .
-
X
-
I.
-
X
-
L.
-
X
-
lhelloworld
gcc
-
c helloworld.c
ar
-
rc libhelloworld.a helloworld.o
valac hellotest.vala
-
-
pkg helloworld
-
-
vapidir .
-
X
-
I.
-
X
-
L.
-
X
-
lhelloworld
public
class
NoodleSoupServer : Soup.Server {
private
int
access_counter
=
0
;
public NoodleSoupServer () {
assert
(this !
=
null);
/
/
Links:
/
/
http:
/
/
localhost:
8088
/
*
this.add_handler (null, default_handler);
/
/
添加一个连接以后的处理函数
}
private static void default_handler (Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
unowned NoodleSoupServer
self
=
server as NoodleSoupServer;
uint
id
=
self
.access_counter
+
+
;
print
(
"Default handler start (%u)\n"
,
id
);
Timeout.add_seconds (
0
, ()
=
> {
string html_head
=
"<head><title>Index</title></head>"
;
string html_body
=
"<body><h1>Index:</h1></body>"
;
msg.set_response (
"text/html"
, Soup.MemoryUse.COPY,
"<html>%s%s</html>"
.printf (html_head, html_body).data);
/
/
Resumes HTTP I
/
O on msg:
self
.unpause_message (msg);
print
(
"Default handler end (%u)\n"
,
id
);
return
false;
}, Priority.DEFAULT);
/
/
Pauses HTTP I
/
O on msg:
self
.pause_message (msg);
}
public static
int
main (string[] args) {
try
{
int
port
=
8088
;
MainLoop loop
=
new MainLoop ();
NoodleSoupServer server
=
new NoodleSoupServer ();
server.listen_all (port,
0
);
/
/
监听web连接的端口
loop.run ();
} catch (Error e) {
print
(
"Error: %s\n"
, e.message);
}
return
0
;
}
}
public
class
NoodleSoupServer : Soup.Server {
private
int
access_counter
=
0
;
public NoodleSoupServer () {
assert
(this !
=
null);
/
/
Links:
/
/
http:
/
/
localhost:
8088
/
*
this.add_handler (null, default_handler);
/
/
添加一个连接以后的处理函数
}
private static void default_handler (Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
unowned NoodleSoupServer
self
=
server as NoodleSoupServer;
uint
id
=
self
.access_counter
+
+
;
print
(
"Default handler start (%u)\n"
,
id
);
Timeout.add_seconds (
0
, ()
=
> {
string html_head
=
"<head><title>Index</title></head>"
;
string html_body
=
"<body><h1>Index:</h1></body>"
;
msg.set_response (
"text/html"
, Soup.MemoryUse.COPY,
"<html>%s%s</html>"
.printf (html_head, html_body).data);
/
/
Resumes HTTP I
/
O on msg:
self
.unpause_message (msg);
print
(
"Default handler end (%u)\n"
,
id
);
return
false;
}, Priority.DEFAULT);
/
/
Pauses HTTP I
/
O on msg:
self
.pause_message (msg);
}
public static
int
main (string[] args) {
try
{
int
port
=
8088
;
MainLoop loop
=
new MainLoop ();
NoodleSoupServer server
=
new NoodleSoupServer ();
server.listen_all (port,
0
);
/
/
监听web连接的端口
loop.run ();
} catch (Error e) {
print
(
"Error: %s\n"
, e.message);
}
return
0
;
}
}
[DBus (name
=
"org.example.Demo"
)]
interface Demo :
Object
{
public abstract
int
ping (string msg) throws IOError;
public abstract
int
ping_with_sender (string msg) throws IOError;
public abstract
int
ping_with_signal (string msg) throws IOError;
public signal void pong (
int
count, string msg);
}
void main () {
/
*
Needed only
if
your client
is
listening to signals; you can omit it otherwise
*
/
var loop
=
new MainLoop();
/
*
Important: keep demo variable out of
try
/
catch scope
not
lose signals!
*
/
Demo demo
=
null;
try
{
demo
=
Bus.get_proxy_sync (BusType.SESSION,
"org.example.Demo"
,
"/org/example/demo"
);
/
*
Connecting to signal pong!
*
/
demo.pong.connect((c, m)
=
> {
stdout.printf (
"Got pong %d for msg '%s'\n"
, c, m);
loop.quit ();
});
int
reply
=
demo.ping (
"Hello from Vala"
);
stdout.printf (
"%d\n"
, reply);
reply
=
demo.ping_with_sender (
"Hello from Vala with sender"
);
stdout.printf (
"%d\n"
, reply);
reply
=
demo.ping_with_signal (
"Hello from Vala with signal"
);
stdout.printf (
"%d\n"
, reply);
} catch (IOError e) {
stderr.printf (
"%s\n"
, e.message);
}
loop.run();
}
[DBus (name
=
"org.example.Demo"
)]
interface Demo :
Object
{
public abstract
int
ping (string msg) throws IOError;
public abstract
int
ping_with_sender (string msg) throws IOError;
public abstract
int
ping_with_signal (string msg) throws IOError;
public signal void pong (
int
count, string msg);
}
void main () {
/
*
Needed only
if
your client
is
listening to signals; you can omit it otherwise
*
/
var loop
=
new MainLoop();
/
*
Important: keep demo variable out of
try
/
catch scope
not
lose signals!
*
/
Demo demo
=
null;
try
{
demo
=
Bus.get_proxy_sync (BusType.SESSION,
"org.example.Demo"
,
"/org/example/demo"
);
/
*
Connecting to signal pong!
*
/
demo.pong.connect((c, m)
=
> {
stdout.printf (
"Got pong %d for msg '%s'\n"
, c, m);
loop.quit ();
});
int
reply
=
demo.ping (
"Hello from Vala"
);
stdout.printf (
"%d\n"
, reply);
reply
=
demo.ping_with_sender (
"Hello from Vala with sender"
);
stdout.printf (
"%d\n"
, reply);
reply
=
demo.ping_with_signal (
"Hello from Vala with signal"
);
stdout.printf (
"%d\n"
, reply);
} catch (IOError e) {
stderr.printf (
"%s\n"
, e.message);
}
loop.run();
}
[DBus (name
=
"org.example.Demo"
)]
public
class
DemoServer :
Object
{
private
int
counter;
public
int
ping (string msg) {
stdout.printf (
"%s\n"
, msg);
return
counter
+
+
;
}
public
int
ping_with_signal (string msg) {
stdout.printf (
"%s\n"
, msg);
pong(counter, msg);
return
counter
+
+
;
}
/
*
Including
any
parameter of
type
GLib.BusName won't be added to the
interface
and
will
return
the dbus sender name (who
is
calling the method)
*
/
public
int
ping_with_sender (string msg, GLib.BusName sender) {
stdout.printf (
"%s, from: %s\n"
, msg, sender);
return
counter
+
+
;
}
public void ping_error () throws Error {
throw new DemoError.SOME_ERROR (
"There was an error!"
);
}
public signal void pong (
int
count, string msg);
}
[DBus (name
=
"org.example.DemoError"
)]
public errordomain DemoError
{
SOME_ERROR
}
void on_bus_aquired (DBusConnection conn) {
try
{
conn.register_object (
"/org/example/demo"
, new DemoServer ());
} catch (IOError e) {
stderr.printf (
"Could not register service\n"
);
}
}
void main () {
Bus.own_name (BusType.SESSION,
"org.example.Demo"
, BusNameOwnerFlags.NONE,
on_bus_aquired,
()
=
> {},
()
=
> stderr.printf (
"Could not aquire name\n"
));
new MainLoop ().run ();
}
[DBus (name
=
"org.example.Demo"
)]
public
class
DemoServer :
Object
{
private
int
counter;
public
int
ping (string msg) {
stdout.printf (
"%s\n"
, msg);
return
counter
+
+
;
}
public
int
ping_with_signal (string msg) {
stdout.printf (
"%s\n"
, msg);
pong(counter, msg);
return
counter
+
+
;
}
/
*
Including
any
parameter of
type
GLib.BusName won't be added to the
interface
and
will
return
the dbus sender name (who
is
calling the method)
*
/
public
int
ping_with_sender (string msg, GLib.BusName sender) {
stdout.printf (
"%s, from: %s\n"
, msg, sender);
return
counter
+
+
;
}
public void ping_error () throws Error {
throw new DemoError.SOME_ERROR (
"There was an error!"
);
}
public signal void pong (
int
count, string msg);
}
[DBus (name
=
"org.example.DemoError"
)]
public errordomain DemoError
{
SOME_ERROR
}
void on_bus_aquired (DBusConnection conn) {
try
{
conn.register_object (
"/org/example/demo"
, new DemoServer ());
} catch (IOError e) {
stderr.printf (
"Could not register service\n"
);
}
}
void main () {
Bus.own_name (BusType.SESSION,
"org.example.Demo"
, BusNameOwnerFlags.NONE,
on_bus_aquired,
()
=
> {},
()
=
> stderr.printf (
"Could not aquire name\n"
));
new MainLoop ().run ();
}
writec
=
find_program(
'writec.py'
)
retval
=
custom_target(
'writec'
,
output :
'retval.c'
,
command : [writec,
'@OUTPUT@'
])
writec
=
find_program(
'writec.py'
)
retval
=
custom_target(
'writec'
,
output :
'retval.c'
,
command : [writec,
'@OUTPUT@'
])
import
sys
c
=
with
open
(sys.argv[
1
],
'w'
) as f:
f.write(c)
import
sys
c
=
with
open
(sys.argv[
1
],
'w'
) as f:
f.write(c)
main函数开始执行
return
run_application (endpoint_params, options, on_ready);
private static
int
run_application (EndpointParameters endpoint_params, ControlServiceOptions options, ReadyHandler on_ready) {
TemporaryDirectory.always_use ((directory !
=
null) ? directory : DEFAULT_DIRECTORY);
application
=
new Application (new ControlService (endpoint_params, options));
.....
....
return
application.run ();
}
application.run 函数
public
int
run () {
Idle.add (()
=
> {
start.begin ();
return
false;
});
exit_code
=
0
;
loop.run ();
return
exit_code;
}
start.begin ();调用
private async void start () {
try
{
yield
service.start (io_cancellable);
/
/
这里调用的是ControlService的start函数
} catch (GLib.Error e) {
if
(e
is
IOError.CANCELLED)
return
;
printerr (
"Unable to start: %s\n"
, e.message);
exit_code
=
5
;
loop.quit ();
return
;
}
Idle.add (()
=
> {
ready ();
return
false;
});
}
main函数开始执行
return
run_application (endpoint_params, options, on_ready);
private static
int
run_application (EndpointParameters endpoint_params, ControlServiceOptions options, ReadyHandler on_ready) {
TemporaryDirectory.always_use ((directory !
=
null) ? directory : DEFAULT_DIRECTORY);
application
=
new Application (new ControlService (endpoint_params, options));
.....
....
return
application.run ();
}
application.run 函数
public
int
run () {
Idle.add (()
=
> {
start.begin ();
return
false;
});
exit_code
=
0
;
loop.run ();
return
exit_code;
}
start.begin ();调用
private async void start () {
try
{
yield
service.start (io_cancellable);
/
/
这里调用的是ControlService的start函数
} catch (GLib.Error e) {
if
(e
is
IOError.CANCELLED)
return
;
printerr (
"Unable to start: %s\n"
, e.message);
exit_code
=
5
;
loop.quit ();
return
;
}
Idle.add (()
=
> {
ready ();
return
false;
});
}
public async void start (Cancellable? cancellable
=
null) throws Error, IOError {
if
(state !
=
STOPPED)
throw new Error.INVALID_OPERATION (
"Invalid operation"
);
state
=
STARTING;
try
{
yield
service.start (cancellable);
/
/
这里是WebService 调用start
....
}
public async void start (Cancellable? cancellable
=
null) throws Error, IOError {
if
(state !
=
STOPPED)
throw new Error.INVALID_OPERATION (
"Invalid operation"
);
state
=
STARTING;
try
{
yield
service.start (cancellable);
/
/
这里是WebService 调用start
....
}
............
var start_request
=
new Promise<SocketAddress> ();
schedule_on_dbus_thread (()
=
> {
/
/
闭包,异步调用handle_start_request 函数
handle_start_request.begin (start_request, cancellable);
return
false;
});
_listen_address
=
yield
start_request.future.wait_async (cancellable);
}
............
var start_request
=
new Promise<SocketAddress> ();
schedule_on_dbus_thread (()
=
> {
/
/
闭包,异步调用handle_start_request 函数
handle_start_request.begin (start_request, cancellable);
return
false;
});
_listen_address
=
yield
start_request.future.wait_async (cancellable);
}
private async void handle_start_request (Promise<SocketAddress> start_request, Cancellable? cancellable) {
try
{do_start
SocketAddress effective_address
=
yield
do_start (cancellable);
/
/
调用 do_start等待这个函数返回
.........
}
private async SocketAddress do_start (Cancellable? cancellable) throws Error, IOError {
server
=
(Soup.Server)
Object
.new (typeof (Soup.Server),
"tls-certificate"
, endpoint_params.certificate);
server.add_websocket_handler (
"/ws"
, endpoint_params.origin, null, on_websocket_opened);
/
/
这个应该是websocket打开时处理的
if
(endpoint_params.asset_root !
=
null)
server.add_handler (
"/"
, on_asset_request);
/
/
看前面demo,web处理函数,这个也是
SocketConnectable connectable
=
(flavor
=
=
CONTROL)
? parse_control_address (endpoint_params.address, endpoint_params.port)
/
/
设置端口
27042
: parse_cluster_address (endpoint_params.address, endpoint_params.port);
Soup.ServerListenOptions listen_options
=
(endpoint_params.certificate !
=
null)
? Soup.ServerListenOptions.HTTPS
:
0
;
SocketAddress? first_effective_address
=
null;
var enumerator
=
connectable.
enumerate
();
while
(true) {
SocketAddress? address;
try
{
address
=
yield
enumerator.next_async (io_cancellable);
} catch (GLib.Error e) {
throw new Error.NOT_SUPPORTED (
"%s"
, e.message);
}
if
(address
=
=
null)
break
;
SocketAddress? effective_address
=
null;
InetSocketAddress? inet_address
=
address as InetSocketAddress;
if
(inet_address !
=
null) {
uint16 start_port
=
inet_address.get_port ();
uint16 candidate_port
=
start_port;
do {
try
{
server.listen (inet_address, listen_options);
/
/
在这里监听
effective_address
=
inet_address;
........
.........
}
```
private async void handle_start_request (Promise<SocketAddress> start_request, Cancellable? cancellable) {
try
{do_start
SocketAddress effective_address
=
yield
do_start (cancellable);
/
/
调用 do_start等待这个函数返回
.........
}
private async SocketAddress do_start (Cancellable? cancellable) throws Error, IOError {
server
=
(Soup.Server)
Object
.new (typeof (Soup.Server),
"tls-certificate"
, endpoint_params.certificate);
server.add_websocket_handler (
"/ws"
, endpoint_params.origin, null, on_websocket_opened);
/
/
这个应该是websocket打开时处理的
if
(endpoint_params.asset_root !
=
null)
server.add_handler (
"/"
, on_asset_request);
/
/
看前面demo,web处理函数,这个也是
SocketConnectable connectable
=
(flavor
=
=
CONTROL)
? parse_control_address (endpoint_params.address, endpoint_params.port)
/
/
设置端口
27042
: parse_cluster_address (endpoint_params.address, endpoint_params.port);
Soup.ServerListenOptions listen_options
=
(endpoint_params.certificate !
=
null)
? Soup.ServerListenOptions.HTTPS
:
0
;
SocketAddress? first_effective_address
=
null;
var enumerator
=
connectable.
enumerate
();
while
(true) {
SocketAddress? address;
try
{
address
=
yield
enumerator.next_async (io_cancellable);
} catch (GLib.Error e) {
throw new Error.NOT_SUPPORTED (
"%s"
, e.message);
}
if
(address
=
=
null)
break
;
SocketAddress? effective_address
=
null;
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-11-16 16:22
被Thehepta编辑
,原因: