指纹浏览器-chromium指纹浏览器参数传递

命令行简单传参

首先说一下怎么找到的传参的位置,方法是找到src\third_party\blink\renderer\core\frame\navigator.cc(或者别的指纹的位置代码也可以),在productStub方法里下一个断点,然后调试,此时会断在代码中,在调用堆栈里往上找,找到最上面的一层函数

chrome_exe_main_win.cc的代码里可以发现如下代码

1
2
3
4
5
6
int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prev, wchar_t*, int)  {
//...
base::CommandLine::Init(0, nullptr);
const base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
const std::string process_type = command_line->GetSwitchValueASCII(switches::kProcessType);
}

调试发现这里的process_type的值就是字符串renderer,也就是说,command_line->GetSwitchValueASCII会获取命令行中的type参数对应的值,switches::kProcessType就是字符串type

简单看一下GetSwitchValueASCII函数和ForCurrentProcess函数

1
2
3
4
5
6
7
8
9
10
11
12
13
CommandLine* CommandLine::ForCurrentProcess() {
DCHECK(current_process_commandline_); // 检查全局是否有current_process_commandline_,没有直接报错
return current_process_commandline_;
}
std::string CommandLine::GetSwitchValueASCII(std::string_view switch_string) const {
StringType value = GetSwitchValueNative(switch_string);
// 检查获取到的值是否为ASCII码,不是就返回空字符
if (!IsStringASCII(base::AsStringPiece16(value))) {
DLOG(WARNING) << "Value of switch (" << switch_string << ") must be ASCII.";
return std::string();
}
return WideToUTF8(value);
}

switches::kProcessType通过std::string_view switch_stringstring_view形式传入,意味着不会修改这个参数字符串,只是查找对应的属性值,然后调用GetSwitchValueNative获取属性值

1
2
3
4
5
6
7
8
9
CommandLine::StringType CommandLine::GetSwitchValueNative(std::string_view switch_string) const {
DCHECK_EQ(ToLowerASCII(switch_string), switch_string);
auto result = switches_.find(switch_string);
return result == switches_.end() ? StringType() : result->second;
}
#if BUILDFLAG(IS_WIN)e.
using StringType = std::wstring;
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
using StringType = std::string;

GetSwitchValueNative里首先通过DCHECK_EQ进行比较,将参数小写之后和原参数进行比对,如果不对直接报错,后面再通过find获取对应的属性值,switches其实就是个字典,通过switches_.end() ? StringType() : result->second判断是否能获取到对应的属性值,如果获取不到,就返回StringType

那么,如果想要达到在renderer进程启动的时候把指定的参数传递过去,从而在renderer调用v8中的浏览器对象的时候可以接收参数、修改指纹的目的,应该在哪里把参数传递过去呢?

前面看到过chrome的多进程架构,renderer进程是由browser进程启动的,通过Broswer进程里的RenderProcessHost创建RenderProcess来进行互相通信,在RenderProcessHost那里把参数加上去

src/content/browser/renderer_host里找到render_process_host_impl.cc,这是RenderProcessHost的实现代码,找到其中的初始化方法Init,下面是其中的部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
bool RenderProcessHostImpl::Init() {
// calling Init() more than once does nothing, this makes it more convenient

const base::CommandLine& browser_command_line =
*base::CommandLine::ForCurrentProcess();
renderer_prefix =
browser_command_line.GetSwitchValueNative(switches::kRendererCmdPrefix);

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
int flags = renderer_prefix.empty() ? ChildProcessHost::CHILD_ALLOW_SELF
: ChildProcessHost::CHILD_NORMAL;
#elif BUILDFLAG(IS_MAC)
int flags = ChildProcessHost::CHILD_RENDERER;
#else
int flags = ChildProcessHost::CHILD_NORMAL;
#endif

// Find the renderer before creating the channel so if this fails early we
// return without creating the channel.
base::FilePath renderer_path = ChildProcessHost::GetChildPath(flags);
if (renderer_path.empty())
return false;

is_initialized_ = true;
is_dead_ = false;
sent_render_process_ready_ = false;

gpu_client_->PreEstablishGpuChannel();

// Set cache information after establishing a channel since the handles are
// stored on the channels. Note that we also check if the factory is
// initialized because in tests the factory may never have been initialized.
if (!GetBrowserContext()->IsOffTheRecord() &&
!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableGpuShaderDiskCache)) {
if (auto* cache_factory = GetGpuDiskCacheFactorySingleton()) {
for (const gpu::GpuDiskCacheType type : gpu::kGpuDiskCacheTypes) {
auto handle = cache_factory->GetCacheHandle(
type, storage_partition_impl_->GetPath().Append(
gpu::GetGpuDiskCacheSubdir(type)));
gpu_client_->SetDiskCacheHandle(handle);
}
}
}

// We may reach Init() during process death notification (e.g.
// RenderProcessExited on some observer). In this case the Channel may be
// null, so we re-initialize it here.
if (!channel_)
InitializeChannelProxy();

// Unpause the Channel briefly. This will be paused again below if we launch a
// real child process. Note that messages may be sent in the short window
// between now and then (e.g. in response to RenderProcessWillLaunch) and we
// depend on those messages being sent right away.
//
// |channel_| must always be non-null here: either it was initialized in
// the constructor, or in the most recent call to ProcessDied().
channel_->Unpause(false /* flush */);

// Call the embedder first so that their IPC filters have priority.
GetContentClient()->browser()->RenderProcessWillLaunch(this);

FieldTrialSynchronizer::UpdateRendererVariationsHeader(this);

#if BUILDFLAG(IS_ANDROID)
// Initialize the java audio manager so that media session tests will pass.
// See internal b/29872494.
static_cast<media::AudioManagerAndroid*>(media::AudioManager::Get())
->InitializeIfNeeded();
#endif // BUILDFLAG(IS_ANDROID)

CreateMessageFilters();
RegisterMojoInterfaces();

// Call this now and not in OnProcessLaunched in case any mojo calls get
// dispatched before this.
GetRendererInterface()->InitializeRenderer(
GetContentClient()->browser()->GetUserAgentBasedOnPolicy(
browser_context_),
GetContentClient()->browser()->GetUserAgentMetadata(),
storage_partition_impl_->cors_exempt_header_list(),
AttributionManager::GetSupport(),
GetContentClient()->browser()->GetOriginTrialsSettings());
// 判断是否以单进程形式启动浏览器,如果是就走下面的逻辑,所以这个不用看
if (run_renderer_in_process()) {
//...
} else {
// Build command line for renderer. We call AppendRendererCommandLine()
// first so the process type argument will appear first.
std::unique_ptr<base::CommandLine> cmd_line =
std::make_unique<base::CommandLine>(renderer_path);
if (!renderer_prefix.empty())
cmd_line->PrependWrapper(renderer_prefix);
// 添加命令行参数
AppendRendererCommandLine(cmd_line.get());

#if BUILDFLAG(IS_WIN)
std::unique_ptr<SandboxedProcessLauncherDelegate> sandbox_delegate =
std::make_unique<RendererSandboxedProcessLauncherDelegateWin>(
*cmd_line, IsPdf(), IsJitDisabled());
#else
std::unique_ptr<SandboxedProcessLauncherDelegate> sandbox_delegate =
std::make_unique<RendererSandboxedProcessLauncherDelegate>();
#endif

auto file_data = std::make_unique<ChildProcessLauncherFileData>();
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC)
file_data->files_to_preload = GetV8SnapshotFilesToPreload();
#endif

// Spawn the child process asynchronously to avoid blocking the UI thread.
// As long as there's no renderer prefix, we can use the zygote process
// at this stage.
child_process_launcher_ = std::make_unique<ChildProcessLauncher>(
std::move(sandbox_delegate), std::move(cmd_line), GetID(), this,
std::move(mojo_invitation_),
base::BindRepeating(&RenderProcessHostImpl::OnMojoError, id_),
std::move(file_data));
channel_->Pause();

// In single process mode, browser-side tracing and memory will cover the
// whole process including renderers.
BackgroundTracingManagerImpl::ActivateForProcess(GetID(),
child_process_.get());

fast_shutdown_started_ = false;
shutdown_requested_ = false;
}

last_init_time_ = base::TimeTicks::Now();
return true;
}

所以给render进程启动的时候添加参数的函数是AppendRendererCommandLine,进入这个函数

1
2
3
4
5
void RenderProcessHostImpl::AppendRendererCommandLine(
base::CommandLine* command_line) {
// Pass the process type first, so it shows first in process listings.
command_line->AppendSwitchASCII(switches::kProcessType,
switches::kRendererProcess);

在该函数代码的内部,通过AppendSwitchASCII进行参数的添加,switches::kProcessType在这里就是--typeswitches::kRendererProcess就是renderer,也就分别是键值对的键和值

现在的关注点就是怎么改这个AppendRendererCommandLine函数,达到动态传递参数的效果

在调试->chrome属性页->配置属性下的调试,修改命令参数,比如这里是

1
--jack="20270630"

这里的参数是传递给主进程的,在启动renderer进程的时候需要先获取主进程的所有参数cmdLine,再用键去获取值

1
2
3
const base::CommandLine* jack_command_line = base::CommandLine::ForCurrentProcess();
const std::string jack_fp = jack_command_line->GetSwitchValueASCII("jack");
command_line->AppendSwitchASCII("navigator", jack_fp);

然后renderer进程就可以成功接收到"20270630"这个参数值,后面要做的就是利用这个参数值去修改对应的指纹

这里以浏览器中navigator对象的productStub属性为例,代码位于src\third_party\blink\renderer\core\frame\navigator.cc,其实就是接收一下参数然后返回对应的值就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 导入头文件,不然拿不到CommandLine
#include "base/command_line.h"

String Navigator::productSub() const {
const base::CommandLine* jack_command_line = base::CommandLine::ForCurrentProcess();
// 检查是否有navigator键,因为这里是获取的当前renderer进程的命令行参数,就不是jack了
if (jack_command_line->HasSwitch("navigator")) {
const std::string jack_fp = jack_command_line->GetSwitchValueASCII("navigator");
// 强制类型转换,不然返回的是string类型
return String(jack_fp);
}
// 这里一定要返回一个,不然其他的renderer起不起来
return "20030107";
}

通过switches进行动态传参

现在基本上能进行基础的指纹传参了,但是目前的属性名是固定在代码中的(固定的字符串”jack”),还需要进行优化,怎么把固定的字符串仿照官方源码中的switches进行修改呢?就需要定位到switches的.h和.cc实现

render_process_host_impl.cc的头文件中找到有switches字符串的头文件,不止一个,要找到包含kProcessType的头文件定义,最终找到src\content\public\common\content_switches.hsrc\content\public\common\content_switches.cc,在.h文件中找到kProcessType的定义

1
CONTENT_EXPORT extern const char kProcessType[];

仿照这个添加一个定义

1
CONTENT_EXPORT extern const char kJackType[];

在.cc文件中同样的方法

1
2
const char kProcessType[] = "type";
const char kJackType[] = "jack";

src\content\browser\renderer_host\render_process_host_impl.cc中修改获取参数的代码,不再使用硬编码

1
2
3
4
5
const base::CommandLine* jack_command_line = base::CommandLine::ForCurrentProcess();
if (jack_command_line->HasSwitch(switches::kJackType)) {
const std::string jack_fp = jack_command_line->GetSwitchValueASCII(switches::kJackType);
command_line->AppendSwitchASCII("navigator", jack_fp);
}

此时阶段性修改成功之后,这里的navigator也可以改成switches::kJackType,达到固定的效果

接下来就是在接收参数的时候,想办法也把属性的名称给固定到switches文件中去

此时navigator.cc要修改的话不能直接调用switches::kJackType,要先修改third_party下对应的switches头文件,因为navigator.ccrender_process_host_impl.cc不是一个模块,navigator.cc的switches文件位于src\third_party\blink\public\common\switches.hsrc\third_party\blink\common\switches.cc

.h文件一样的方法修改

1
2
BLINK_COMMON_EXPORT extern const char kJackType[];
BLINK_COMMON_EXPORT extern const char kAllowPreCommitInput[];

.cc文件修改值

1
2
const char kAllowPreCommitInput[] = "allow-pre-commit-input";
const char kJackType[] = "jack";

然后修改navigator.cc

1
2
3
4
5
6
7
8
9
10
11
#include "third_party/blink/public/common/switches.h"

String Navigator::productSub() const {
const base::CommandLine* jack_command_line = base::CommandLine::ForCurrentProcess();
// 注意这里是blink::switches::
if (jack_command_line->HasSwitch(blink::switches::kJackType)) {
const std::string jack_fp = jack_command_line->GetSwitchValueASCII(blink::switches::kJackType);
return String(jack_fp);
}
return "20030107";
}

json格式传参

当需要传递的指纹参数比较多的时候,当然是json格式更方便接收并且判断对应的指纹参数的值

比如

1
--jack="{\"productStub\":\"20030808\",\"vendor\":\"test\"}"

导入几个头文件

1
2
3
#include "base/json/json_reader.h"
#include "base/values.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

修改代码

1
2
3
4
5
6
7
8
9
10
11
12
String Navigator::productSub() const {
const base::CommandLine* jack_command_line = base::CommandLine::ForCurrentProcess();
if (jack_command_line->HasSwitch(blink::switches::kJackType)) {
const std::string jack_fp = jack_command_line->GetSwitchValueASCII(blink::switches::kJackType);
// 这里的返回值类型和调用方法都是看json_reader.h里
absl::optional<base::Value> json_reader = base::JSONReader::Read(jack_fp);
std::string product_fp =
*(json_reader->GetDict().FindString("productStub"));
return String(product_fp);
}
return "20030107";
}

这个也可以封装成函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
String getfp_string(std::string name) {
const base::CommandLine* jack_command_line = base::CommandLine::ForCurrentProcess();
if (jack_command_line->HasSwitch(blink::switches::kJackType)) {
const std::string jack_fp = jack_command_line->GetSwitchValueASCII(blink::switches::kJackType);
absl::optional<base::Value> json_reader = base::JSONReader::Read(jack_fp);
std::string product_fp = *(json_reader->GetDict().FindString(name));
return String(product_fp);
}
return "";
}

String Navigator::productSub() const {
String result = getfp_string("productStub");
if (result != "")
{
return result;
}
return "20030107";
}

String Navigator::vendor() const {
String result = getfp_string("vendor");
if (result != "") {
return result;
}
// Do not change without good cause. History:
// https://code.google.com/p/chromium/issues/detail?id=276813
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=27786
// https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/QrgyulnqvmE
return "Google Inc.";
}