Click here to Skip to main content
13,198,850 members (41,093 online)
Click here to Skip to main content
Add your own
alternative version


4 bookmarked
Posted 28 Dec 2012

Last branch records and branch tracing

, 27 Mar 2013
Rate this:
Please Sign up or sign in to vote.
Usage of Intel/AMD extended processor features.


It would seem for the most part that a large sum of the malware analysis and reverse engineering world takes for granted some of the extended features that the processor provides us. This write-up will explain the details of system debug MSR's (Model Specific Registers) for both AMD and Intel and how these features can be leveraged to user-mode level debuggers and not just code running at a CPL of 0.

It is important to note that certain debug feature MSRs vary between Intel and AMD processors. For example certain Intel CPUs provide up to 15 Last Branch records whereas AMD does not. In either case however those cannot be leveraged from code running at a CPL of 3 and is not the scope of our discussion.


The author assumes that you have a decent knowledge of Windows debugger APIs, Windows internals, and assembly. Last branch recording and branch tracing should have a solid place among the malware analyst's or software reverse engineer's arsenal of tactics for analysis of ring 3 code. The Windows OS itself provides several backdoors into leveraging these techniques from user mode. The goal of this article is to provide a decent explanation of how to use these features and incorporate them into your own debuggers and analysis tools.

Branch tracing

A branch is an instruction that can conditionally or unconditionally transfer control flow. For example any conditional jump, unconditional jump, call, ret, far call, far jump, iret, retf, int n, syscall, sysexit, icebp, etc.

The term 'branch taken' means there was an actual change in control flow resulting from the branch. In an unconditional branch instruction, the branch will always be taken. However with a conditional jump (for example following a bitwise comparison) the branch will not always be taken and is based upon the result of the prior comparison.

As you hopefully already know, the processor single-step feature (EFLAGS.TF=1) causes a #DB exception to occur after each and every instruction boundary is reached. This type of exception is known as a trap, meaning the instruction pointer that is pushed onto the interrupt handler stack will point to the next instruction to be executed.

Simple x86 example:

or dword ptr [esp], 0x100
inc eax  //<--after this instruction has finished execution,
         //      the address pushed onto the handler stack is the next instruction 
push ebx

The DebugCtl MSR provides a bit that will, when set along with EFLAGS.TF=1, only raise a #DB trap (single_step) after a branch instruction boundary has been reached, instead of every instruction. This occurs only if the branch is taken. The instruction pushed onto the handler stack is then that of the destination of the branch, which is then of course your instruction pointer EIP/RIP in the Windows debugger CONTEXT structure.

Simple x86 example:

(EFLAGS.TF=1 and DebugCtl.BTF=1)

push ebx        //<----even though trap flag is set, nothing.
push eax        //<----even though trap flag is set, nothing.
call ecx        //<----will raise a #DB exception, IP on handler stack will be the destination of the call. In this case ECX.
xor eax, eax    //<----even though trap flag is set, nothing.
inc ebx         //<----even though trap flag is set, nothing.
pop eax         //<----even though trap flag is set, nothing.
pop ebx         //<----even though trap flag is set, nothing.
ret             //<----will raise a #DB exception, IP on handler stack will 
   //be the destination of the return. In this case, the contents pointed to by ESP.

Now, how do we access DebugCtl from usermode? It's simple, and Windows provides access to both BTF and LBR bits of DebugCtl via bits 8 and 9 of DR7. If interested, see KiRestoreDebugRegisterState.

  • bit 8 of DR7 represents bit 0 of DebugCtl. This is the LBR bit. (last branch record, will explain)
  • bit 9 of DR7 represents bit 1 of DebugCtl. This is the BTF bit. (single-step on branches)

As I'm sure you can imagine, this can speed up a running trace by a long shot. Because in theory, when looking for a difference in code control flow, or a bug our answer is most likely going to rely in which branches are taken and which are not, and when only tracing branches, you can trace hundreds of thousands of instructions per second as opposed to generating an interrupt after every instruction boundary.

Now maybe you have noticed, or maybe not, this leaves us with a problem. The instruction pointer pushed onto the handler stack is that of the destination of the branching instruction. Thus RIP/EIP in your usermode CONTEXT structure will be that of the destination. What if we want to know the location of the branching instruction itself? This is where the last branch record stack comes in, also known as LBR.

Lets imagine you are already branch tracing a program with your user mode debugger or analysis tool. You have bit 9 of DR7 set to enable branch tracing, and the trap flag set as well. Here is what to do. Additionally set the LBR bit via DR7 (bit 8, as shown above). When a #DB exception occurs due to a taken branch, analyze EIP/RIP in your CONTEXT. That as stated before is your destination instruction. Now for the yummy part of the article: the address of the branching instruction itself is tucked away by Windows at EXCEPTION_RECORD->ExceptionInformation[0] provided of course that you properly enabled LBR. This is then the virtual address of the branching instruction itself which branched to whatever your instruction pointer is.

I had gotten a little creative with this myself and I couldn't find any in-depth articles on the web related to these features so I decided to write one myself. I was analyzing a little piece of software for my friend and I had noticed that prior to calling into ws32.send() it would clear the stack, and set up a fake return address then JMP to send() as to not push the original return address onto the stack, making the job of finding wherever it originated from a royal pain.

LBR to the rescue

Here is how we could easily overcome this problem, some of you may already know by this point, but read on for important details.

  • First of all, we must initialize LBR on the thread we are analyzing. In this case we do not need the BTF feature. So set bit 8 of DR7 for the thread.
  • Next we must establish a breakpoint on ws32.send() or whatever you are analyzing. Here is the important part: the type of exception raised MUST be a #DB exception.

This is because the only Windows interrupt handler that inserts the LastBranchFromIp into EXCEPTION_RECORD->ExceptionInformation[0] is the Windows int 01 handler.

The Windows int 3 handler does not do this for us, and if you use int 3 your ExceptionInformation[0] member will be empty.

You can either use a debug register breakpoint or ICEBP (int 01 with no DPL check). I would personally recommend ICEBP and here is why: The code could IRET to send() with the resume flag set. If the user of your debugger initialized a breakpoint on the first instruction of send() it would be ignored!

And let's face it, a lot of us like the put breakpoints on the first instruction of an API.

Points of Interest  -  VM detection

Besides analyzing a branch to a function which was setup with a bogus stack, the LBR feature works as a pretty decent method to detect whether or not your program is running within a hyper-visor. This works because most virtualization software including both VMware and Vbox do not make use of LBR virtualization (even though it is possible and supported).

Here is a rough example. I will leave the logic of the exception handler up to you.

RunningInHyperVisor PROC

    //eax = CONTEXT pointer
    mov [eax], 0x10 //CONTEXT_DEBUG_REGISTERS
    lea ebx, [eax+0x18]
        mov [ebx], 0x100 //LBR bit @ DR7
    push eax
    push ecx
    call SetThreadContext
    _emit 0xeb  //previous branch
    _emit 0x00
    _emit 0xf1 //icebp

RunningInHyperVisor ENDP

At this point you would then examine the contents of ExceptionInformation[0]. If running under VM it will not contain the virtual address of the most previous branch.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

United States United States
Nick is a hobbyist programmer, software reverse engineer and malware analyst.

You may also be interested in...


Comments and Discussions

QuestionThread ID and PID associates to LBRs Pin
Tritron8-Nov-16 15:22
memberTritron8-Nov-16 15:22 
QuestionLast branch recording fails on Windows 7-8 x64 but works on Windows 10 x64. Pin
Gideon711-Sep-16 16:02
memberGideon711-Sep-16 16:02 
I implemented the tests in the article to try to turn on Last Branch Recording (LBR) and Branch Trace Recording (BTR). None of the examples worked on Windows 7, Windows 8 or Windows 8.1. I tested both x86 and x64 programs and they all consistently failed. I also tested Windows Server 2008 R2 and Windows Server 2012 R2 and they failed there as well.

The tests consistently work on Windows 10.

I ran all the tests on x64 (not x86) operating systems. This is because I'm interested in detecting malware stealth VMs and rootkit viruses. Your hypervisor detection tests work great (thank you!), but they only work on Windows 10 hosts.

Did you run all your tests on x86 operating systems only? That might explain it. Hardware based VMs require x64 Long Mode (VT-x, AMD-V), so I'm only interested in testing LBR/BTR on x64 operating systems. There doesn't seem to be any way to do it in user mode. (I tried writing a mini-debugger that used WaitForDebugEvent to turn on BTR/LBR in the child process, but no joy.)

Question: Is there any way to enable LBR/BTR in user mode on x64 operating systems prior to Windows 10?
AnswerRe: Last branch recording fails on Windows 7-8 x64 but works on Windows 10 x64. Pin
Gideon716-Sep-16 18:43
memberGideon716-Sep-16 18:43 
AnswerRe: Last branch recording fails on Windows 7-8 x64 but works on Windows 10 x64. Pin
Gideon728-Sep-16 19:19
memberGideon728-Sep-16 19:19 
QuestionMore information (?) Pin
Member 1030950329-Dec-13 4:48
memberMember 1030950329-Dec-13 4:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.171020.1 | Last Updated 27 Mar 2013
Article Copyright 2012 by nick.p.everdox
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid