<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Erik Pistelli &#8211; NTCore</title>
	<atom:link href="https://ntcore.com/author/ntcore/feed/" rel="self" type="application/rss+xml" />
	<link>https://ntcore.com</link>
	<description></description>
	<lastBuildDate>Thu, 04 Dec 2025 13:27:58 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9</generator>

<image>
	<url>https://ntcore.com/wp-content/uploads/2018/07/cropped-ntcore_icon-2-32x32.png</url>
	<title>Erik Pistelli &#8211; NTCore</title>
	<link>https://ntcore.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">184274875</site>	<item>
		<title>🚨 WARNING: Impersonator website</title>
		<link>https://ntcore.com/warning-impersonator/</link>
					<comments>https://ntcore.com/warning-impersonator/#respond</comments>
		
		<dc:creator><![CDATA[Erik Pistelli]]></dc:creator>
		<pubDate>Tue, 06 May 2025 13:36:54 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://ntcore.com/?p=767</guid>

					<description><![CDATA[A fake domain—cff-explorer[.]com—has been registered to impersonate CFF Explorer. It currently appears as the top or second (depending on language) Google result when searching for &#8220;CFF Explorer&#8221;. The only legitimate domain is ntcore.com.]]></description>
										<content:encoded><![CDATA[<p>A fake domain—cff-explorer[.]com—has been registered to impersonate CFF Explorer. It currently appears as the top or second (depending on language) Google result when searching for &#8220;CFF Explorer&#8221;. <strong>The only legitimate domain is <a href="https://ntcore.com">ntcore.com</a>.</strong></p>
]]></content:encoded>
					
					<wfw:commentRss>https://ntcore.com/warning-impersonator/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">767</post-id>	</item>
		<item>
		<title>Windows Memory Analysis</title>
		<link>https://ntcore.com/windows-memory-analysis/</link>
					<comments>https://ntcore.com/windows-memory-analysis/#respond</comments>
		
		<dc:creator><![CDATA[Erik Pistelli]]></dc:creator>
		<pubDate>Tue, 25 Mar 2025 10:37:04 +0000</pubDate>
				<category><![CDATA[News]]></category>
		<category><![CDATA[Analysis]]></category>
		<category><![CDATA[Cerbero Labs]]></category>
		<category><![CDATA[Cerbero Suite]]></category>
		<category><![CDATA[Forensics]]></category>
		<category><![CDATA[Memory]]></category>
		<guid isPermaLink="false">https://ntcore.com/?p=755</guid>

					<description><![CDATA[If, just like me, you&#8217;re fascinated by memory forensics, you might be interested in the new Memory Analysis package for Cerbero Suite developed by my company. The package supports analyzing memory dumps from Windows XP up to Windows 11, on both x86 and x64 systems, and is designed to make complex forensic tasks simpler and &#8230; <a href="https://ntcore.com/windows-memory-analysis/" class="more-link">Continue reading<span class="screen-reader-text"> "Windows Memory Analysis"</span></a>]]></description>
										<content:encoded><![CDATA[<p>If, just like me, you&#8217;re fascinated by memory forensics, you might be interested in the new <a href="https://cerbero.io/memory/">Memory Analysis package</a> for <a href="https://cerbero.io/suite/">Cerbero Suite</a> developed by my company. The package supports analyzing memory dumps from Windows XP up to Windows 11, on both x86 and x64 systems, and is designed to make complex forensic tasks simpler and more intuitive.</p>
<p><a href="https://cerbero.io/memory/"><img decoding="async" class="center-img" src="/wp-content/uploads/2025/12/memory.png"/></a></p>
<p>I&#8217;ve always been intrigued by the possibility of visually exploring an entire system, from the complete overview down to its mapped executables in memory. As Cerbero Suite&#8217;s functionality evolved, it is now capable of offering an unparalleled experience in this regard.</p>
<p>If you or your organization are involved in memory analysis, you might want to check it out.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ntcore.com/windows-memory-analysis/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">755</post-id>	</item>
		<item>
		<title>Cerbero Journal</title>
		<link>https://ntcore.com/cerbero-journal/</link>
					<comments>https://ntcore.com/cerbero-journal/#comments</comments>
		
		<dc:creator><![CDATA[Erik Pistelli]]></dc:creator>
		<pubDate>Mon, 20 Jun 2022 08:45:21 +0000</pubDate>
				<category><![CDATA[News]]></category>
		<category><![CDATA[Cerbero Labs]]></category>
		<guid isPermaLink="false">https://ntcore.com/?p=723</guid>

					<description><![CDATA[Since I&#8217;m a nostalgic, my company now has an official e-zine. If, like me, you&#8217;re old enough, it will perhaps remind you of the golden era of e-zines.]]></description>
										<content:encoded><![CDATA[<p>Since I&#8217;m a nostalgic, my company now has an official <a href="https://cerbero.io/e-zine/">e-zine</a>.</p>
<p>If, like me, you&#8217;re old enough, it will perhaps remind you of the golden era of e-zines.</p>
<p><center><a href="https://cerbero.io/e-zine/"><img decoding="async" src="/wp-content/uploads/2022/06/cerbero_journal_issue_1.png"/></a></center></p>
]]></content:encoded>
					
					<wfw:commentRss>https://ntcore.com/cerbero-journal/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">723</post-id>	</item>
		<item>
		<title>Rust for closed-source projects</title>
		<link>https://ntcore.com/rust-for-closed-source-projects/</link>
					<comments>https://ntcore.com/rust-for-closed-source-projects/#comments</comments>
		
		<dc:creator><![CDATA[Erik Pistelli]]></dc:creator>
		<pubDate>Sun, 23 Jun 2019 19:50:03 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Rust]]></category>
		<guid isPermaLink="false">https://ntcore.com/?p=641</guid>

					<description><![CDATA[I&#8217;ve been playing with Rust for a while now. With a few thousands lines of Rust code under my belt I can&#8217;t by any means claim to be an expert, but I&#8217;ve formed a few opinions on it. I mostly like its syntax very much. It&#8217;s refreshing compared to many other languages out there. I &#8230; <a href="https://ntcore.com/rust-for-closed-source-projects/" class="more-link">Continue reading<span class="screen-reader-text"> "Rust for closed-source projects"</span></a>]]></description>
										<content:encoded><![CDATA[<p>I&#8217;ve been playing with Rust for a while now. With a few thousands lines of Rust code under my belt I can&#8217;t by any means claim to be an expert, but I&#8217;ve formed a few opinions on it. I mostly like its syntax very much. It&#8217;s refreshing compared to many other languages out there. I also consider Rust a complex language. While I can easily develop code in Java, which is what I&#8217;m currently doing, without having ever written a line of code in it, Rust is different. Just like with your first book on C or C++, you have to actually learn Rust.</p>
<p>So why did I start looking into Rust? Well, in the beginning it was just curiosity and wanting to learn something new and I had to choose between Go and Rust. I first looked into Go, took the official language tour and had understood its syntax in about 1-2 hours. That, of course, doesn&#8217;t mean I mastered it, but I was ready to program something in it. And that&#8217;s also the key strength of Go: you can learn programming in it right away, it&#8217;s extremely simple. </p>
<p>Then I started studying Rust and after half a day spent on a train reading parts of the Rust book, I ported a <a href="/?p=594">small CHIP-8 emulator</a> to it in one day. Just a small exercise to become more familiar with the language. Throughout the following month I continued reading the Rust book and even re-reading chapters, in order not to forget things. It is necessary to use a language in order to remember it: this is true both for programming languages as for spoken ones. And the more complex a language is, the easier it is to forget it and Rust has a learning curve which is steeper than Go or C#.</p>
<p>I would say that a C++ developer can easily program in any C-dialect (Java, C#, Go, JS, etc.) and can easily learn languages such as Python or Ruby. Rust is a paradigm shift and takes longer to learn.</p>
<p>About two months ago, I had to start the development of a command-line project and needed a decent standard library. The end-result had to be a native, statically linked executable. Realistically, the possibilities  were: C/C++, Rust or Go. For C++ the STL was out of question, because I think it&#8217;s terrible and anyway lacks many features which are essential. Qt is a beautiful C++ library and I use it gladly whenever I can, but static linking would require a commercial Qt license. On top of that, compiling on multiple OSs would require a rebuild of the static Qt library on each OS, so a lot of extra work.</p>
<p>But apart from all this, there was also the fact that the project in question was boring to develop and I wanted it to be fun. So while I briefly considered Go, I went almost immediately with Rust. Go is not just simple, it&#8217;s simplified. It lacks many important constructs present in other languages for the purpose of simplicity. While the simplicity of Go can be refreshing and has its charm, I found myself naturally gravitating towards the complexity of Rust. </p>
<p>To be clear, I don&#8217;t like unnecessary complexity and that&#8217;s why I am not using most of the new features to be found in C++. Complexity has to be kept simple. Rust set a number of goals for itself and some of them are complex to solve without a garbage collector. Within the complexity which arises from these goals, it needs to keep things as simple as possible.</p>
<p>Having said all that, Rust is not yet a mature language in many ways and can&#8217;t be used for just any project like C/C++. Its library is less rich than that of Go, some its standard library does have, in my opinion, an odd syntax and compiling can be really slow.</p>
<p>What I wrote in the CHIP-8 post was:</p>
<p><i>I can’t yet write something exhaustive about Rust, because I’m still learning it. What I can say up until now is that, apart some minor things which I dislike (snake-case: ugh), it seems fun to program in it. The amount of rules make the programming a bit more challenging, but it pays off in satisfaction once everything builds without complaints.</p>
<p>The only thing I can say is that I can’t clearly see a use-case for Rust. Yes, it’s a solid and secure language which is fun to use. But will it be used in the real world? I can see many use-cases for Go, not so many for Rust. What I hope is for Rust to mature some more and then to become stable, without going down the path to insanity like modern C++.</i></p>
<p>I must say I changed my mind. I definitely see a future for Rust, because if there are enough talented programmers who think it&#8217;s fun to program in it, it will grow. That&#8217;s a safe bet. The only thing it must avoid is to have people implementing useless features in it, the way it is being done in C++, just for their academic score. But before that happens Rust will definitely flourish.</p>
<p>One of the aspects about Rust in connection to closed-source projects which needs to be mentioned is that there&#8217;s a lot of debug information inside of a Rust executable, even in release mode. Every <b>panic!</b> in Rust prints out a lot of metadata. </p>
<p>Let&#8217;s take for instance this small sample I created:</p>
<pre lang="rust">fn foo() {
	panic!("this is an error");
}

fn main() {
    println!("Hello, world!");
	foo();
}</pre>
<p>It will print out:</p>
<pre lang="batch">Hello, world!
thread 'main' panicked at 'this is an error', src\main.rs:2:2
note: Run with 'RUST_BACKTRACE=1' environment variable to display a backtrace.</pre>
<p>By setting <b>RUST_BACKTRACE</b> to 1, it&#8217;s even worse:</p>
<pre lang="batch">Hello, world!
thread 'main' panicked at 'this is an error', src\main.rs:2:2
stack backtrace:
   0: std::sys_common::backtrace::print
             at /rustc/2aa4c46cfdd726e97360c2734835aa3515e8c858\/src\libstd\sys_common\backtrace.rs:58
   1: std::panicking::default_hook::{{closure}}
             at /rustc/2aa4c46cfdd726e97360c2734835aa3515e8c858\/src\libstd\panicking.rs:200
   2: std::panicking::default_hook
             at /rustc/2aa4c46cfdd726e97360c2734835aa3515e8c858\/src\libstd\panicking.rs:215
   3: std::panicking::rust_panic_with_hook
             at /rustc/2aa4c46cfdd726e97360c2734835aa3515e8c858\/src\libstd\panicking.rs:478
   4: std::panicking::begin_panic
   5: std::panicking::try
             at /rustc/2aa4c46cfdd726e97360c2734835aa3515e8c858\/src\libstd\panicking.rs:276
   6: std::panic::catch_unwind
             at /rustc/2aa4c46cfdd726e97360c2734835aa3515e8c858\/src\libstd\panic.rs:388
   7: std::rt::lang_start_internal
             at /rustc/2aa4c46cfdd726e97360c2734835aa3515e8c858\/src\libstd\rt.r
s:48
   8: main
   9: BaseThreadInitThunk
  10: RtlInitializeExceptionChain
  11: RtlInitializeExceptionChain</pre>
<p>I found these privacy issues related to closed-source projects being mentioned in this <a href="https://github.com/rust-lang/rust/issues/41555">thread on GitHub</a>, but I didn&#8217;t find any ready-to-use solution.</p>
<p>So the obvious and only solution is to modify the Rust compiler and that&#8217;s exactly what we&#8217;re going to do. While I&#8217;m describing how to do this on Windows, the parts not related to the build process are valid on Unix as well.</p>
<p>I&#8217;d like to mention that, in order to avoid this hassle, I briefly looked into Go to check how much metadata was to be found in Go binaries. The answer is: a lot. And it&#8217;s even way worse than in Rust, because Go has <a href="https://golang.org/pkg/reflect/">reflection</a> and patching that out of the compiler is way more difficult and may break lots of stuff.</p>
<p>Another reason worth mentioning why Go was a no-go is that at least on Windows the capability of Go to call C-code via <a href="https://golang.org/cmd/cgo/">CGo</a> requires the mingw compiler and that leads to a whole new set of problems.</p>
<p>The first step to build the Rust compiler is to download the source. You can do so either from the <a href="https://forge.rust-lang.org/other-installation-methods.html">website</a> or from <a href="https://github.com/rust-lang/rust">GitHub</a>. Then you need Visual Studio 2017 or above. The README says 2013 or above but I found another part of the documentation mentioning 2017 or above and I had difficulties building with Visual Studio 2013. The community edition of Visual Studio is more than enough. I used Visual Studio 2017.</p>
<p>Extremely important, however, are the packages of Visual Studio you need to install. Initially, since I didn&#8217;t think I needed many, I limited myself to the essential and got strange build errors. It took me a _lot_ of time to understand that I needed to install certain additional packages in Visual Studio. If you&#8217;re the kind of guy who just installs everything which comes with Visual Studio, then you&#8217;re good to go. But if you&#8217;re more like me and want to limit the installation size, here&#8217;s the essential packages you absolutely need to install in order not to anger the gods:</p>
<p><center><img decoding="async" src="/wp-content/uploads/2019/rustclosed/features.png"/></center></p>
<p>Build instructions can be found in the README. The relevant part for us is:</p>
<pre lang="github">#### MSVC
[windows-msvc]: #windows-msvc

MSVC builds of Rust additionally require an installation of Visual Studio 2013
(or later) so `rustc` can use its linker. Make sure to check the “C++ tools”
option.

With these dependencies installed, you can build the compiler in a `cmd.exe`
shell with:

```sh
> python x.py build
```

Currently, building Rust only works with some known versions of Visual Studio. If
you have a more recent version installed the build system doesn't understand
then you may need to force rustbuild to use an older version. This can be done
by manually calling the appropriate vcvars file before running the bootstrap.

```batch
> CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat"
> python x.py build
```

#### Specifying an ABI
[specifying-an-abi]: #specifying-an-abi

Each specific ABI can also be used from either environment (for example, using
the GNU ABI in PowerShell) by using an explicit build triple. The available
Windows build triples are:
- GNU ABI (using GCC)
    - `i686-pc-windows-gnu`
    - `x86_64-pc-windows-gnu`
- The MSVC ABI
    - `i686-pc-windows-msvc`
    - `x86_64-pc-windows-msvc`

The build triple can be specified by either specifying `--build=<triple>` when
invoking `x.py` commands, or by copying the `config.toml` file (as described
in Building From Source), and modifying the `build` option under the `[build]`
section.</pre>
<p>The build triple I used is &#8216;i686-pc-windows-msvc&#8217;, because I needed the application to be 32-bit, in order to maximize compatibility. What I did is to copy the &#8216;config.toml.example&#8217; in the main directory to &#8216;config.toml&#8217; and modify parts of it. </p>
<p>The following are the parts I modified:</p>
<pre lang="ini">[llvm]

# Indicates whether the LLVM build is a Release or Debug build
optimize = true

# Indicates whether an LLVM Release build should include debug info
release-debuginfo = false

# Indicates whether the LLVM assertions are enabled or not
assertions = false

# Link libstdc++ statically into the librustc_llvm instead of relying on a
# dynamic version to be available.
static-libstdcpp = true

# LLVM targets to build support for.
# Note: this is NOT related to Rust compilation targets. However, as Rust is
# dependent on LLVM for code generation, turning targets off here WILL lead to
# the resulting rustc being unable to compile for the disabled architectures.
# Also worth pointing out is that, in case support for new targets are added to
# LLVM, enabling them here doesn't mean Rust is automatically gaining said
# support. You'll need to write a target specification at least, and most
# likely, teach rustc about the C ABI of the target. Get in touch with the
# Rust team and file an issue if you need assistance in porting!
targets = "X86"

# When invoking `llvm-config` this configures whether the `--shared` argument is
# passed to prefer linking to shared libraries.
link-shared = false

# Build triple for the original snapshot compiler. This must be a compiler that
# nightlies are already produced for. The current platform must be able to run
# binaries of this build triple and the nightly will be used to bootstrap the
# first compiler.
build = "i686-pc-windows-msvc"    # defaults to your host platform

# Flag to specify whether any documentation is built. If false, rustdoc and
# friends will still be compiled but they will not be used to generate any
# documentation.
docs = false

# Indicates whether the native libraries linked into Cargo will be statically
# linked or not.
cargo-native-static = true

[rust]

# Whether or not to optimize the compiler and standard library.
#
# Note: the slowness of the non optimized compiler compiling itself usually
#       outweighs the time gains in not doing optimizations, therefore a
#       full bootstrap takes much more time with `optimize` set to false.
optimize = true

# [...]
debug = false

# Whether or not debug assertions are enabled for the compiler and standard
# library.
debug-assertions = false

# Whether or not `panic!`s generate backtraces (RUST_BACKTRACE)
backtrace = false

[target.i686-pc-windows-msvc]

# Force static or dynamic linkage of the standard library for this target. If
# this target is a host for rustc, this will also affect the linkage of the
# compiler itself. This is useful for building rustc on targets that normally
# only use static libraries. If unset, the target's default linkage is used.
crt-static = true</pre>
<p>rustc is a bootstrapping compiler, which means that it uses itself to build itself. There are 3 build stages called stage0, stage1 and stage2. Only at stage1 the sources in our directory are used. The resulting compiler then builds itself again in stage2. This process is described in detail on this <a href="https://rust-lang.github.io/rustc-guide/how-to-build-and-run.html">page</a>.</p>
<p>Finding the <b>panic!</b> macro is very easy: it&#8217;s inside src/libcore/macros.rs.</p>
<pre lang="rust">#[macro_export]
#[allow_internal_unstable(core_panic, __rust_unstable_column)]
#[stable(feature = "core", since = "1.6.0")]
macro_rules! panic {
    () => (
        panic!("explicit panic")
    );
    ($msg:expr) => ({
        $crate::panicking::panic(&($msg, file!(), line!(), __rust_unstable_column!()))
    });
    ($msg:expr,) => (
        panic!($msg)
    );
    ($fmt:expr, $($arg:tt)+) => ({
        $crate::panicking::panic_fmt(format_args!($fmt, $($arg)*),
                                     &(file!(), line!(), __rust_unstable_column!()))
    });
}</pre>
<p>While the first instinct would be to patch this macro, if we look at what is called inside of it, we can see it calls the macros <b>file!</b>, <b>line!</b> and <b>__rust_unstable_column!</b>. These macros are defined in the same file:</p>
<pre lang="rust">    #[stable(feature = "rust1", since = "1.0.0")]
    #[rustc_doc_only_macro]
    macro_rules! line { () => ({ /* compiler built-in */ }) }

    /// Expands to the column number on which it was invoked.
    ///
    /// For more information, see the documentation for [`std::column!`].
    ///
    /// [`std::column!`]: ../std/macro.column.html
    #[stable(feature = "rust1", since = "1.0.0")]
    #[rustc_doc_only_macro]
    macro_rules! column { () => ({ /* compiler built-in */ }) }

    /// Expands to the file name from which it was invoked.
    ///
    /// For more information, see the documentation for [`std::file!`].
    ///
    /// [`std::file!`]: ../std/macro.file.html
    #[stable(feature = "rust1", since = "1.0.0")]
    #[rustc_doc_only_macro]
    macro_rules! file { () => ({ /* compiler built-in */ }) }</pre>
<p>Unfortunately, they are built-in. However, patching out these macros is much better than modifying the <b>panic!</b> macro, as it solves the issue at its roots and prevents these macros from generating metadata elsewhere. </p>
<p>So I searched for the &#8220;column&#8221; word in the whole source tree and after a bit of inspection finally got to the location where built-in macros are expanded, which is in src/libsyntax/ext/source_util.rs.</p>
<p>So I patched out the relevant parts:</p>
<pre lang="rust">use syntax_pos::{self, Pos, Span, FileName};

// becomes

use syntax_pos::{self, Span, FileName};

// note: this is important because Rust doesn't tolerate unused imports and after these
//       changes the 'Pos' import is no longer used

/// line!(): expands to the current line number
pub fn expand_line(cx: &mut ExtCtxt<'_>, sp: Span, tts: &[tokenstream::TokenTree])
                   -> Box<dyn base::MacResult+'static> {
    base::check_zero_tts(cx, sp, tts, "line!");

    let topmost = cx.expansion_cause().unwrap_or(sp);
    let loc = cx.source_map().lookup_char_pos(topmost.lo());

    base::MacEager::expr(cx.expr_u32(topmost, loc.line as u32))
}

// becomes

/// line!(): expands to the current line number
pub fn expand_line(cx: &mut ExtCtxt<'_>, sp: Span, tts: &[tokenstream::TokenTree])
                   -> Box<dyn base::MacResult+'static> {
    base::check_zero_tts(cx, sp, tts, "line!");

    let topmost = cx.expansion_cause().unwrap_or(sp);

    base::MacEager::expr(cx.expr_u32(topmost, 0))
}

/* column!(): expands to the current column number */
pub fn expand_column(cx: &mut ExtCtxt<'_>, sp: Span, tts: &[tokenstream::TokenTree])
                  -> Box<dyn base::MacResult+'static> {
    base::check_zero_tts(cx, sp, tts, "column!");

    let topmost = cx.expansion_cause().unwrap_or(sp);
    let loc = cx.source_map().lookup_char_pos(topmost.lo());

    base::MacEager::expr(cx.expr_u32(topmost, loc.col.to_usize() as u32 + 1))
}

// becomes

/* column!(): expands to the current column number */
pub fn expand_column(cx: &mut ExtCtxt<'_>, sp: Span, tts: &[tokenstream::TokenTree])
                  -> Box<dyn base::MacResult+'static> {
    base::check_zero_tts(cx, sp, tts, "column!");

    let topmost = cx.expansion_cause().unwrap_or(sp);

    base::MacEager::expr(cx.expr_u32(topmost, 0))
}

/// file!(): expands to the current filename */
/// The source_file (`loc.file`) contains a bunch more information we could spit
/// out if we wanted.
pub fn expand_file(cx: &mut ExtCtxt<'_>, sp: Span, tts: &[tokenstream::TokenTree])
                   -> Box<dyn base::MacResult+'static> {
    base::check_zero_tts(cx, sp, tts, "file!");

    let topmost = cx.expansion_cause().unwrap_or(sp);
    let loc = cx.source_map().lookup_char_pos(topmost.lo());
    base::MacEager::expr(cx.expr_str(topmost, Symbol::intern(&loc.file.name.to_string())))
}

// becomes

/// file!(): expands to the current filename */
/// The source_file (`loc.file`) contains a bunch more information we could spit
/// out if we wanted.
pub fn expand_file(cx: &mut ExtCtxt<'_>, sp: Span, tts: &[tokenstream::TokenTree])
                   -> Box<dyn base::MacResult+'static> {
    base::check_zero_tts(cx, sp, tts, "file!");

    let topmost = cx.expansion_cause().unwrap_or(sp);
	
    base::MacEager::expr(cx.expr_str(topmost, Symbol::intern("")))
}</pre>
<p>After these changes we can open the Visual Studio command prompt and compile by entering: </p>
<pre lang="batch">python.exe x.py build</pre>
<p>The compile process will take a while. If you have many cores, you can try to speed it up by changing relevant parts in the config.toml file. It can also happen that the build ends with some strange error. This may happen if you&#8217;re compiling for 32-bit and LLVM exhausts memory. The documentation mentions this. It&#8217;s not a big issue, just relaunch the build command and the build process will continue from where it left off. It never happened to me that I had to rebuild more than once.</p>
<p>If the build ends successfully, you should end up with a rustc compiler in build/i686-pc-windows-msvc/stage2/bin. I didn&#8217;t find any cargo.exe in that directory, so I just copied the one from the official installation into it.</p>
<p>I then prepared a batch file to launch the Visual Studio command prompt for the correct Rust version:</p>
<pre lang="batch">SET PATH=%PATH%;C:\[...]\rustc-1.35.0-src\build\i686-pc-windows-msvc\stage2\bin
%comspec% /k "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat"</pre>
<p>And compiled the release of the test binary via:</p>
<pre lang="batch">cargo run --release</pre>
<p>The output now is:</p>
<pre lang="batch">Hello, world!
thread 'main' panicked at 'this is an error', :0:0
error: process didn't exit successfully: `target\release\simple.exe` (exit code: 101)</pre>
<p>If we set <b>RUST_BACKTRACE</b>, the result will be the same.</p>
<p>After inspecting the executable we can see that there is still some metadata left in the shape of some absolute paths, such as:</p>
<pre lang="hex">Offset     0  1  2  3  4  5  6  7    8  9  A  B  C  D  E  F     Ascii   

0001BE40  4C 6F 63 6B 53 68 61 72   65 64 00 00 30 10 40 00     LockShared..0.@.
0001BE50  04 00 00 00 04 00 00 00   C0 93 40 00 70 92 40 00     ..........@.p.@.
0001BE60  70 93 40 00 00 00 00 00   00 00 00 00 00 00 00 00     p.@.............
0001BE70  43 3A 5C 55 73 65 72 73   5C 63 5C 72 75 73 74 5F     C:\Users\c\rust_
0001BE80  62 75 69 6C 64 73 5C 72   75 73 74 63 2D 31 2E 33     builds\rustc-1.3
0001BE90  35 2E 30 2D 73 72 63 5C   73 72 63 5C 6C 69 62 63     5.0-src\src\libc
0001BEA0  6F 72 65 5C 66 6D 74 5C   6D 6F 64 2E 72 73 00 00     ore\fmt\mod.rs..
0001BEB0  70 CE 41 00 3E 00 00 00   63 01 00 00 13 00 00 00     p.A.>...c.......
0001BEC0  C0 CE 41 00 00 00 00 00   00 00 00 00 00 00 00 00     ..A.............
0001BED0  3A 20 00 00 C0 CE 41 00   00 00 00 00 D0 CE 41 00     :.....A.......A.</pre>
<p>All the paths I could find were related to the path of the compiler and not that of the project. If you&#8217;re bothered by them, it&#8217;s easy to write a simple Python script to zero them out as a post-build step.</p>
<p>Now we could be ready, save for the fact that the libc wasn&#8217;t linked statically into our executable. If we take a look at the import table, we can see the ugly imports produced by newer versions of Visual Studio.</p>
<p><center><img decoding="async" src="/wp-content/uploads/2019/rustclosed/imports.png"/></center></p>
<p>To solve this we need to invoke rustc like this:</p>
<pre lang="batch">rustc -C target-feature=+crt-static ...</pre>
<p>I found the relevant documentation for this <a href="https://github.com/rust-lang/rfcs/blob/master/text/1721-crt-static.md">here</a>. But we want to specify this flag for cargo. We can achieve this by setting the environment variable RUSTFLAGS:</p>
<pre lang="batch">RUSTFLAGS='-C target-feature=+crt-static'</pre>
<p>So I modified my batch script like so:</p>
<pre lang="batch">SET RUSTFLAGS=-C target-feature=+crt-static
SET PATH=%PATH%;C:\[...]\rustc-1.35.0-src\build\i686-pc-windows-msvc\stage2\bin
%comspec% /k "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat"</pre>
<p>Now after the build process, we end up with a bigger executable and no external dependencies apart from kernel32. Perfect!</p>
<p>At this point we only have to strip the debug directory from the PE. We can do this by using a simple script for Cerbero Suite or CFF Explorer. To be honest for my programs I still use a CFF Explorer script and never bothered writing one for Cerbero.</p>
<pre lang="lua">function fixdbg(name)
    local h = OpenFile(dir .. "\\" .. name)
    if h == null then
        MsgBox("fail")
        return
    end
    RemoveDebugDirectory(h)
    UpdateChecksum(h)
    SaveFile(h)
    -- don't close, otherwise it fails, don't know why
end

dir = GetCurrentDirectory()

-- list of files to fix
fixdbg("app.exe")</pre>
<p>You can call this script fixdbg.cff and launch it directly as the cff extension is associated to CFF Explorer. This can be arranged as a post-build step.</p>
<p>Let&#8217;s finish this nicely by maximizing compatibility. Now that we have a clean, statically-linked executable, we can try to make it run on XP. We just need to modify some fields in the Optional Header of the Portable Executable.</p>
<p><center><img decoding="async" src="/wp-content/uploads/2019/rustclosed/majver.png"/></center></p>
<p>We modify these fields as follows:</p>
<p><strong>MajorOperatingSystemVersion</strong>: 5<br />
<strong>MinorOperatingSystemVersion</strong>: 0<br />
<strong>MajorSubsystemVersion</strong>: 5<br />
<strong>MinorSubsystemVersion</strong>: 0</p>
<p>And now it&#8217;s time to try&#8230;</p>
<p><center><img decoding="async" src="/wp-content/uploads/2019/rustclosed/xp.png"/></center></p>
<p>We have a stripped Rust executable built with the latest stable Rust compiler and Visual Studio 2017 running on Windows XP!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ntcore.com/rust-for-closed-source-projects/feed/</wfw:commentRss>
			<slash:comments>9</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">641</post-id>	</item>
		<item>
		<title>Porting a CHIP-8 emulator to Rust</title>
		<link>https://ntcore.com/porting-a-chip-8-emulator-to-rust/</link>
					<comments>https://ntcore.com/porting-a-chip-8-emulator-to-rust/#comments</comments>
		
		<dc:creator><![CDATA[Erik Pistelli]]></dc:creator>
		<pubDate>Fri, 15 Feb 2019 18:24:07 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<guid isPermaLink="false">https://ntcore.com/?p=594</guid>

					<description><![CDATA[I&#8217;ve been meaning to learn the Rust language for quite some years and found only now the time to start this endeavor. I must say it has probably been for the best, as the language has clearly matured a lot since the last time I looked into it. As a first project to try out &#8230; <a href="https://ntcore.com/porting-a-chip-8-emulator-to-rust/" class="more-link">Continue reading<span class="screen-reader-text"> "Porting a CHIP-8 emulator to Rust"</span></a>]]></description>
										<content:encoded><![CDATA[<p>I&#8217;ve been meaning to learn the Rust language for quite some years and found only now the time to start this endeavor. I must say it has probably been for the best, as the language has clearly matured a lot since the last time I looked into it.</p>
<p>As a first project to try out Rust I ported Laurence Muller&#8217;s <a href="http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/">CHIP-8 emulator</a> to it. It&#8217;s a simple C++ project and it took me only a day to port it to Rust.</p>
<p>You can download my port from <a href="https://github.com/epistelli/dale8">GitHub</a>.</p>
<p><center><img decoding="async" src="/wp-content/uploads/2019/dale8/dale8.png"/></center></p>
<p>There&#8217;s not much to write about the project itself apart that the original code used GLUT and the port uses SDL2. I also implemented basic audio support, but didn&#8217;t work on providing a realistic clock speed.</p>
<p>I can&#8217;t yet write something exhaustive about Rust, because I&#8217;m still learning it. What I can say up until now is that, apart some minor things which I dislike (snake-case: ugh), it seems fun to program in it. The amount of rules make the programming a bit more challenging, but it pays off in satisfaction once everything builds without complaints.</p>
<p>The only thing I can say is that I can&#8217;t clearly see a use-case for Rust. Yes, it&#8217;s a solid and secure language which is fun to use. But will it be used in the real world? I can see many use-cases for Go, not so many for Rust. What I hope is for Rust to mature some more and then to become stable, without going down the path to insanity like modern C++.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ntcore.com/porting-a-chip-8-emulator-to-rust/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">594</post-id>	</item>
		<item>
		<title>Batch image manipulation using Python and GIMP</title>
		<link>https://ntcore.com/batch-image-manipulation-using-python-and-gimp/</link>
					<comments>https://ntcore.com/batch-image-manipulation-using-python-and-gimp/#comments</comments>
		
		<dc:creator><![CDATA[Erik Pistelli]]></dc:creator>
		<pubDate>Fri, 03 Aug 2018 17:04:19 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<guid isPermaLink="false">https://ntcore.com/?p=509</guid>

					<description><![CDATA[Not a very common topic for me, but I thought it could be neat to mention some tips &#038; tricks. I won&#8217;t go into the details of the Python GIMP SDK, most of it can be figured out from the GIMP documentation. I spent a total of one hour researching this topic, so I&#8217;m not &#8230; <a href="https://ntcore.com/batch-image-manipulation-using-python-and-gimp/" class="more-link">Continue reading<span class="screen-reader-text"> "Batch image manipulation using Python and GIMP"</span></a>]]></description>
										<content:encoded><![CDATA[<p>Not a very common topic for me, but I thought it could be neat to mention some tips &#038; tricks. I won&#8217;t go into the details of the Python GIMP SDK, most of it can be figured out from the <a href="https://www.gimp.org/docs/python/">GIMP documentation</a>. I spent a total of one hour researching this topic, so I&#8217;m not an expert and I could have made mistakes, but perhaps I can save some effort to others which want to achieve the same results. You can jump to the end of the tutorial to find a nice skeleton batch script if you&#8217;re not interested in reading the theory.</p>
<p>To those wondering why GIMP, it&#8217;s because I created a new icon for Profiler and wanted to automatize some operations on it in order to have it in all sizes and flavors I need. One of the produced images had to be semi-transparent. So I thought, why not using a GIMP batch command, since anyway GIMP is installed on most Linux systems by default?</p>
<p>Just to mention, GIMP supports also a Lisp syntax to write scripts, but it caused my eyes to bleed profusely, so I didn&#8217;t even take into it consideration and focused directly on Python.</p>
<p>Of course, I could&#8217;ve tried other solutions like PIL (Python Imaging Library) which I have used in the past. But GIMP is actually nice, you can do many complex UI operations from code and you also have an interactive Python shell to test your code live on an image.</p>
<p>For example, open an image in  GIMP, then open the Python console from Filters -> Python-Fu -> Console and execute the following code:</p>
<pre lang="python">img = gimp.image_list()[0]
img.layers[0].opacity = 50.0</pre>
<p>And you&#8217;ll see that the image is now halfway transparent. What the code does is to take the first image from the list of open images and sets the opacity of the first layer to 50%.</p>
<p>This is the nice thing about GIMP scripting: it lets you manipulate layers just like in the UI. This allows for very powerful scripting capabilities.</p>
<p>The first small issue I&#8217;ve encountered in my attempt to write a batch script, is that GIMP only accepts Python code as command line argument, not the path to a script on disk. According to the official documentation:</p>
<p><em>GIMP Python All this means that you could easily invoke a GIMP Python plug-in such as the one above directly from your shell using the (plug-in-script- fu-eval …) evaluator:</p>
<p>gimp &#8211;no-interface &#8211;batch &#8216;(python-fu-console-echo RUN-NONINTERACTIVE &#8220;another string&#8221; 777 3.1416 (list 1 0 0))&#8217; &#8216;(gimp-quit 1)&#8217;</em></p>
<p>The idea behind it is that you create a GIMP plugin script, put it in the GIMP plugin directory, register methods like in the following small example script:</p>
<pre lang="python">#! /usr/bin/env python
from gimpfu import *

def echo(*args):
  """Print the arguments on standard output"""
  print "echo:", args

register(
  "console_echo", "", "", "", "", "",
  "<Toolbox>/Xtns/Languages/Python-Fu/Test/_Console Echo", "",
  [
  (PF_STRING, "arg0", "argument 0", "test string"),
  (PF_INT,    "arg1", "argument 1", 100          ),
  (PF_FLOAT,  "arg2", "argument 2", 1.2          ),
  (PF_COLOR,  "arg3", "argument 3", (0, 0, 0)    ),
  ],
  [],
  echo
  )

main()</pre>
<p>And then invoke the registered method from the command line as explained above.</p>
<p>I noticed many threads on stackoverflow.com where people were trying to figure out how to execute a batch script from the command line. Now, the obvious solution which came to my mind is to execute Python code from the command line which prepends the current path to the sys.path and then to import the batch script. So I searched and found that solution suggested by the user xenoid in this <a href="https://stackoverflow.com/questions/44430081/how-to-run-python-scripts-using-gimpfu-from-windows-command-line">stackoverflow thread</a>.</p>
<p>So the final code for my case would be:</p>
<pre lang="bash">gimp-console -idf --batch-interpreter python-fu-eval -b "import sys;sys.path=['.']+sys.path;import batch;batch.setOpacity('appicon.png', 50)" -b "pdb.gimp_quit(1)"</pre>
<p>And:</p>
<pre lang="python">from gimpfu import *

def setOpacity(fname, level):
    img = pdb.gimp_file_load(fname, fname)
    img.layers[0].opacity = float(level)
    img.merge_visible_layers(NORMAL_MODE)
    pdb.file_png_save(img, img.layers[0], fname, fname, 0, 9, 1, 0, 0, 1, 1)
    pdb.gimp_image_delete(img)</pre>
<p>What took me most to understand was to call the method <strong>merge_visible_layers</strong> before saving the image. Initially, I was trying to do it without calling it and the saved image was not transparent at all. So I thought the opacity was not correctly set and tried to do it with other methods like calling <strong>gimp_layer_set_opacity</strong>, but without success.</p>
<p>I then tried in the console and noticed that the opacity is actually set correctly, but that that information is lost when saving the image to disk. I then found the image method <strong>flatten</strong> and noticed that the transparency was retained, but unfortunately the saved PNG background was now white and no longer transparent. So I figured that there had to be a method to obtain a similar result but without losing the transparent background. Looking a bit among the methods in the SDK I found <strong>merge_visible_layers</strong>. I think it&#8217;s important to point this out, in case you experience the same issue and can&#8217;t find a working solution just like it happened to me.</p>
<p>Now we have a working solution, but let&#8217;s create a more elegant one, which allows use to use GIMP from within the same script, without any external invocation.</p>
<pre lang="python">#!/usr/bin/env python

def setOpacity(fname, level):
    img = pdb.gimp_file_load(fname, fname)
    img.layers[0].opacity = float(level)
    img.merge_visible_layers(NORMAL_MODE)
    pdb.file_png_save(img, img.layers[0], fname, fname, 0, 9, 1, 0, 0, 1, 1)
    pdb.gimp_image_delete(img)

# GIMP auto-execution stub
if __name__ == "__main__":
    import os, sys, subprocess
    if len(sys.argv) < 2:
        print("you must specify a function to execute!")
        sys.exit(-1)
    scrdir = os.path.dirname(os.path.realpath(__file__))
    scrname = os.path.splitext(os.path.basename(__file__))[0]
    shcode = "import sys;sys.path.insert(0, '" + scrdir + "');import " + scrname + ";" + scrname + "." + sys.argv[1] + str(tuple(sys.argv[2:]))
    shcode = "gimp-console -idf --batch-interpreter python-fu-eval -b \"" + shcode + "\" -b \"pdb.gimp_quit(1)\""
    sys.exit(subprocess.call(shcode, shell=True))
else:
    from gimpfu import *</pre>
<p>We can now call our function simply like this:</p>
<pre lang="bash">./batch.py setOpacity appicon.png 50.0</pre>
<p>Which looks very pretty to me.</p>
<p>I could go on showing other nice examples of image manipulation, but the gist of the tutorial was just this. However, GIMP has a rich SDK which allows to automatize very complex operations.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ntcore.com/batch-image-manipulation-using-python-and-gimp/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">509</post-id>	</item>
		<item>
		<title>Time Travel: Running Python 3.7 on XP</title>
		<link>https://ntcore.com/time-travel-running-python-3-7-on-xp/</link>
					<comments>https://ntcore.com/time-travel-running-python-3-7-on-xp/#comments</comments>
		
		<dc:creator><![CDATA[Erik Pistelli]]></dc:creator>
		<pubDate>Mon, 23 Jul 2018 00:16:45 +0000</pubDate>
				<category><![CDATA[Article]]></category>
		<category><![CDATA[Internals]]></category>
		<guid isPermaLink="false">https://ntcore.com/?p=458</guid>

					<description><![CDATA[To restart my career as a technical writer, I chose a light topic. Namely, running applications compiled with new versions of Visual Studio on Windows XP. I didn&#8217;t find any prior research on the topic, but I also didn&#8217;t search much. There&#8217;s no real purpose behind this article, beyond the fact that I wanted to &#8230; <a href="https://ntcore.com/time-travel-running-python-3-7-on-xp/" class="more-link">Continue reading<span class="screen-reader-text"> "Time Travel: Running Python 3.7 on XP"</span></a>]]></description>
										<content:encoded><![CDATA[<p>To restart my career as a technical writer, I chose a light topic. Namely, running applications compiled with new versions of Visual Studio on Windows XP. I didn&#8217;t find any prior research on the topic, but I also didn&#8217;t search much. There&#8217;s no real purpose behind this article, beyond the fact that I wanted to know what could prevent a new application to run on XP. Our target application will be the <a href="https://www.python.org/ftp/python/3.7.0/python-3.7.0-embed-win32.zip">embedded version of Python 3.7</a> for x86.</p>
<p>If we try to start any new application on XP, we&#8217;ll get an error message informing us that it is not a valid Win32 application. This happens because of some fields in the Optional Header of the Portable Executable.</p>
<p><center><img decoding="async" src="/wp-content/uploads/2018/xptm/majver.png"/></center></p>
<p>Most of you probably already know that you need to adjust these fields as follows:</p>
<p><strong>MajorOperatingSystemVersion</strong>: 5<br />
<strong>MinorOperatingSystemVersion</strong>: 0<br />
<strong>MajorSubsystemVersion</strong>: 5<br />
<strong>MinorSubsystemVersion</strong>: 0</p>
<p>Fortunately, it&#8217;s enough to adjust the fields in the executable we want to start (python.exe), there&#8217;s no need to adjust the DLLs as well.</p>
<p>If we try run the application now, we&#8217;ll get an error message due to a missing API in kernel32. So let&#8217;s turn our attention to the imports.</p>
<p><center><img decoding="async" src="/wp-content/uploads/2018/xptm/imports.png"/></center></p>
<p>We have a missing vcruntime140.dll, then a bunch of &#8220;api-ms-win-*&#8221; DLLs, then only python37.dll and kernel32.dll.</p>
<p>The first thing which comes to mind is that in new applications we often find these &#8220;api-ms-win-*&#8221; DLLs. If we search for the prefix in the Windows directory, we&#8217;ll find a directory both in System32 and SysWOW64 called &#8220;downlevel&#8221;, which contains a huge list of these DLLs. </p>
<p><center><img decoding="async" src="/wp-content/uploads/2018/xptm/downlevel.png"/></center></p>
<p>As we&#8217;ll see later, these DLLs aren&#8217;t actually used, but if we open one with a PE viewer, we&#8217;ll see that it contains exclusively forwarders to APIs contained in the usual suspects such as kernel32, kernelbase, user32 etc.</p>
<p><center><img decoding="async" src="/wp-content/uploads/2018/xptm/forwarders.png"/></center></p>
<p>There&#8217;s a <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/dn764993(v=vs.85).aspx">MSDN page</a> documenting these DLLs.</p>
<p>Interestingly, in the downlevel directory we can&#8217;t find any of the files imported by python.exe. These DLLs actually expose C runtime APIs like <strong>strlen</strong>, <strong>fopen</strong>, <strong>exit</strong> and so on.</p>
<p>If we don&#8217;t have any prior knowledge on the topic and just do a string search inside the Windows directory for such a DLL name, we&#8217;ll find a match in C:\Windows\System32\apisetschema.dll. This DLL is special as it contains a .apiset section, whose data can easily be identified as some sort of format for mapping &#8220;api-ms-win-*&#8221; names to others.</p>
<pre lang="hex">Offset     0  1  2  3  4  5  6  7    8  9  A  B  C  D  E  F     Ascii   

00013AC0  C8 3A 01 00 20 00 00 00   73 00 74 00 6F 00 72 00     .:......s.t.o.r.
00013AD0  61 00 67 00 65 00 75 00   73 00 61 00 67 00 65 00     a.g.e.u.s.a.g.e.
00013AE0  2E 00 64 00 6C 00 6C 00   65 00 78 00 74 00 2D 00     ..d.l.l.e.x.t.-.
00013AF0  6D 00 73 00 2D 00 77 00   69 00 6E 00 2D 00 73 00     m.s.-.w.i.n.-.s.
00013B00  78 00 73 00 2D 00 6F 00   6C 00 65 00 61 00 75 00     x.s.-.o.l.e.a.u.
00013B10  74 00 6F 00 6D 00 61 00   74 00 69 00 6F 00 6E 00     t.o.m.a.t.i.o.n.
00013B20  2D 00 6C 00 31 00 2D 00   31 00 2D 00 30 00 00 00     -.l.1.-.1.-.0...
00013B30  00 00 00 00 00 00 00 00   00 00 00 00 44 3B 01 00     ............D;..
00013B40  0E 00 00 00 73 00 78 00   73 00 2E 00 64 00 6C 00     ....s.x.s...d.l.
00013B50  6C 00 00 00 65 00 78 00   74 00 2D 00 6D 00 73 00     l...e.x.t.-.m.s.</pre>
<p>Searching on the web, the first resource I found on this topic were two articles on the blog of Quarkslab (<a href="https://blog.quarkslab.com/runtime-dll-name-resolution-apisetschema-part-i.html">Part 1</a> and <a href="https://blog.quarkslab.com/runtime-dll-name-resolution-apisetschema-part-ii.html">Part 2</a>). However, I quickly figured that, while useful, they were too dated to provide me with up-to-date structures to parse the data. In fact, the second article shows a version number of 2 and at the time of my writing the version number is 6.</p>
<pre lang="hex">Offset     0  1  2  3  4  5  6  7    8  9  A  B  C  D  E  F     Ascii   

00000000  06 00 00 00                                           ....            </pre>
<p>Just for completeness, after the publication of the current article, I was made aware of an <a href="http://www.vxsecurity.sg/2012/02/14/apimapset-deroko-of-arteam/">article by deroko</a> about the topic predating those of Quarkslab.</p>
<p>Anyway, I searched some more and found a code snippet by Alex Ionescu and Pavel Yosifovich in the repository of <a href="https://github.com/zodiacon/WindowsInternals/blob/master/APISetMap/ApiSet.h">Windows Internals</a>. I took the following structures from there.</p>
<pre lang="c">typedef struct _API_SET_NAMESPACE {
	ULONG Version;
	ULONG Size;
	ULONG Flags;
	ULONG Count;
	ULONG EntryOffset;
	ULONG HashOffset;
	ULONG HashFactor;
} API_SET_NAMESPACE, *PAPI_SET_NAMESPACE;

typedef struct _API_SET_HASH_ENTRY {
	ULONG Hash;
	ULONG Index;
} API_SET_HASH_ENTRY, *PAPI_SET_HASH_ENTRY;

typedef struct _API_SET_NAMESPACE_ENTRY {
	ULONG Flags;
	ULONG NameOffset;
	ULONG NameLength;
	ULONG HashedLength;
	ULONG ValueOffset;
	ULONG ValueCount;
} API_SET_NAMESPACE_ENTRY, *PAPI_SET_NAMESPACE_ENTRY;

typedef struct _API_SET_VALUE_ENTRY {
	ULONG Flags;
	ULONG NameOffset;
	ULONG NameLength;
	ULONG ValueOffset;
	ULONG ValueLength;
} API_SET_VALUE_ENTRY, *PAPI_SET_VALUE_ENTRY;</pre>
<p>The data starts with a <strong>API_SET_NAMESPACE</strong> structure.</p>
<p><center><img decoding="async" src="/wp-content/uploads/2018/xptm/apisetschemaformat.png"/></center></p>
<p><strong>Count</strong> specifies the number of <strong>API_SET_NAMESPACE_ENTRY</strong> and <strong>API_SET_HASH_ENTRY</strong> structures. <strong>EntryOffset</strong> points to the start of the array of <strong>API_SET_NAMESPACE_ENTRY</strong> structures, which in our case comes exactly after <strong>API_SET_NAMESPACE</strong>.</p>
<p>Every <strong>API_SET_NAMESPACE_ENTRY</strong> points to the name of the &#8220;api-ms-win-*&#8221; DLL via the <strong>NameOffset</strong> field, while <strong>ValueOffset</strong> and <strong>ValueCount</strong> specify the position and count of <strong>API_SET_VALUE_ENTRY</strong> structures. The <strong>API_SET_VALUE_ENTRY</strong> structure yields the resolution values (e.g. kernel32.dll, kernelbase.dll) for the given &#8220;api-ms-win-*&#8221; DLL.</p>
<p>With this information we can already write a small script to map the new names to the actual DLLs.</p>
<pre lang="python">import os
from Pro.Core import *
from Pro.PE import *

def main():
    c = createContainerFromFile("C:\\Windows\\System32\\apisetschema.dll")
    pe = PEObject()
    if not pe.Load(c):
        print("couldn't load apisetschema.dll")
        return
    sect = pe.SectionHeaders()
    nsects = sect.Count()
    d = None
    for i in range(nsects):
        if sect.Bytes(0) == b".apiset\x00":
            cs = pe.SectionData(i)[0]
            d = CFFObject()
            d.Load(cs)
            break
        sect = sect.Add(1)
    if not d:
        print("could find .apiset section")
        return
    n, ret = d.ReadUInt32(12)
    offs, ret = d.ReadUInt32(16)
    for i in range(n):
        name_offs, ret = d.ReadUInt32(offs + 4)
        name_size, ret = d.ReadUInt32(offs + 8)
        name = d.Read(name_offs, name_size).decode("utf-16")
        line = str(i) + ") " + name + " ->"
        values_offs, ret = d.ReadUInt32(offs + 16)
        value_count, ret = d.ReadUInt32(offs + 20)
        for j in range(value_count):
            vname_offs, ret = d.ReadUInt32(values_offs + 12)
            vname_size, ret = d.ReadUInt32(values_offs + 16)
            vname = d.Read(vname_offs, vname_size).decode("utf-16")
            line += " " + vname
            values_offs += 20
        offs += 24
        print(line)
     
main()</pre>
<p>This code can be executed with Cerbero Profiler from command line as &#8220;cerpro.exe -r apisetschema.py&#8221;. These are the first lines of the produced output:</p>
<pre lang="text">0) api-ms-onecoreuap-print-render-l1-1-0 -> printrenderapihost.dll
1) api-ms-onecoreuap-settingsync-status-l1-1-0 -> settingsynccore.dll
2) api-ms-win-appmodel-identity-l1-2-0 -> kernel.appcore.dll
3) api-ms-win-appmodel-runtime-internal-l1-1-3 -> kernel.appcore.dll
4) api-ms-win-appmodel-runtime-l1-1-2 -> kernel.appcore.dll
5) api-ms-win-appmodel-state-l1-1-2 -> kernel.appcore.dll
6) api-ms-win-appmodel-state-l1-2-0 -> kernel.appcore.dll
7) api-ms-win-appmodel-unlock-l1-1-0 -> kernel.appcore.dll
8) api-ms-win-base-bootconfig-l1-1-0 -> advapi32.dll
9) api-ms-win-base-util-l1-1-0 -> advapi32.dll
10) api-ms-win-composition-redirection-l1-1-0 -> dwmredir.dll
11) api-ms-win-composition-windowmanager-l1-1-0 -> udwm.dll
12) api-ms-win-core-apiquery-l1-1-0 -> ntdll.dll
13) api-ms-win-core-appcompat-l1-1-1 -> kernelbase.dll
14) api-ms-win-core-appinit-l1-1-0 -> kernel32.dll kernelbase.dll
...</pre>
<p>Going back to <strong>API_SET_NAMESPACE</strong>, its field <strong>HashOffset</strong> points to an array of <strong>API_SET_HASH_ENTRY</strong> structures. These structures, as we&#8217;ll see in a moment, are used by the Windows loader to quickly index a &#8220;api-ms-win-*&#8221; DLL name. The <strong>Hash</strong> field is effectively the hash of the name, calculated by taking into consideration both <strong>HashFactor</strong> and <strong>HashedLength</strong>, while <strong>Index</strong> points to the associated <strong>API_SET_NAMESPACE_ENTRY</strong> entry.</p>
<p>The code which does the hashing is inside the function <strong>LdrpPreprocessDllName</strong> in ntdll:</p>
<pre lang="x86">77EA1DAC mov       ebx, dword ptr [ebx + 0x18]      ; HashFactor in ebx 
77EA1DAF mov       esi, eax                         ; esi = dll name length                    
77EA1DB1 movzx     eax, word ptr [edx]              ; one unicode character into eax
77EA1DB4 lea       ecx, dword ptr [eax - 0x41]      ; ecx = character - 0x41
77EA1DB7 cmp       cx, 0x19                         ; compare to 0x19
77EA1DBB jbe       0x77ea2392                       ; if below or equal, bail out
77EA1DC1 mov       ecx, ebx                         ; ecx = HashFactor
77EA1DC3 movzx     eax, ax
77EA1DC6 imul      ecx, edi                         ; ecx *= edi
77EA1DC9 add       edx, 2                           ; edx += 2
77EA1DCC add       ecx, eax                         ; ecx += eax
77EA1DCE mov       edi, ecx                         ; edi = ecx
77EA1DD0 sub       esi, 1                           ; len -= 1
77EA1DD3 jne       0x77ea1db1                       ; if not zero repeat from 77EA1DB1</pre>
<p>Or more simply in C code:</p>
<pre lang="c">const char *p = dllname;
int HashedLength = 0x23;
int HashFactor = 0x1F;
int Hash = 0;
for (int i = 0; i < HashedLength; i++, p++)
    Hash = (Hash * HashFactor) + *p;</pre>
<p>As a practical example, let's take the DLL name "api-ms-win-core-processthreads-l1-1-2.dll". Its hash would be 0x445B4DF3. If we find its matching <strong>API_SET_HASH_ENTRY</strong> entry, we'll have the <strong>Index</strong> to the associated <strong>API_SET_NAMESPACE_ENTRY</strong> structure.</p>
<pre lang="hex">Offset     0  1  2  3  4  5  6  7    8  9  A  B  C  D  E  F     Ascii   

00014DA0                                        F3 4D 5B 44                 .M[D
00014DB0  5B 00 00 00                                           [...            </pre>
<p>So, 0x5b (or 91) is the index. By going back to the output of mappings, we can see that it matches.</p>
<pre lang="text">91) api-ms-win-core-processthreads-l1-1-3 -> kernel32.dll kernelbase.dll</pre>
<p>By inspecting the same output, we can also notice that all C runtime DLLs are resolved to ucrtbase.dll.</p>
<pre lang="text">167) api-ms-win-crt-conio-l1-1-0 -> ucrtbase.dll
168) api-ms-win-crt-convert-l1-1-0 -> ucrtbase.dll
169) api-ms-win-crt-environment-l1-1-0 -> ucrtbase.dll
170) api-ms-win-crt-filesystem-l1-1-0 -> ucrtbase.dll
171) api-ms-win-crt-heap-l1-1-0 -> ucrtbase.dll
172) api-ms-win-crt-locale-l1-1-0 -> ucrtbase.dll
173) api-ms-win-crt-math-l1-1-0 -> ucrtbase.dll
174) api-ms-win-crt-multibyte-l1-1-0 -> ucrtbase.dll
175) api-ms-win-crt-private-l1-1-0 -> ucrtbase.dll
176) api-ms-win-crt-process-l1-1-0 -> ucrtbase.dll
177) api-ms-win-crt-runtime-l1-1-0 -> ucrtbase.dll
178) api-ms-win-crt-stdio-l1-1-0 -> ucrtbase.dll
179) api-ms-win-crt-string-l1-1-0 -> ucrtbase.dll
180) api-ms-win-crt-time-l1-1-0 -> ucrtbase.dll
181) api-ms-win-crt-utility-l1-1-0 -> ucrtbase.dll</pre>
<p>I was already resigned at having to figure out how to support the C runtime on XP, when I noticed that Microsoft actually supports the deployment of the runtime on it. The following excerpt from <a href="https://blogs.msdn.microsoft.com/vcblog/2015/03/03/introducing-the-universal-crt/">MSDN</a> says as much:</p>
<p><em>If you currently use the VCRedist (our redistributable package files), then things will just work for you as they did before. The Visual Studio 2015 VCRedist package includes the above mentioned Windows Update packages, so simply installing the VCRedist will install both the Visual C++ libraries and the Universal CRT. This is our recommended deployment mechanism. <u>On Windows XP, for which there is no Universal CRT Windows Update MSU, the VCRedist will deploy the Universal CRT itself.</u></em></p>
<p>Which means that on Windows editions coming after XP the support is provided via Windows Update, but on XP we have to deploy the files ourselves. We can find the files to deploy inside C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs. This path contains three sub-directories: x86, x64 and arm. We're obviously interested in the x86 one. The files contained in it are many (42), apparently the most common "api-ms-win-*" DLLs and ucrtbase.dll. We can deploy those files onto XP to make our application work. We are still missing the vcruntime140.dll, but we can take that DLL from the Visual C++ installation. In fact, that DLL is intended to be deployed, while the Universal CRT (ucrtbase.dll) is intended to be part of the Windows system.</p>
<p>This satisfies our dependencies in terms of DLLs. However, Windows introduced many new APIs over the years which aren't present on XP. So I wrote a script to test the compatibility of an application by checking the imported APIs against the API exported by the DLLs on XP. The command line for it is "cerpro.exe -r xpcompat.py application_path". It will check all the PE files in the specified directory.</p>
<pre lang="python">import os, sys
from Pro.Core import *
from Pro.PE import *

xp_system32 = "C:\\Users\\Admin\\Desktop\\system32"
apisetschema = { "OMITTED FOR BREVITY" }
cached_apis = {}
missing_result = {}

def getAPIs(dllpath):
    apis = {}
    c = createContainerFromFile(dllpath)
    dll = PEObject()
    if not dll.Load(c):
        print("error: couldn't load dll")
        return apis
    ordbase = dll.ExportDirectory().Num("Base")
    functions = dll.ExportDirectoryFunctions()
    names = dll.ExportDirectoryNames()
    nameords = dll.ExportDirectoryNameOrdinals()
    n = functions.Count()
    it = functions.iterator()
    for x in range(n):
        func = it.next()
        ep = func.Num(0)
        if ep == 0:
            continue
        apiord = str(ordbase + x)
        n2 = nameords.Count()
        it2 = nameords.iterator()
        name_found = False
        for y in range(n2):
            no = it2.next()
            if no.Num(0) == x:
                name = names.At(y)
                offs = dll.RvaToOffset(name.Num(0))
                name, ret = dll.ReadUInt8String(offs, 500)
                apiname = name.decode("ascii")
                apis[apiname] = apiord
                apis[apiord] = apiname
                name_found = True
                break
        if not name_found:
            apis[apiord] = apiord
    return apis
    
def checkMissingAPIs(pe, ndescr, dllname, xpdll_apis):
    ordfl = pe.ImportOrdinalFlag()
    ofts = pe.ImportThunks(ndescr)
    it = ofts.iterator()
    while it.hasNext():
        ft = it.next().Num(0)
        if (ft & ordfl) != 0:
            name = str(ft ^ ordfl)
        else:
            offs = pe.RvaToOffset(ft)
            name, ret = pe.ReadUInt8String(offs + 2, 400)
            if not ret:
                continue
            name = name.decode("ascii")
        if not name in xpdll_apis:
            print("       ", "missing:", name)
            temp = missing_result.get(dllname, set())
            temp.add(name)
            missing_result[dllname] = temp

def verifyXPCompatibility(fname):
    print("file:", fname)
    c = createContainerFromFile(fname)
    pe = PEObject()
    if not pe.Load(c):
        return
    it = pe.ImportDescriptors().iterator()
    ndescr = -1
    while it.hasNext():
        descr = it.next()
        ndescr += 1
        offs = pe.RvaToOffset(descr.Num("Name"))
        name, ret = pe.ReadUInt8String(offs, 400)
        if not ret:
            continue
        name = name.decode("ascii").lower()
        if not name.endswith(".dll"):
            continue
        fwdlls = apisetschema.get(name[:-4], [])
        if len(fwdlls) == 0:
            print("   ", name)
        else:
            fwdll = fwdlls[0]
            print("   ", name, "->", fwdll)
            name = fwdll
        if name == "ucrtbase.dll":
            continue
        xpdll_path = os.path.join(xp_system32, name)
        if not os.path.isfile(xpdll_path):
            continue
        if not name in cached_apis:
            cached_apis[name] = getAPIs(xpdll_path)
        checkMissingAPIs(pe, ndescr, name, cached_apis[name])
    print()
        
def main():
    if os.path.isfile(sys.argv[1]):
        verifyXPCompatibility(sys.argv[1])
    else:
        files = [os.path.join(dp, f) for dp, dn, fn in os.walk(sys.argv[1]) for f in fn]
        for fname in files:
            with open(fname, "rb") as f:
                if f.read(2) == b"MZ":
                    verifyXPCompatibility(fname)
            
    # summary
    n = 0
    print("\nsummary:")
    for rdll, rapis in missing_result.items():
        print("   ", rdll)
        for rapi in rapis:
            print("       ", "missing:", rapi)
            n += 1
    print("total of missing APIs:", str(n))

main()</pre>
<p>I had to omit the contents of the <strong>apisetschema</strong> global variable for the sake of brevity. You can download the full script from <a href="https://github.com/epistelli/xptmrt/blob/master/scripts/xpcompat.py">here</a>. The system32 directory referenced in the code is the one of Windows XP, which I copied to my desktop.</p>
<p>And here are the relevant excerpts from the output:</p>
<pre lang="text">file: python-3.7.0-embed-win32\python37.dll
    version.dll
    shlwapi.dll
    ws2_32.dll
    kernel32.dll
        missing: GetFinalPathNameByHandleW
        missing: InitializeProcThreadAttributeList
        missing: UpdateProcThreadAttribute
        missing: DeleteProcThreadAttributeList
        missing: GetTickCount64
    advapi32.dll
    vcruntime140.dll
    api-ms-win-crt-runtime-l1-1-0.dll -> ucrtbase.dll
    api-ms-win-crt-math-l1-1-0.dll -> ucrtbase.dll
    api-ms-win-crt-locale-l1-1-0.dll -> ucrtbase.dll
    api-ms-win-crt-string-l1-1-0.dll -> ucrtbase.dll
    api-ms-win-crt-stdio-l1-1-0.dll -> ucrtbase.dll
    api-ms-win-crt-convert-l1-1-0.dll -> ucrtbase.dll
    api-ms-win-crt-time-l1-1-0.dll -> ucrtbase.dll
    api-ms-win-crt-environment-l1-1-0.dll -> ucrtbase.dll
    api-ms-win-crt-process-l1-1-0.dll -> ucrtbase.dll
    api-ms-win-crt-heap-l1-1-0.dll -> ucrtbase.dll
    api-ms-win-crt-conio-l1-1-0.dll -> ucrtbase.dll
    api-ms-win-crt-filesystem-l1-1-0.dll -> ucrtbase.dll

[...]

file: python-3.7.0-embed-win32\_socket.pyd
    ws2_32.dll
        missing: inet_ntop
        missing: inet_pton
    kernel32.dll
    python37.dll
    vcruntime140.dll
    api-ms-win-crt-runtime-l1-1-0.dll -> ucrtbase.dll

[...]

summary:
    kernel32.dll
        missing: InitializeProcThreadAttributeList
        missing: GetTickCount64
        missing: GetFinalPathNameByHandleW
        missing: UpdateProcThreadAttribute
        missing: DeleteProcThreadAttributeList
    ws2_32.dll
        missing: inet_pton
        missing: inet_ntop
total of missing APIs: 7</pre>
<p>We're missing 5 APIs from kernel32.dll and 2 from ws2_32.dll, but the Winsock APIs are imported just by _socket.pyd, a module which is loaded only when a network operation is performed by Python. So, in theory, we can focus our efforts on the missing kernel32 APIs for now.</p>
<p>My plan was to create a fake kernel32.dll, called xernel32.dll, containing forwarders for most APIs and real implementations only for the missing ones. Here's a script to create C++ files containing forwarders for all APIs of common DLLs on Windows 10:</p>
<pre lang="python">import os, sys
from Pro.Core import *
from Pro.PE import *

xpsys32path = "C:\\Users\\Admin\\Desktop\\system32"
sys32path = "C:\\Windows\\SysWOW64"

def getAPIs(dllpath):
    pass # same code as above
    
def isOrdinal(i):
    try:
        int(i)
        return True
    except:
        return False
    
def createShadowDll(name):
    xpdllpath = os.path.join(xpsys32path, name + ".dll")
    xpapis = getAPIs(xpdllpath)
    dllpath = os.path.join(sys32path, name + ".dll")
    apis = sorted(getAPIs(dllpath).keys())
    if len(apis) != 0:
        with open(name + ".cpp", "w") as f:
            f.write("#include <WinDef.h>\n\n")
            for a in apis:
                comment = " // XP" if a in xpapis else ""
                if not isOrdinal(a):
                    f.write("#pragma comment(linker, \"/export:" + a + "=" + name + "." + a + "\")" + comment + "\n")
                #
            print("created", name + ".cpp")
    
def main():
    dlls = ("advapi32", "comdlg32", "gdi32", "iphlpapi", "kernel32", "ole32", "oleaut32", "shell32", "shlwapi", "user32", "uxtheme", "ws2_32")
    for dll in dlls:
        createShadowDll(dll)

main()</pre>
<p>It creates files like the following kernel32.cpp:</p>
<pre lang="cpp">#include <WinDef.h>

#pragma comment(linker, "/export:AcquireSRWLockExclusive=kernel32.AcquireSRWLockExclusive")
#pragma comment(linker, "/export:AcquireSRWLockShared=kernel32.AcquireSRWLockShared")
#pragma comment(linker, "/export:ActivateActCtx=kernel32.ActivateActCtx") // XP
#pragma comment(linker, "/export:ActivateActCtxWorker=kernel32.ActivateActCtxWorker")
#pragma comment(linker, "/export:AddAtomA=kernel32.AddAtomA") // XP
#pragma comment(linker, "/export:AddAtomW=kernel32.AddAtomW") // XP
#pragma comment(linker, "/export:AddConsoleAliasA=kernel32.AddConsoleAliasA") // XP
#pragma comment(linker, "/export:AddConsoleAliasW=kernel32.AddConsoleAliasW") // XP
#pragma comment(linker, "/export:AddDllDirectory=kernel32.AddDllDirectory")
[...]</pre>
<p>The comment on the right ("// XP") indicates whether the forwarded API is present on XP or not. We can provide real implementations exclusively for the APIs we want. The Windows loader doesn't care whether we forward functions which don't exist as long as they aren't imported.</p>
<p>The APIs we need to support are the following:</p>
<p><strong>GetTickCount64</strong>: I just called <strong>GetTickCount</strong>, not really important<br />
<strong>GetFinalPathNameByHandleW</strong>: took the implementation from Wine, but had to adapt it slightly<br />
<strong>InitializeProcThreadAttributeList</strong>: took the implementation from Wine<br />
<strong>UpdateProcThreadAttribute</strong>: same<br />
<strong>DeleteProcThreadAttributeList</strong>: same</p>
<p>I have to be grateful to the <a href="https://www.winehq.org/">Wine project</a> here, as it provided useful implementations, which saved me the effort.</p>
<p>I called the attempt at a support runtime for older Windows versions "XP Time Machine Runtime" and you can find the <a href="https://github.com/epistelli/xptmrt">repository here</a>. I compiled it with Visual Studio 2013 and cmake.</p>
<p>So that we have now our xernel32.dll, the only thing we have to do is to rename the imported DLL inside python37.dll.</p>
<p><center><img decoding="async" src="/wp-content/uploads/2018/xptm/modimport.png"/></center></p>
<p>Let's try to start python.exe.</p>
<p><center><img decoding="async" src="/wp-content/uploads/2018/xptm/xppy.png"/></center></p>
<p>Awesome.</p>
<p>Of course, we're still not completely done, as we didn't implement the missing Winsock APIs, but perhaps this and some more could be the content of a second part to this article.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ntcore.com/time-travel-running-python-3-7-on-xp/feed/</wfw:commentRss>
			<slash:comments>22</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">458</post-id>	</item>
		<item>
		<title>NTCore revamped</title>
		<link>https://ntcore.com/ntcore-revamped/</link>
					<comments>https://ntcore.com/ntcore-revamped/#comments</comments>
		
		<dc:creator><![CDATA[Erik Pistelli]]></dc:creator>
		<pubDate>Mon, 09 Jul 2018 22:04:45 +0000</pubDate>
				<category><![CDATA[News]]></category>
		<guid isPermaLink="false">https://ntcore.com/?p=418</guid>

					<description><![CDATA[After over a decade, I finally took two afternoons to revamp this personal web-page and to merge the content of the old NTCore page with the content of its blog (rcecafe.net). All the URLs of the old web-page and blog have been preserved in the process. The people who voted for this on Twitter are &#8230; <a href="https://ntcore.com/ntcore-revamped/" class="more-link">Continue reading<span class="screen-reader-text"> "NTCore revamped"</span></a>]]></description>
										<content:encoded><![CDATA[<p>After over a decade, I finally took two afternoons to revamp this personal web-page and to merge the content of the old NTCore page with the content of its blog (rcecafe.net). All the URLs of the old web-page and blog have been preserved in the process.</p>
<p>The people who voted for this on Twitter are the guilty ones.</p>
<p>You know who you are.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ntcore.com/ntcore-revamped/feed/</wfw:commentRss>
			<slash:comments>22</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">418</post-id>	</item>
		<item>
		<title>Ctor conflicts</title>
		<link>https://ntcore.com/ctor-conflicts/</link>
					<comments>https://ntcore.com/ctor-conflicts/#comments</comments>
		
		<dc:creator><![CDATA[Erik Pistelli]]></dc:creator>
		<pubDate>Fri, 29 Nov 2013 18:24:06 +0000</pubDate>
				<category><![CDATA[Internals]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Collisions]]></category>
		<category><![CDATA[Constructor]]></category>
		<category><![CDATA[ctor]]></category>
		<guid isPermaLink="false">http://rcecafe.net/?p=305</guid>

					<description><![CDATA[Perhaps the content of this post is trivial and widely known(?), but I just spent some time fixing a bug related to the following C++ behavior. Let&#8217;s take a look at this code snippet: // main.cpp ------------------------------ #include void bar(); void foo(); int main(int argc, const char *argv[]) { bar(); foo(); return 0; } // &#8230; <a href="https://ntcore.com/ctor-conflicts/" class="more-link">Continue reading<span class="screen-reader-text"> "Ctor conflicts"</span></a>]]></description>
										<content:encoded><![CDATA[<p>Perhaps the content of this post is trivial and widely known(?), but I just spent some time fixing a bug related to the following C++ behavior.</p>
<p>Let&#8217;s take a look at this code snippet:</p>
<pre lang="cpp">// main.cpp ------------------------------

#include <stdio.h>

void bar();
void foo();

int main(int argc, const char *argv[])
{
    bar();
    foo();
    return 0;
}

// a.cpp ---------------------------------

#include <stdio.h>

struct A
{
    A() { printf("apple\n"); }
};

void bar()
{
    new A;
}

// b.cpp ---------------------------------

#include <stdio.h>

struct A
{
    A() { printf("orange\n"); }
};

void foo()
{
    new A;
}</pre>
<p>The output of the code above is:</p>
<pre lang="txt">apple
apple</pre>
<p>Whether we compile it with VC++ or g++, the result is the same.</p>
<p>The problem is that although the struct or class is declared locally the name of the constructor is considered a global symbol. So while the allocation size of the struct or class is correct, the constructor being invoked is always the first one encountered by the compiler, which in this case is the one which prints &#8216;apple&#8217;.</p>
<p>The problem here is that the compiler doesn&#8217;t warn the user in any way that the wrong constructor is being called and in a large project with hundreds of files it may very well be that two constructors collide.</p>
<p>Since namespaces are part of the name of the symbol, the code above can be fixed by adding a namespace:</p>
<pre lang="cpp">namespace N
{
struct A
{
    A() { printf("orange\n"); }
};
}
using namespace N;

void foo()
{
    new A;
}</pre>
<p>Now the correct constructor will be called.</p>
<p>I wrote a small (dumb) Python script to detect possible ctor conflicts. It just looks for struct or class declarations and reports duplicate symbol names. It&#8217;s far from perfect.</p>
<pre lang="python"># ctor_conflicts.py
import os, sys, re

source_extensions = ["h", "hxx", "hpp", "cpp", "cxx"]

symbols = { }
psym = re.compile("(typedef\\s+)?(struct|class)\\s+([a-zA-Z_][a-zA-Z0-9_]*)(\\s+)?([{])?")

def processSourceFile(fname):
    with open(fname) as f:
        content = f.readlines()
    n = len(content)
    i = 0
    while i < n:
        m = psym.search(content[i])
        i += 1
        if m == None:
            continue
        symname = m.group(3)
        # exclude some recurring symbols in different projects
        if symname == "Dialog" or symname == "MainWindow":
            continue
        # make sure a bracket is present
        if m.group(5) == None and (i >= n or content[i].startswith("{") == False):
            continue
        loc = fname + ":" + str(i)
        if symname in symbols:
            # found a possible collision
            print("Possible collision of '" + symname + "' in:")
            print(symbols[symname])
            print(loc)
            print("")
        else:
            symbols[symname] = loc

def walkFiles(path):
    for root, dirs, files in os.walk(path):
        for f in files:
            # skip SWIG wrappers
            if f.find("PyWrapWin") != -1:
                continue
            # skip Qt ui files
            if f.startswith("ui_"):
                continue
            fname = root + os.sep + f
            ext = os.path.splitext(fname)[1]
            if ext != None and len(ext) > 1 and ext[1:] in source_extensions:
                processSourceFile(fname)


if __name__ == '__main__':
    nargs = len(sys.argv)
    if nargs < 2:
        path = os.getcwd()
    else:
        path = sys.argv[1]
    walkFiles(path)
</pre>
<p>In my opinion this could be handled better on the compiler side, at least by giving a warning.</p>
<p><b>ADDENDUM:</b> Myria ‏(@Myriachan) explained the compiler internals on this one on twitter:</p>
<p><i>I'm just surprised that it doesn't cause a "duplicate symbol" linker error. Symbol flagged "weak" from being inline, maybe? [...] Member functions defined inside classes like that are automatically "inline" by C++ standard. [...] The "inline" keyword has two meanings: hint to compiler that inlining machine code may be wise, and making symbol weak. [...] Regardless of whether the compiler chooses to inline machine code within calling functions, the weak symbol part still applies. [...] It is as if all inline functions (including functions defined inside classes) have __declspec(selectany) on them, in MSVC terms. [...] Without this behavior, if you ever had a class in a header with functions defined, the compiler would either have to always inline the machine code, or you'd have to use #ifdef nonsense to avoid more than one .cpp defining the function.</i></p>
<p>The explanation is the correct one. And yes, if we define the ctor outside of the class the compiler does generate an error.</p>
<p>The logic mismatch here is that local structures in C do exist, local ctors in C++ don't. So, the correct struct is allocated but the wrong ctor is being called. Also, while the symbol is weak for the reasons explained by Myria, the compiler could still give an error if the ctor code doesn't match across files.</p>
<p>So the rule here could be: if you have local classes, avoid defining the ctor inside the class. If you already have a conflict as I did and don't want to change the code, you can fix it with a namespace as shown above.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ntcore.com/ctor-conflicts/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">305</post-id>	</item>
		<item>
		<title>Raw File System Analysis (FAT32 File Recovery)</title>
		<link>https://ntcore.com/raw-file-system-analysis-fat32-file-recovery/</link>
					<comments>https://ntcore.com/raw-file-system-analysis-fat32-file-recovery/#respond</comments>
		
		<dc:creator><![CDATA[Erik Pistelli]]></dc:creator>
		<pubDate>Tue, 29 Oct 2013 12:44:58 +0000</pubDate>
				<category><![CDATA[Article]]></category>
		<category><![CDATA[FAT32]]></category>
		<category><![CDATA[Forensics]]></category>
		<category><![CDATA[Headers]]></category>
		<guid isPermaLink="false">https://ntcore.com/?p=676</guid>

					<description><![CDATA[This post isn&#8217;t about upcoming features, it&#8217;s about things you can already do with Cerbero. What we&#8217;ll see is how to import structures used for file system analysis from C/C++ sources, use them to analyze raw hex data, create a script to do the layout work for us in the future and at the end &#8230; <a href="https://ntcore.com/raw-file-system-analysis-fat32-file-recovery/" class="more-link">Continue reading<span class="screen-reader-text"> "Raw File System Analysis (FAT32 File Recovery)"</span></a>]]></description>
										<content:encoded><![CDATA[<p>This post isn&#8217;t about upcoming features, it&#8217;s about things you can already do with Cerbero. What we&#8217;ll see is how to import structures used for file system analysis from C/C++ sources, use them to analyze raw hex data, create a script to do the layout work for us in the future and at the end we&#8217;ll see how to create a little utility to recover deleted files. The file system used for this demonstration is FAT32, which is simple enough to avoid making the post too long.</p>
<ul>
<li><a href="#import">Import file system structures</a></li>
<li><a href="#mbr">Parse the Master Boot Record</a></li>
<li><a href="#analysis">Analyze raw file system data</a></li>
<li><a href="#auto">Automatically create an analysis layout</a></li>
<li><a href="#recover">Recover deleted files</a></li>
<li><a href="#tool">Create a recovery tool</a></li>
<li><a href="#concl">Conclusions</a></li>
<li><a href="#ref">References</a></li>
</ul>
<p><a name="import"></a></p>
<h2>Import file system structures</h2>
<p>Importing file system structures from C/C++ sources is easy thanks to the Header Manager tool. In fact, it took me less than 30 minutes to import the structures for the most common file systems from different code bases. <a href="/wp-content/uploads/2013/10/fat/fs.zip" title="Structures of popular file systems">Click here</a> to download the archive with all the headers.</p>
<p>Here&#8217;s the list of headers I have created:</p>
<ul>
<li>ext &#8211; ext2/3/4 imported from FreeBSD</li>
<li>ext2 &#8211; imported from Linux</li>
<li>ext3 &#8211; imported from Linux</li>
<li>ext4 &#8211; imported from Linux</li>
<li>fat &#8211; imported from FreeBSD</li>
<li>hfs &#8211; imported from Darwin</li>
<li>iso9660 &#8211; imported from FreeBSD</li>
<li>ntfs &#8211; imported from Linux</li>
<li>reiserfs &#8211; imported from Linux</li>
<li>squashfs &#8211; imported from Linux</li>
<li>udf &#8211; imported from FreeBSD</li>
</ul>
<p>Copy the files to your user headers directory (e.g. &#8220;AppData\Roaming\CProfiler\headers&#8221;). It&#8217;s better to not put them in a sub-directory. Please note that apart from the FAT structures, none of the others have been tried out.</p>
<p>Note: Headers created from Linux sources contain many additional structures, this is due to the includes in the parsed source code. This is a bit ugly: in the future it would be a good idea to add an option to import only structures belonging to files in a certain path hierarchy and those referenced by them.</p>
<p>Since this post is about FAT, we&#8217;ll see how to import the structures for this particular file system. But the same steps apply for other file systems as well and not only for them. If you&#8217;ve never imported structures before, you might want to take a look at <a href="/?p=1217">this previous post about dissecting an ELF</a> and read the documentation about C++ types.</p>
<p>We open the Header Manager and configure some basic options like &#8216;OS&#8217;, &#8216;Language&#8217; and &#8216;Standard&#8217;. In this particular case I imported the structures from FreeBSD, so I just set &#8216;freebsd&#8217;, &#8216;c&#8217; and &#8216;c11&#8217;. Then we need to add the header paths, which in my case were the following:</p>
<pre lang="txt">C:/Temp/freebsd-master
C:/Temp/freebsd-master/include
C:/Temp/freebsd-master/sys
C:/Temp/freebsd-master/sys/x86
C:/Temp/freebsd-master/sys/i386/include
C:/Temp/freebsd-master/sys/i386</pre>
<p>Then in the import edit we insert the following code:</p>
<pre lang="c">HEADER_START("fat");

#include <sys/types.h>
#include <sys/fs/msdosfs/bootsect.h>
#include <sys/fs/msdosfs/denode.h>
#include <sys/fs/msdosfs/direntry.h>
#include <sys/fs/msdosfs/bpb.h></pre>
<p>Now we can click on &#8216;Import&#8217;.</p>
<p><center><a href="/wp-content/uploads/2013/10/fat/fat_import.png"><img decoding="async" src="/wp-content/uploads/2013/10/fat/fat_import.png" alt="Import FAT structures" /></a></center></p>
<p>That&#8217;s it! We now have all the FAT structures we need in the &#8216;fat&#8217; header file. </p>
<p>It should also be mentioned that I modified some fields of the <strong>direntry</strong> structure from the Header Manager, because they were declared as byte arrays, but should actually be shown as short and int values.</p>
<p><a name="mbr"></a></p>
<h2>Parse the Master Boot Record</h2>
<p>Before going on with the FAT analysis, we need to briefly talk about the MBR. FAT partitions are usually found in a larger container, like a partitioned device.</p>
<p>To perform my tests I created <a href="http://www.howtogeek.com/howto/5291/">a virtual hard-disk in Windows 7</a> and formatted it with FAT32.</p>
<p><center><a href="/wp-content/uploads/2013/10/fat/vhd_mbr.png"><img decoding="async" src="/wp-content/uploads/2013/10/fat/vhd_mbr.png" alt="VHD MBR" /></a></center></p>
<p>As you might be able to spot, the VHD file begins with a MBR. In order to locate the partitions it is necessary to parse the MBR first. The format of the MBR is very simple and you can look it up on <a href="http://en.wikipedia.org/wiki/Master_boot_record">Wikipedia</a>. In this case we&#8217;re only interested in the start and size of each partition.</p>
<p>Cerbero doesn&#8217;t yet support the MBR format, although it might be added in the future. In any case, it&#8217;s easy to add the missing feature: I wrote a small hook which parses the MBR and adds the partitions as embedded objects.</p>
<p>Here&#8217;s the cfg data:</p>
<pre lang="ini">[GenericMBR]
label = Generic MBR Partitions
file = generic_mbr.py
scanning = scanning</pre>
<p>And here&#8217;s the Python script:</p>
<pre lang="python">def scanning(sp, ud):
    # make sure we're at the first level and that the format is unknown
    if sp.scanNesting() != 0 or sp.getObjectFormat() != "":
        return
    # check boot signature
    obj = sp.getObject()
    bsign = obj.Read(0x1FE, 2)
    if len(bsign) != 2 or bsign[0] != 0x55 or bsign[1] != 0xAA:
        return
    # add partitions
    for x in range(4):
        entryoffs = 0x1BE + (x * 0x10)
        offs, ret = obj.ReadUInt32(entryoffs + 8)
        size, ret = obj.ReadUInt32(entryoffs + 12)
        if offs != 0 and size != 0:
            sp.addEmbeddedObject(offs * 512, size * 512, "?", "Partition #" + str(x + 1))</pre>
<p>And now we can inspect the partitions directly (do not forget to enable the hook from the extensions).</p>
<p><center><a href="/wp-content/uploads/2013/10/fat/vhd_partitions.png"><img decoding="async" src="/wp-content/uploads/2013/10/fat/vhd_partitions.png" alt="VHD Partitions" /></a></center></p>
<p>Easy.</p>
<p><a name="analysis"></a></p>
<h2>Analyze raw file system data</h2>
<p>The basics of the FAT format are quite simple to describe. The data begins with the boot sector header and some additional fields for FAT32 over FAT16 and for FAT16 over FAT12. We&#8217;re only interested in FAT32, so to simplify the description I will only describe this particular variant. The boot sector header specifies essential information such as sector size, sectors in clusters, number of FATs, size of FAT etc. It also specifies the number of reserved sectors. These reserved sectors start with the boot sector and where they end the FAT begins. </p>
<p>The &#8216;FAT&#8217; in this case is not just the name of the file system, but the File Allocation Table itself. The size of the FAT, as already mentioned, is specified in the boot sector header. Usually, for data-loss prevention, more than one FAT is present. Normally there are two FATs: the number is specified in the boot sector header. The backup FAT follows the first one and has the same size. The data after the FAT(s) and right until the end of the partition includes directory entries and file data. The cluster right after the FAT(s) usually starts with the Root Directory entry, but even this is specified in the boot sector header.</p>
<p>The FAT itself is just an array of 32-bit indexes pointing to clusters. The first 2 indexes are special: they specify the range of EOF values for indexes. It works like this: a directory entry for a file (directories and files share the same structure) specifies the first cluster of said file, if the file is bigger than one cluster, the FAT is looked up at the index representing the current cluster, this index specifies the next cluster belonging to the file. If the index contains one of the values in the EOF range, the file has no more clusters or perhaps contains a damaged cluster (0xFFFFFFF7). Indexes with a value of zero are marked as free. Cluster index are 2-based: cluster 2 is actually cluster 0 in the data region. This means that if the Root Directory is specified to be located at cluster 2, it is located right after the FATs.</p>
<p>Hence, the size of the FAT depends on the size of the partition, and it must be big enough to accommodate an array large enough to represent every cluster in the data area.</p>
<p>So, let&#8217;s perform our raw analysis by adding the boot sector header and the additional FAT32 fields:</p>
<p><center><a href="/wp-content/uploads/2013/10/fat/add_struct.png"><img decoding="async" src="/wp-content/uploads/2013/10/fat/add_struct.png" alt="Add struct" /></a></center></p>
<p>Note: When adding a structure make sure that it&#8217;s packed to 1, otherwise field alignment will be wrong.</p>
<p><center><a href="/wp-content/uploads/2013/10/fat/bootsector.png"><img decoding="async" src="/wp-content/uploads/2013/10/fat/bootsector.png" alt="Boot sector" /></a></center></p>
<p>Then we highlight the FATs.</p>
<p><center><a href="/wp-content/uploads/2013/10/fat/fats.png"><img decoding="async" src="/wp-content/uploads/2013/10/fat/fats.png" alt="FATs" /></a></center></p>
<p>And the Root Directory entry.</p>
<p><center><a href="/wp-content/uploads/2013/10/fat/rootdir.png"><img decoding="async" src="/wp-content/uploads/2013/10/fat/rootdir.png" alt="Root Directory" /></a></center></p>
<p>This last step was just for demonstration, as we&#8217;re currently not interested in the Root Directory. Anyway, now we have a basic layout of the FAT to inspect and this is useful.</p>
<p>Let&#8217;s now make our analysis applicable to future cases.</p>
<p><a name="auto"></a></p>
<h2>Automatically create an analysis layout</h2>
<p>Manually analyzing a file is very useful and it&#8217;s the first step everyone of us has to do when studying an unfamiliar file format. However, chances are that we have to analyze files with the same format in the future.</p>
<p>That&#8217;s why we could write a small Python script to create the analysis layout for us. We&#8217;ve already seen how to do this <a href="/?p=1217">in the post about dissecting an ELF</a>.</p>
<p>Here&#8217;s the code:</p>
<pre lang="python">from Pro.Core import *
from Pro.UI import *
 
def buildFATLayout(obj, l):
    hname = "fat"
    hdr = CFFHeader()
    if hdr.LoadFromFile(hname) == False:
        return
    sopts = CFFSO_VC | CFFSO_Pack1
    d = LayoutData()
    d.setTypeOptions(sopts)
 
    # add boot sector header and FAT32 fields
    bhdr = obj.MakeStruct(hdr, "bootsector", 0, sopts)
    d.setColor(ntRgba(0, 170, 255, 70))
    d.setStruct(hname, "bootsector")
    l.add(0, bhdr.Size(), d)
    bexhdr = obj.MakeStruct(hdr, "bpb710", 0xB, sopts)
    d.setStruct(hname, "bpb710")
    l.add(0xB, bexhdr.Size(), d)

    # get FAT32 info
    bytes_per_sec = bexhdr.Num("bpbBytesPerSec")
    sec_per_clust = bexhdr.Num("bpbSecPerClust")
    res_sect = bexhdr.Num("bpbResSectors")
    nfats = bexhdr.Num("bpbFATs")
    fat_sects = bexhdr.Num("bpbBigFATsecs")
    root_clust = bexhdr.Num("bpbRootClust")
    bytes_per_clust = bytes_per_sec * sec_per_clust

    # add FAT intervals, highlight copies with a different color
    d2 = LayoutData()
    d2.setColor(ntRgba(255, 255, 127, 70))
    fat_start = res_sect * bytes_per_sec
    fat_size = fat_sects * bytes_per_sec
    d2.setDescription("FAT1")
    l.add(fat_start, fat_size, d2)
    # add copies
    d2.setColor(ntRgba(255, 170, 127, 70))
    for x in range(nfats - 1):
        fat_start = fat_start + fat_size
        d2.setDescription("FAT" + str(x + 2))
        l.add(fat_start, fat_size, d2)
    fat_end = fat_start + fat_size

    # add root directory
    rootdir_offs = (root_clust - 2) + fat_end
    rootdir = obj.MakeStruct(hdr, "direntry", rootdir_offs, sopts)
    d.setStruct(hname, "direntry")
    d.setDescription("Root Directory")
    l.add(rootdir_offs, rootdir.Size(), d)
    
 
hv = proContext().getCurrentView()
if hv.isValid() and hv.type() == ProView.Type_Hex:
    c = hv.getData()
    obj = CFFObject()
    obj.Load(c)
    lname = "FAT_ANALYSIS" # we could make the name unique
    l = proContext().getLayout(lname) 
    buildFATLayout(obj, l)
    # apply the layout to the current hex view
    hv.setLayoutName(lname)</pre>
<p>We can create an action with this code or just run it on the fly with Ctrl+Alt+R.</p>
<p><a name="recover"></a></p>
<h2>Recover deleted files</h2>
<p>Now that we know where the FAT is located and where the data region begins, we can try to recover deleted files. There&#8217;s more than one possible approach to this task (more on that later). What I chose to do is to scan the entire data region for file directory entries and to perform integrity checks on them, in order to establish that they really are what they seem to be.</p>
<p>Let&#8217;s take a look at the original direntry structure:</p>
<pre lang="c">struct direntry {
	u_int8_t	deName[11];	/* filename, blank filled */
#define	SLOT_EMPTY	0x00		/* slot has never been used */
#define	SLOT_E5		0x05		/* the real value is 0xe5 */
#define	SLOT_DELETED	0xe5		/* file in this slot deleted */
	u_int8_t	deAttributes;	/* file attributes */
#define	ATTR_NORMAL	0x00		/* normal file */
#define	ATTR_READONLY	0x01		/* file is readonly */
#define	ATTR_HIDDEN	0x02		/* file is hidden */
#define	ATTR_SYSTEM	0x04		/* file is a system file */
#define	ATTR_VOLUME	0x08		/* entry is a volume label */
#define	ATTR_DIRECTORY	0x10		/* entry is a directory name */
#define	ATTR_ARCHIVE	0x20		/* file is new or modified */
	u_int8_t	deLowerCase;	/* NT VFAT lower case flags */
#define	LCASE_BASE	0x08		/* filename base in lower case */
#define	LCASE_EXT	0x10		/* filename extension in lower case */
	u_int8_t	deCHundredth;	/* hundredth of seconds in CTime */
	u_int8_t	deCTime[2];	/* create time */
	u_int8_t	deCDate[2];	/* create date */
	u_int8_t	deADate[2];	/* access date */
	u_int8_t	deHighClust[2];	/* high bytes of cluster number */
	u_int8_t	deMTime[2];	/* last update time */
	u_int8_t	deMDate[2];	/* last update date */
	u_int8_t	deStartCluster[2]; /* starting cluster of file */
	u_int8_t	deFileSize[4];	/* size of file in bytes */
};</pre>
<p>Every directory entry has to be aligned to 0x20. If the file has been deleted the first byte of the <strong>deName</strong> field will be set to <strong>SLOT_DELETED</strong> (0xE5). That&#8217;s the first thing to check. The directory name should also not contain certain values like 0x00. <a href="http://en.wikipedia.org/wiki/File_Allocation_Table#Directory_table">According to Wikipedia</a>, the following values aren&#8217;t allowed:</p>
<ul>
<li>&#8221; * / : < > ? \ |<br />
    Windows/MS-DOS has no shell escape character</li>
<li>+ , . ; = [ ]<br />
    They are allowed in long file names only.</li>
<li>Lower case letters a–z<br />
    Stored as A–Z. Allowed in long file names.</li>
<li>Control characters 0–31</li>
<li>Value 127 (DEL)</li>
</ul>
<p>We can use these rules to validate the short file name. Moreover, certain directory entries are used only to store long file names:</p>
<pre lang="c">/*
 * Structure of a Win95 long name directory entry
 */
struct winentry {
	u_int8_t	weCnt;
#define	WIN_LAST	0x40
#define	WIN_CNT		0x3f
	u_int8_t	wePart1[10];
	u_int8_t	weAttributes;
#define	ATTR_WIN95	0x0f
	u_int8_t	weReserved1;
	u_int8_t	weChksum;
	u_int8_t	wePart2[12];
	u_int16_t	weReserved2;
	u_int8_t	wePart3[4];
};</pre>
<p>We can exclude these entries by making sure that the <strong>deAttributes</strong>/<strong>weAttributes</strong> isn&#8217;t <strong>ATTR_WIN95</strong> (0xF).</p>
<p>Once we have confirmed the integrity of the file name and made sure it&#8217;s not a long file name entry, we can validate the <strong>deAttributes</strong>. It should definitely not contain the flags <strong>ATTR_DIRECTORY</strong> (0x10) and <strong>ATTR_VOLUME</strong> (8).</p>
<p>Finally we can make sure that <strong>deFileSize</strong> isn&#8217;t 0 and that <strong>deHighClust</strong> combined with <strong>deStartCluster</strong> contains a valid cluster index.</p>
<p>It&#8217;s easier to write the code than to talk about it. Here&#8217;s a small snippet which looks for deleted files and prints them to the output view:</p>
<pre lang="python">from Pro.Core import *

class FATData(object):
    pass

def setupFATData(obj):
    hdr = CFFHeader()
    if hdr.LoadFromFile("fat") == False:
        return None
    bexhdr = obj.MakeStruct(hdr, "bpb710", 0xB, CFFSO_VC | CFFSO_Pack1)
    fi = FATData()
    fi.obj = obj
    fi.hdr = hdr
    # get FAT32 info
    fi.bytes_per_sec = bexhdr.Num("bpbBytesPerSec")
    fi.sec_per_clust = bexhdr.Num("bpbSecPerClust")
    fi.res_sect = bexhdr.Num("bpbResSectors")
    fi.nfats = bexhdr.Num("bpbFATs")
    fi.fat_sects = bexhdr.Num("bpbBigFATsecs")
    fi.root_clust = bexhdr.Num("bpbRootClust")
    fi.bytes_per_clust = fi.bytes_per_sec * fi.sec_per_clust
    fi.fat_offs = fi.res_sect * fi.bytes_per_sec
    fi.fat_size = fi.fat_sects * fi.bytes_per_sec
    fi.data_offs = fi.fat_offs + (fi.fat_size * fi.nfats)
    fi.data_size = obj.GetSize() - fi.data_offs
    fi.data_clusters = fi.data_size // fi.bytes_per_clust
    return fi

invalid_short_name_chars = [
    127,
    ord('"'), ord("*"), ord("/"), ord(":"), ord("<"), ord(">"), ord("?"), ord("\\"), ord("|"),
    ord("+"), ord(","), ord("."), ord(";"), ord("="), ord("["), ord("]")
    ]
def validateShortName(name):
    n = len(name)
    for x in range(n):
        c = name[x]
        if (c >= 0 and c <= 31) or (c >= 0x61 and c <= 0x7A) or c in invalid_short_name_chars:
            return False
    return True

# validate short name
# validate attributes: avoid long file name entries, directories and volumes
# validate file size
# validate cluster index
def validateFileDirectoryEntry(fi, de):
    return validateShortName(de.name) and de.attr != 0xF and (de.attr &#038; 0x18) == 0 and \
            de.file_size != 0 and de.clust_idx >= 2 and de.clust_idx - 2 < fi.data_clusters

class DirEntryData(object):
    pass

def getDirEntryData(b):
    # reads after the first byte
    de = DirEntryData()
    de.name = b.read(10)
    de.attr = b.u8()     
    b.read(8) # skip some fields
    de.high_clust = b.u16()
    b.u32() # skip two fields
    de.clust_idx = (de.high_clust << 16) | b.u16()
    de.file_size = b.u32()
    return de

def findDeletedFiles(fi):
    # scan the data region one cluster at a time using a buffer
    # this is more efficient than using an array of CFFStructs
    dir_entries = fi.data_size // 0x20
    b = fi.obj.ToBuffer(fi.data_offs)
    b.setBufferSize(0xF000)
    for x in range(dir_entries):
        try:
            unaligned = b.getOffset() % 0x20
            if unaligned != 0:
                b.read(0x20 - unaligned)
            # has it been deleted?
            if b.u8() != 0xE5:
                continue
            # validate fields
            de = getDirEntryData(b)
            if validateFileDirectoryEntry(fi, de) == False:
                continue
            # we have found a deleted file entry!
            name = de.name.decode("ascii", "replace")
            print(name + " - offset: " + hex(b.getOffset() - 0x20))
        except:
            # an exception occurred, debug info
            print("exception at offset: " + hex(b.getOffset() - 0x20))
            raise

obj = proCoreContext().currentScanProvider().getObject()
fi = setupFATData(obj)
if fi != None:
    findDeletedFiles(fi)</pre>
<p>This script is to be run on the fly with Ctrl+Alt+R. It's not complete, otherwise I would have added a wait box, since like it's now the script just blocks the UI for the entire execution. We'll see later how to put everything together in a meaningful way.</p>
<p>The output of the script is the following:</p>
<pre lang="txt">���������� - offset: 0xd6a0160
���������� - offset: 0x181c07a0
���������� - offset: 0x1d7ee980
&�&�&�&�&� - offset: 0x1e7dee20
'�'�'�'�'� - offset: 0x1f3b49a0
'�'�'�'�'� - offset: 0x1f5979a0
'�'�'�'�'� - offset: 0x1f9f89a0
'�'�'�'�'� - offset: 0x1fbdb9a0
$�$�$�$�$� - offset: 0x1fdcad40
&�&�&�&�&� - offset: 0x1fdcc520
'�'�'�'�'� - offset: 0x2020b9a0
'�'�'�'�'� - offset: 0x205a99a0
'�'�'�'�'� - offset: 0x20b0fe80
'�'�'�'�'� - offset: 0x20b0fec0
'�'�'�'�'� - offset: 0x20e08e80
'�'�'�'�'� - offset: 0x20e08ec0
'�'�'�'�'� - offset: 0x21101e80
'�'�'�'�'� - offset: 0x21101ec0
'�'�'�'�'� - offset: 0x213fae80
'�'�'�'�'� - offset: 0x213faec0
 � � � � � - offset: 0x21d81fc0
#�#�#�#�#� - offset: 0x221b96a0
'�'�'�'�'� - offset: 0x226279a0
 � � � � � - offset: 0x2298efc0
'�'�'�'�'� - offset: 0x22e1ee80
'�'�'�'�'� - offset: 0x22e1eec0
'�'�'�'�'� - offset: 0x232c69a0
'�'�'�'�'� - offset: 0x234a99a0
'�'�'�'�'� - offset: 0x2368c9a0
'�'�'�'�'� - offset: 0x23a37e80
'�'�'�'�'� - offset: 0x23a37ec0
'�'�'�'�'� - offset: 0x23d30e80
'�'�'�'�'� - offset: 0x23d30ec0
'�'�'�'�'� - offset: 0x24029e80
'�'�'�'�'� - offset: 0x24029ec0
'�'�'�'�'� - offset: 0x24322e80
'�'�'�'�'� - offset: 0x24322ec0
'�'�'�'�'� - offset: 0x2461be80
'�'�'�'�'� - offset: 0x2461bec0
'�'�'�'�'� - offset: 0x2474d9a0
 � � � � � - offset: 0x24ab4fc0
 � � � � � - offset: 0x24f01fc0
 � � � � � - offset: 0x2534efc0
���������O - offset: 0x33b4f2e0
�������@@@ - offset: 0x345c7200
OTEPAD EXE - offset: 0x130c009e0
TOSKRNLEXE - offset: 0x130c00b80
TPRINT EXE - offset: 0x130c00bc0
��S�W����� - offset: 0x1398fddc0
��S�V���YY - offset: 0x13af3ad60
��M����E�� - offset: 0x13bbec640
EGEDIT EXE - offset: 0x13ef1f1a0</pre>
<p>We can see many false positives in the list. The results would be cleaner if we allowed only ascii characters in the name, but this wouldn't be correct, because short names do allow values above 127. We could make this an extra option, generally speaking it's probably better to have some false positives than missing valid entries. Among the false positives we can spot four real entries. What I did on the test disk was to copy many files from the System32 directory of Windows and then to delete four of them, exactly those four found by the script.</p>
<p>The next step is recovering the content of the deleted files. The theory here is that we retrieve the first cluster of the file from the directory entry and then use the FAT to retrieve more entries until the file size is satisfied. The cluster indexes in the FAT won't contain the next cluster value and will be set to 0. We look for adjacent 0 indexes to find free clusters which may have belonged to the file. Another approach would be to dump the entire file size starting from the first cluster, but that approach is worse, because it doesn't tolerate even a little bit of fragmentation in the FAT. Of course, heavy fragmentation drastically reduces the chances of a successful recovery.</p>
<p>However, there's a gotcha which I wasn't aware of and it wasn't mentioned in my references. Let's take a look at the deleted directory entry of 'notepad.exe'.</p>
<p><center><a href="/wp-content/uploads/2013/10/fat/notepad_direntry.png"><img decoding="async" src="/wp-content/uploads/2013/10/fat/notepad_direntry.png" alt="Notepad directory entry" /></a></center></p>
<p>In FAT32 the index of the first cluster is obtained by combining the high-word <strong>deHighClust</strong> with the low-word <strong>deStartCluster</strong> in order to obtain a 32-bit index.</p>
<p>The problem is that the high-word has been zeroed. The actual value should be 0x0013. Seems this behavior is common on Microsoft operating systems as mentioned <a href="http://www.forensicfocus.com/Forums/viewtopic/t=9382/">in this thread</a> on Forensic Focus.</p>
<p>This means that only files with a cluster index equal or lower than 0xFFFF will be correctly pointed at. This makes another approach for FAT32 file recovery more appealing: instead of looking for deleted directly entries, one could directly look for cluster indexes with a value of 0 in the FAT and recognize the start of a file by matching signatures. Cerbero offers an API to identify file signatures (although limited to the file formats it supports), so we could easily implement this logic. Another advantage of this approach is that it doesn't require a deleted file directory entry to work, increasing the possibility to recover deleted files. However, even that approach has certain disadvantages:</p>
<ol>
<li>Files which have no signature (like text files) or are not identified won't be recovered.</li>
<li>The name of the files won't be recovered at all, unless they contain it themselves, but that's unlikely.</li>
</ol>
<p>Disadvantages notwithstanding I think that if one had to choose between the two approaches the second one holds higher chances of success. So why then did I opt to do otherwise? Because I thought it would be nice to recover file names, even though only partially and delve a bit more in the format of FAT32. The blunt approach could be generalized more and requires less FAT knowledge.</p>
<p>However, the surely best approach is to combine both systems in order to maximize chances of recovery at the cost of duplicates. But this is just a demonstration, so let's keep it relatively simple and let's go back to the problem at hand: the incomplete start cluster index.</p>
<p>Recovering files only from lower parts of the disk isn't really good enough. We could try to recover the high-word of the index from adjacent directory entries of existing files. For instance, let's take a look at the deleted directory entry:</p>
<p><center><a href="/wp-content/uploads/2013/10/fat/deleted_entry.png"><img decoding="async" src="/wp-content/uploads/2013/10/fat/deleted_entry.png" alt="Deleted entry" /></a></center></p>
<p>As you can see, the directory entry above the deleted one represents a valid file entry and contains an intact high-word we could use to repair our index. Please remember that this technique is just something I came up with and offers no guarantee whatsoever. In fact, it only works under certain conditions:</p>
<ol>
<li>The cluster containing the deleted entry must also contain a valid file directory entry.</li>
<li>The FAT can't be heavily fragmented, otherwise the retrieved high-word might not be correct.</li>
</ol>
<p>Still I think it's interesting and while it might not always be successful in automatic mode, it can be helpful when trying a manual recovery.</p>
<p>This is how the code to recover partial cluster indexes might look like:</p>
<pre lang="python">def recoverClusterHighWord(fi, offs):
    cluster_start = offs - (offs % fi.bytes_per_clust)
    deloffs = offs - (offs % 0x20)
    nbefore = (deloffs - cluster_start) // 0x20
    nafter = (fi.bytes_per_clust - (deloffs - cluster_start)) // 0x20 - 1
    b = fi.obj.ToBuffer(deloffs + 0x20, Bufferize_BackAndForth)
    b.setBufferSize(fi.bytes_per_clust * 2)
    de_before = None
    de_after = None
    try:
        # try to find a valid entry before
        if nbefore > 0:
            for x in range(nbefore):
                b.setOffset(b.getOffset() - 0x40)
                # it can't be a deleted entry
                if b.u8() == 0xE5:
                    continue
                de = getDirEntryData(b)
                if validateFileDirectoryEntry(fi, de):
                    de_before = de
                    break
        # try to find a valid entry after
        if nafter > 0 and de_before == None:
            b.setOffset(deloffs + 0x20)
            for x in range(nafter):
                # it can't be a deleted entry
                if b.u8() == 0xE5:
                    continue
                de = getDirEntryData(b)
                if validateFileDirectoryEntry(fi, de):
                    de_after = de
                    break
    except:
        pass
    # return the high-word if any
    if de_before != None:
        return de_before.high_clust
    if de_after != None:
        return de_after.high_clust
    return 0</pre>
<p>It tries to find a valid file directory entry before and after the deleted entry, remaining in the same cluster. Now we can write a small function to recover the file content.</p>
<pre lang="python"># dump the content of a deleted file using the FAT
def dumpDeletedFileContent(fi, f, start_cluster, file_size):
    while file_size > 0:
        offs = clusterToOffset(fi, start_cluster)
        data = fi.obj.Read(offs, fi.bytes_per_clust)
        if file_size < fi.bytes_per_clust:
            data = data[:file_size]
        f.write(data)
        # next
        file_size = file_size - min(file_size, fi.bytes_per_clust)
        # find next cluster
        while True:
            start_cluster = start_cluster + 1
            idx_offs = start_cluster * 4 + fi.fat_offs
            idx, ok = fi.obj.ReadUInt32(idx_offs)
            if ok == False:
                return False
            if idx == 0:
                break
    return True</pre>
<p>All the pieces are there, it's time to bring them together.</p>
<p><a name="tool"></a></p>
<h2>Create a recovery tool</h2>
<p>With the recently introduced <a href="/?p=1324" title="Logic providers">logic provider extensions</a>, it's possible to create every kind of easy-to-use custom utility. Until now we have seen useful pieces of code, but using them as provided is neither user-friendly nor practical. Wrapping them up in a nice graphical utility is much better.</p>
<p><center><a href="/wp-content/uploads/2013/10/fat/home.png"><img decoding="async" src="/wp-content/uploads/2013/10/fat/home.png" alt="Home view" /></a></center></p>
<p>What follows is the source code or at least part of it: I have omitted those parts which haven't significantly changed. You can download the full source code from <a href="/wp-content/uploads/2013/10/fat/fat32_recovery.zip">here</a>.</p>
<p>Here's the cfg entry:</p>
<pre lang="ini">[FAT32Recovery]
label = FAT32 file recovery utility
descr = Recover files from a FAT32 partition or drive.
file = fat32_recovery.py
init = FAT32Recovery_init</pre>
<p>And the Python code:</p>
<pre lang="python">class RecoverySystem(LocalSystem):

    def __init__(self):
        LocalSystem.__init__(self)
        self.ctx = proCoreContext()
        self.partition = None
        self.current_partition = 0
        self.fi = None
        self.counter = 0

    def wasAborted(self):
        Pro.UI.proProcessEvents(1)
        return self.ctx.wasAborted()

    def nextFile(self):
        fts = FileToScan()

        if self.partition == None:
            # get next partition
            while self.current_partition < 4:
                entryoffs = 0x1BE + (self.current_partition * 0x10)
                self.current_partition = self.current_partition + 1
                offs, ret = self.disk.ReadUInt32(entryoffs + 8)
                size, ret = self.disk.ReadUInt32(entryoffs + 12)
                if offs != 0 and size != 0:
                    cpartition = self.disk.GetStream()
                    cpartition.setRange(offs * 512, size * 512)
                    part = CFFObject()
                    part.Load(cpartition)
                    self.fi = setupFATData(part)
                    if self.fi != None:
                        self.fi.system = self
                        self.partition = part
                        self.next_entry = self.fi.data_offs
                        self.fi.ascii_names_conv = self.ascii_names_conv
                        self.fi.repair_start_clusters = self.repair_start_clusters
                        self.fi.max_file_size = self.max_file_size
                        break

        if self.partition != None:
            de = findDeletedFiles(self.fi, self.next_entry)
            if de != None:
                self.next_entry = de.offs + 0x20
                fname = "%08X" % self.counter
                f = open(self.dump_path + fname, "wb")
                if f == None:
                    ctx.msgBox(MsgErr, "Couldn't open file '" + fname + "'")
                    return fts
                dumpDeletedFileContent(self.fi, f, de.clust_idx, de.file_size)
                f.close()
                self.counter = self.counter + 1
                fts.setName(fname + "\\" + de.name)
                fts.setLocalName(self.dump_path + fname)
            else:
                self.partition = None

        return fts

def recoveryOptionsCallback(pe, id, ud):
    if id == Pro.UI.ProPropertyEditor.Notification_Close:
        path = pe.getValue(0)
        if len(path) == 0 or os.path.isdir(path) == False:
            errs = NTIntList()
            errs.append(0)
            pe.setErrors(errs)
            return False
    return True

def FAT32Recovery_init():
    ctx = Pro.UI.proContext()
    file_name = ctx.getOpenFileName("Select disk...")
    if len(file_name) == 0:
        return False

    cdisk = createContainerFromFile(file_name)
    if cdisk.isNull():
        ctx.msgBox(MsgWarn, "Couldn't open disk!")
        return False

    disk = CFFObject()
    disk.Load(cdisk)
    bsign = disk.Read(0x1FE, 2)
    if len(bsign) != 2 or bsign[0] != 0x55 or bsign[1] != 0xAA:
        ctx.msgBox(MsgWarn, "Invalid MBR!")
        return False

    dlgxml = """
<peditor title="Settings">
  <section label="Options">
    <property id="0" label="Select directory for dumps" type="open-directory"/>
    <property id="1" label="Ascii only names" type="check" value="false"/>
    <property id="2" label="Start cluster repair" type="check" value="true"/>
    <property id="3" label="Max file size (in MBs)" type="integer" value="100" signed="false"/>
  </section>
</peditor>"""
    opts = ctx.askParams(dlgxml, "FAT32RecoveryOptions", recoveryOptionsCallback, None)
    if opts.isEmpty():
        return False

    s = RecoverySystem()
    s.disk = disk
    s.dump_path = os.path.normpath(opts.value(0)) + os.sep
    s.ascii_names_conv = "strict" if opts.value(1) else "replace"
    s.repair_start_clusters = opts.value(2)
    if opts.value(3) != 0:
        s.max_file_size = opts.value(3) * 1024 * 1024
    proCoreContext().setSystem(s)
    return True</pre>
<p>When the tool is activated it will ask for the disk file to be selected, then it will show an options dialog.</p>
<p><center><img decoding="async" src="/wp-content/uploads/2013/10/fat/options.png" alt="Options" /></center></p>
<p>In our case we can select the option 'Ascii only names' to exclude false positives. </p>
<p>The options dialog asks for a directory to save the recovered files. In the future it will be possible to save volatile files in the temporary directory created for the report, but since it's not yet possible, it's the responsibility of the user to delete the recovered files if he wants to.</p>
<p>The end results of the recovery operation:</p>
<p><center><a href="/wp-content/uploads/2013/10/fat/results.png"><img decoding="async" src="/wp-content/uploads/2013/10/fat/results.png" alt="Results" /></a></center></p>
<p>All four deleted files have been successfully recovered.</p>
<p>Three executables are marked as risky because<a href="/?p=514"> intrinsic risk is enabled</a> and only 'ntoskrnl.exe' contains a valid digital certificate.</p>
<p><a name="concl"></a></p>
<h2>Conclusions</h2>
<p>I'd like to remind you that this utility hasn't been tested on disks other than on the one I've created for the post and, as already mentioned, it doesn't even implement the best method to recover files from a FAT32, which is to use a signature based approach. It's possible that in the future we'll improve the script and include it in an update.</p>
<p>The purpose of this post was to show some of the many things which can be done with Cerbero. I used only Cerbero for the entire job: from analysis to code development (I even wrote the entire Python code with it). And finally <strong>to demonstrate how a utility with commercial value like the one presented could be written in under 300 lines of Python code (counting comments and new-lines)</strong>.</p>
<p>The advantages of using the Cerbero SDK are many. Among them:</p>
<ul>
<li>It hugely simplifies the analysis of files. In fact, I used only two external Python functions: one to check the existence of a directory and one to normalize the path string.</li>
<li>It helps building a fast robust product.</li>
<li>It offers a graphical analysis experience to the user with none or little effort.</li>
<li>It gives the user the benefit of all the other features and extension offered by Cerbero.</li>
</ul>
<p>To better explain what is meant by the last point, let's take the current example. Thanks to the huge amount of formats supported by Cerbero, it will be easy for the user to validate the recovered files.</p>
<p><center><a href="/wp-content/uploads/2013/10/fat/inspect.png"><img decoding="async" src="/wp-content/uploads/2013/10/fat/inspect.png" alt="Validate recovered files" /></a></center></p>
<p>In the case of Portable Executables it's extremely easy because of the presence of digital certificates, checksums and data structures. But even with other files it's easy, because Cerbero may detect errors in the format or unused ranges.</p>
<p>I hope you enjoyed this post!</p>
<p>P.S. You can download the complete source code and related files from <a href="/wp-content/uploads/2013/10/fat/fat32_recovery.zip">here</a>.</p>
<p><a name="ref"></a></p>
<h2>References</h2>
<ol>
<li><a href="http://www.amazon.com/System-Forensic-Analysis-Brian-Carrier/dp/0321268172">File System Forensic Analysis - Brian Carrier</a></li>
<li><a href="http://www.pjrc.com/tech/8051/ide/fat32.html">Understanding FAT32 Filesystems - Paul Stoffregen</a></li>
<li><a href="http://msdn.microsoft.com/en-US/windows/hardware/gg463084">Official documentation - Microsoft</a></li>
<li><a href="http://en.wikipedia.org/wiki/File_Allocation_Table">File Allocation Table - Wikipedia</a></li>
<li><a href="http://en.wikipedia.org/wiki/Master_boot_record">Master boot record - Wikipedia</a></li>
</ol>
]]></content:encoded>
					
					<wfw:commentRss>https://ntcore.com/raw-file-system-analysis-fat32-file-recovery/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">676</post-id>	</item>
	</channel>
</rss>
