--single-process
at the command line) but unfortunately this is an unsupported and disabled mode in Google Chrome (see this message on the chromium-dev mailing list). But this wasn't going to stop me...The first thing to do was to figure out how
--single-process
is implemented and how it is disabled in Google Chrome official builds. The first link in the mailing list posting points us in the right direction:From src/chrome/app/chrome_dll_main.c:
bool single_process =
#if defined (GOOGLE_CHROME_BUILD)
// This is an unsupported and not fully tested mode, so don't enable it for
// official Chrome builds.
false;
#else
parsed_command_line.HasSwitch(switches::kSingleProcess);
#endif
if (single_process)
RenderProcessHost::set_run_renderer_in_process(true);
What we want to do is change the value of
single_process
after it is initialized to false but before the if
. Unfortunately, here this is impossible because the compiler has optimized away that variable! This snippet of code actually does not even appear in chrome.dll since the compiler has also optimized away the call to RenderProcessHost::set_run_renderer_in_process
.We are lucky though, because
single_process
is never used again so if we can emulate a call to RenderProcessHost::set_run_renderer_in_process(true)
, we're golden. Digging deeper, that method does the following:Google Code Search finds src/chrome/browser/renderer_host/render_process_host.h:
static void set_run_renderer_in_process(bool value) {
run_renderer_in_process_ = value;
}
..with
run_renderer_in_process_
a static bool in RenderProcessHost
. This means we'll find it like a global variable with storage in .data.Now, of course, we don't have symbols for chrome.dll so we have to figure out a way of finding the variable's location in memory. This can be done by looking for other references to this variable, fingerprinting those and using them to find
run_renderer_in_process_
. In this particular case, we need to look for the accessors of this field, which the compiler has also inlined.There are a few references scattered across the codebase, but remember that we are looking for ones that are easy to fingerprint and unlikely to change across versions. This means uncommon constants, code sequences, etc. I chose two places:
BrowserRenderProcessHost::BrowserRenderProcessHost
and RenderProcessHost::ShouldTryToUseExistingProcessHost
.First, let's look at
BrowserRenderProcessHost::BrowserRenderProcessHost
:BrowserRenderProcessHost::BrowserRenderProcessHost(Profile* profile)
: RenderProcessHost(profile),
[...]
if (run_renderer_in_process()) {
// We need a "renderer pid", but we don't have one when there's no renderer
// process. So pick a value that won't clash with other child process pids.
// Linux has PID_MAX_LIMIT which is 2^22. Windows always uses pids that are
// divisible by 4. So...
static int next_pid = 4 * 1024 * 1024;
next_pid += 3;
SetProcessID(next_pid);
}
Now this is a very nice snippet because we will have the constant (4 * 1024 * 1024) somewhere in .data and a reference to it from an ADD instruction with "3" as the second operand. This is fairly uncommon so we'll start looking for this one. (Open up IDA.) We come up with this (in chrome.dll 3.0.195.6 11,844,080 bytes):
.text:021749A8 cmp byte_2659CEC, 0
.text:021749AF jz short loc_21749C5
.text:021749B1 add dword_2639A58, 3
Here
run_renderer_in_process_
is at byte_2659CEC
.Now I wanted to be on the safe side and confirm this with another reference. One of the references to
run_renderer_in_process_
is in RenderProcessHost
itself:From src/chrome/browser/renderer_host/render_process_host.cc:
bool RenderProcessHost::ShouldTryToUseExistingProcessHost() {
size_t renderer_process_count = all_hosts.size();
[...]
return run_renderer_in_process() ||
(renderer_process_count >= GetMaxRendererProcessCount());
}
Remember that that getter is inlined. In fact,
GetMaxRendererProcessCount
is inlined too. Let's look at the source for that as well to get an idea of what to expect.From chrome/browser/renderer_host/renderer_process_host.cc:
size_t GetMaxRendererProcessCount() {
[...]
static const size_t kMaxRenderersByRamTier[] = {
3, // less than 256MB
6, // 256MB
9, // 512MB
12, // 768MB
14, // 1024MB
[...]
};
static size_t max_count = 0;
if (!max_count) {
size_t memory_tier = base::SysInfo::AmountOfPhysicalMemoryMB() / 256;
if (memory_tier >= arraysize(kMaxRenderersByRamTier))
max_count = chrome::kMaxRendererProcessCount;
else
max_count = kMaxRenderersByRamTier[memory_tier];
}
return max_count;
}
Going back to the original boolean expression in
ShouldTryToUseExistingProcessHost
, we expect to see a CMP on run_renderer_in_process_
, followed at some point by an indexed access into kMaxRenderersByRamTier
. The reason I chose this is kMaxRenderersByRamTier
is very easy to find and unlikely to change across versions. Anyway, the assembly ends up looking like this:.text:02038775 cmp byte_2659CEC, 0
.text:0203877C push esi
.text:0203877D mov esi, dword_2636D08
[...]
.text:020387A9 loc_20387A9: ; CODE XREF: sub_2038775+2Dj
.text:020387A9 mov eax, ds:dword_25CF518[eax*4]
So if we find
kMaxRenderersByRamTier
(dword_2636D08
here), we can find the reference to it, then look in its neighborhood for a "CMP m32,0".And of course it would be nice if a computer could do this automatically for us :) Luckily there's an awesome Python module called pydbg that lets you write debugging scripts in Python.
Check out the finished product: chromehack.py.
So.. what have we learned?
- Beware of compiler optimizations
- If you cannot cause your target to execute a particular piece of code, try to emulate all the side-effects of that code
- Don't give up! Anything and everything is possible in assembly :)
-Cat