2010-01-05:

DR6 may or may not be useful for bochs/VirtualPC detection

medium:x86:assembler:bochs:virtualpc
This post will be similar to the previous one, and will be about small, but interesting, details of x86 architecture, that might be (and sometimes are) easily overlooked by creators of emulators and virtual machines. The hero of today's post is the DR6 debug register, or, to be more precise, the four least significant bits of this register - B0 to B3 (breakpoint condition detected flags). Please read the whole post before jumping into any conclusions :)

Let's take a look at the Intel manual (volume 3B):
B0 through B3 (breakpoint condition detected) flags (bits 0 through 3) — Indicates (when set) that its associated breakpoint condition was met when a debug exception was generated. These flags are set if the condition described for each breakpoint by the LENn, and R/Wn flags in debug control register DR7 is true. They are set even if the breakpoint is not enabled by the Ln and Gn flags in register DR7.
So, this has to be interpreted the following way:
1. A debugger exception (int 1) is generated
2. For each hardware breakpoint set (0-3) a check is made, and a flag is, or is not set

Now, please note, that nothing here is said about checking if a hardware breakpoint set is enabled (register DR7, bits Ln and Gn) before setting the flags (exception generation is another thing).

OK, so how many of you guys suspect that some vm/emu authors might interpret it another way: "when a hardware breakpoint is hit, set it's flag in the DR6 register, and generate int1"? Let's check it! :)

I'll start with bochs. First of all, as far as I know, bochs default binary distro has the debugging register functionality turned off, so if you would like to test it, a recompilation (detection by DRn not working at all anyone?) will be needed (--enable-x86-debugger option at ./configure).
Let's look at the following code:

#if BX_X86_DEBUGGER
 else {
   // only bother comparing if any breakpoints enabled and
   // debug events are not inhibited on this boundary.
   if (! (BX_CPU_THIS_PTR inhibit_mask & BX_INHIBIT_DEBUG_SHADOW) && ! BX_CPU_THIS_PTR in_repeat) {
     if (BX_CPU_THIS_PTR dr7 & 0x000000ff) {
       bx_address iaddr = get_laddr(BX_SEG_REG_CS, BX_CPU_THIS_PTR prev_rip);
       Bit32u dr6_bits = hwdebug_compare(iaddr, 1, BX_HWDebugInstruction, BX_HWDebugInstruction);
       if (dr6_bits) {
         // Add to the list of debug events thus far.
         BX_CPU_THIS_PTR debug_trap |= dr6_bits;
         BX_ERROR(("#DB: x86 code breakpoint catched"));
         exception(BX_DB_EXCEPTION, 0, 0); // no error, not interrupt
       }
     }
   }
 }
#endif

Let's look into the hwdebug_compare funciton:

 for (unsigned n=0;n<4;n++) {
   bx_address dr_start = BX_CPU_THIS_PTR dr[n] & ~alignment_mask[dr_len[n]];
   bx_address dr_end = dr_start + alignment_mask[dr_len[n]];
   ibpoint_found_n[n] = 0;

   // See if this instruction address matches any breakpoints
   if (dr7 & (3 << n*2)) {
     if ((dr_op[n]==opa || dr_op[n]==opb) &&
          (laddr_0 <= dr_end) &&
          (laddr_n >= dr_start)) {
       ibpoint_found_n[n] = 1;
       ibpoint_found = 1;
     }
   }
 }

 // If *any* enabled breakpoints matched, then we need to
 // set status bits for *all* breakpoints, even disabled ones,
 // as long as they meet the other breakpoint criteria.
 // dr6_mask is the return value.  These bits represent the bits
 // to be OR'd into DR6 as a result of the debug event.
 Bit32u dr6_mask = 0;

 if (ibpoint_found) {
   if (ibpoint_found_n[0]) dr6_mask |= 0x1;
   if (ibpoint_found_n[1]) dr6_mask |= 0x2;
   if (ibpoint_found_n[2]) dr6_mask |= 0x4;
   if (ibpoint_found_n[3]) dr6_mask |= 0x8;
 }

As you can see in the comment, the authors were aware of this detail. However, the code above is invalid - they check at first if the exception is enabled, before checking if the condition was met. So, the comment is OK, but the code is not.

Below, you can see a screenshot (click to enlarge) from a proof of concept code:

bochs is detected


So, seem that bochs can be detected. Now, for some screenshots from other vm/emu's (ignore the interrupt 8 message):

Microsoft VirtualPC (detected)
VirtualPC is detected

QEMU (not detected)
QEMU is not detected

Sun VirtualBox (not detected)
VirtualBox is not detected


And now, so we can compare the output, two photos of real hardware (ASUS laptop with Core 2 Duo, and ASUS EeePC netbook with Intel Atom cpu):
Laptop (not detected)
Laptop is not detected

Netbook (not detected)
Netbook is not detected


Yaay, so we can you this method to detect Bochs and VirtualPC!!!

By the way...
On 22nd Nov'24 we're running a webinar called "CVEs of SSH" – it's free, but requires sign up: https://hexarcana.ch/workshops/cves-of-ssh (Dan from HexArcana is the speaker).


Well... not exactly. The manual I quoted at the beginning of this post is from October 2006. A few days ago a new set of manuals were released (December 2009), and seems something changed:

B0 through B3 (breakpoint condition detected) flags (bits 0 through 3) — Indicates (when set) that its associated breakpoint condition was met when a debug exception was generated. These flags are set if the condition described for each breakpoint by the LENn, and R/Wn flags in debug control register DR7 is true. They may or may not be set if the breakpoint is not enabled by the Ln or the Gn flags in register DR7. Therefore on a #DB, a debug handler should check only those B0-B3 bits which correspond to an enabled breakpoint.
Well, this makes this post useful or not useful. However, as far as I'm concerned, this change is made in the new processors, which are handled by this version of the manual, and are not handled by the previous. These new CPUs are (it's not stated in which of them the change was made):
Intel® Core™2 Quad processor Q6000 series
Intel® Xeon® processor 3000, 3200 series
Intel® Xeon® processor 5000 series
Intel® Xeon® processor 5300 series
Intel® Core™2 Extreme processor X7000 and X6800 series
Intel® Core™2 Extreme QX6000 series
Intel® Xeon® processor 7100 series
Intel® Pentium® Dual-Core processor
Intel® Xeon® processor 7200, 7300 series
Intel® Core™2 Extreme QX9000 series
Intel® Xeon® processor 5200, 5400, 7400 series
Intel® CoreTM2 Extreme processor QX9000 and X9000 series
Intel® CoreTM2 Quad processor Q9000 series
Intel® CoreTM2 Duo processor E8000, T9000 series
Intel® AtomTM processor family
Intel® CoreTM i7 processor
Intel® CoreTM i5 processor


So I guess, the detection needs additional check, to know which CPU it's dealing with.

All in all, it was quite fun to play with it ;>

Comments:

2010-01-06 07:02:00 = Schrödinger's Cat
{
Thanks for the post. Lol.
}
2010-01-06 11:20:48 = wheelq
{
Musze przyznac ze wciagnal mnie post.
A komentarz...:D
}
2010-01-06 15:59:22 = MazeGen
{
Manuals from October 2006? Intel releases like 4 revisions every year, man.
}
2010-01-06 17:35:04 = Gynvael Coldwind
{
@Schrödinger's Cat
Ahahaha ;>>> Thanks for your comment, it's very insightful ;>>>

@wheelq
Glad to hear you enjoyed the post!
Yeah, the comment is great;>

@MazeGen
Thanks for your comment!
Yeah, I know, but my paper edition (which I read when traveling to/from work) is from 2006, hence this post.
Guess it's time to order a new one :)
}
2011-02-14 21:50:18 = kosiara
{
Virtual Box Cloner (cloning Virtual Machines with btrfs --reflink support)
http://kosiara87.blogspot.com/2011/01/virtualbox-40-machine-cloner.html
}

Add a comment:

Nick:
URL (optional):
Math captcha: 9 ∗ 10 + 5 =