Beyond Procedures: Understanding the function call stack (2023)

In the cybersecurity industry, many of us have a natural inclination to delve into technical concepts and understand what goes on under the hood. Or, if you're like me, you develop this over time and realize many of the complexities and dependencies you've overlooked, leading to a desire to dig deeper and find out what's out there. is going on. I've found this challenges assumptions and forces a shift in perspective, which I think is healthy for an analytical mindset.

More recently, this has led to an interest in delving into what lies below the 'procedural' level. For many of us, the MITER ATT&CK matrix represents the conceptual model for how enemy making works. There are Tactics, Techniques and Procedures (TTPs), and these explain the motives, actions and methods used by adversaries to achieve their malicious goals.

If you're anything like me, you may have stopped at the "Procedure" level and didn't take the time to delve into all the intricacies. Because of my fixed mindset, I hardly thought this was as deep as it needed to be. It has become clear to me that there is a clear fallacy in this way of thinking, as such ways of thinking can encourage the development of detection and mitigation methods that focus on more static Indicators of Compromise (IOCs). However, once we look under the hood of the "proceedings", we discover that there is a whole host of activities going on. In this case, that's itfunction call stackwhat Jared Atkinson talks about on his blog,Understand the CallStack feature.

In my case, it seemed a bit daunting at first. I had never delved into these concepts before and was afraid I would have to learn a lot more things before I could. This too was a wrong assumption. I recently had the opportunity to analyze a few different tools as part of the methodology he outlined in this blog. My purpose here is to provide an overview of how I went about this as well as a methodology you can use for basic analysis of the function call stack. Once you can, I'm sure you'll discover a whole new level of concepts worth discovering and understanding to demystify enemy craft.

As an administrative note, this blog is written for you to follow as I walk you through the steps. I'll conclude with a step-by-step methodology for quick reference. And just in case you want to join, I've also included a requirements section at the bottom of this blog for your convenience.

Let's start!


Originally, I struggled with the idea that I needed a solid knowledge base to handle the function invocation stack. While this is not at the level I initially assumed, I think there are at least a few things you need to understand. First, familiarity with the function call stack discussed in the previous blog link is a good place to start (you don't need to understand every detail, but you should be able to look at it and get a sense of what's in it). before you go conceptual). Second, you don't need to be an experienced programmer, but you do need to be familiar with reading code - it's important to know the flow, variables, different statements (if-then, else, etc.). And of course you have to be willing to learn over time. Things may not make sense at first, but as you dig deeper, read the documentation, look at the code, and analyze the connection between functions, things will start to connect and make more sense.

To that end, I decided to use a fairly simple tool for my analysis. InsidePowersplitIn the Privilege Escalation module, the PowerUp script is written byWill Schroeder, which contains a function calledGet-ProcessTokenPrivilege. This function isn't necessarily malicious, but it had everything I needed to better understand the methodology for analyzing the function call stack.

For the context, Get-ProcessTokenPrivilege is a PowerShell function that returns privileges for a current or specified process ID. So an attacker can use this to discover the permissions for the process they are using or for a process they are trying to switch to. To do this, a process handle is opened, the process token is opened, the token information is requested, and then the token handle is closed. It's slightly different when you query the process you're in (pseudo handle vs regular handle), but otherwise it's the same.

For this analysis, we'll look at one of those steps to understand what's going on under the hood. This analysis is straight forward and should give you a good understanding of what the function call stack looks like when dealing with a single function. Note that I am not repeating these steps for every function as it is redundant for this tool.


Beyond Procedures: Understanding the function call stack (2)

I have shown one aboveShareof the PowerUp code. If you read on now, you'll notice that I started with a later function in Get-ProcessTokenPrivilege and chose to start with OpenProcessToken instead of OpenProcess. You can join here if you like, or start at the beginning (OpenProcess) and go from there  - "I'll provide the guidelines for both, as there are minimal differences between the two." What you see above is definitely the code block in PowerUp script that references OpenProcessToken. To give some context, this is where a process handle was obtained (note the "IF" statement above, which checks the validity of the process handle) and passed it to the function on the line marked with a green square ($ ). becomes Advapi32::OpenProcessToken). Well, to go deeper into the function call stack, we won't bother with everything you see listed here. It is the specific OpenProcessToken function we are interested in. A useful piece of context information we also have here is that there is an explicit declaration of the Dynamic Link Library (DLL) from which OpenProcessToken is exported - Advapi32. I would note that you don't always see a reference to the DLL. So if you only have the function, it makes sense to look at the API documentation, which has a list of requirements. So in this caseOpenProcessTokenThis confirms Advapi32 as the exporting library.

Beyond Procedures: Understanding the function call stack (3)

With that in mind, we need to look at this referenced library and find out what's really going on under the hood. For this we need a software disassembler - which I useIDA(although you can also use Ghidra). A few tips: Make sure you open IDA as an administrator or you will have problems loading the DLL. and Advapi32 is located in C:\Windows\System32\, which you should go to if IDA asks for it.

After you open IDA, make sure you see the function you want. This is done in the lower right corner of IDA (A). From there you can look at the Export tab (B), press CTRL+F and type the function name ©. The function should appear at the top (D). . The documentation for this function, of course, pointed out that the module is advapi32.dll, so it should also appear in the functions window on the left (E). If you look closely you will see that this is not the case (F, G). Instead you have OpenProcessTokenStub. This is fine and will not affect your ability to continue analyzing. Just double-click OpenProcessToken in the Exports tab and you'll be taken to the function in the disassembled output.

Beyond Procedures: Understanding the function call stack (4)

Selecting this will take you to the IDA View-A window, which will show:

Beyond Procedures: Understanding the function call stack (5)

The interesting thing here is that the process we're seeing isn't really OpenProcessToken, but OpenProcessTokenStub (A, similar to what we saw in the functions window in the last step). And the only reference to OpenProcessToken is below where it refers to __imp_OpenProcessToken(B).

This is interesting because it is another indicator that OpenProcessToken is not in advapi32.dll. However, this is not uncommon and is often seen when viewing features. In this case, this "__imp_" part indicates that OpenProcessToken is an imported function. Which ultimately means...

Beyond Procedures: Understanding the function call stack (6)

From here, since it's imported, you'll need to go to the "Import" tab and look for OpenProcessToken (another CTRL+F). If you do, you should get this output, which might seem a bit confusing:

Beyond Procedures: Understanding the function call stack (7)

Here you can see the library OpenProcessToken (A) was imported from, listed as "api-ms-win-core-processthreads-l1-1-0" (B). This is the so-calledAPI-set, and unfortunately does not appear in your System32 folder as a DLL that you can disassemble in IDA.

That's okay, because there's still a way around this. James Forshaw's NtObjectManager tool has a cmdlet called Get-NtApiSet. With the following command structure "Get-NtApiSet -Name' we can trace this API set back to the 'endpoint' DLL from which the original function is exported.

Beyond Procedures: Understanding the function call stack (8)

In this case it is kernel32.dll. This means that we should actually be looking for OpenProcessToken in Kernel32. In that case you can follow exactly the same steps as described earlier to see what is going on with OpenProcessToken. I will not discuss them in detail here. So if you want to open Kernel32 in IDA and do the same, feel free to go for the extrarep.

If you do, you will find it all againIsexported but does not appear in the functions listed in the left pane I referenced above. In addition, you will notice that if you select it from the Export tab, you will get:

Beyond Procedures: Understanding the function call stack (9)

So again we see that OpenProcessToken was imported from somewhere else. If you go to the "Imports" tab and search for "OpenProcessToken", you will see something familiar:

Beyond Procedures: Understanding the function call stack (10)

It is imported from another API set. If you look closely, this is exactly the same API set as before. From a previous step, we now know that api-ms-win-core-processthreads-l1-1-0 points to kernel32.dll as the host module. What now? We have a function that imports, but the reference library is the same DLL that says it imports. To get around this, we need to access the objects through the Get-NtApiSet output we used earlier. Specifically, the Hosts object. So our command changes slightly: `Get-NtApiSet -name| Select-Object -ExpandProperty Hosts`. This expands the output so we can better look at the API set associated with OpenProcessToken. Here we can see that instead of kernel32.dll we have kernelbase.dll. Without going into too much detail, different functions do this - they exist in different DLLs, and when this happens there is a primary DLL they point to (Kernel32 in this case). However, it can still be referenced as an imported function from this DLL. In this case there is some kind of "base" DLL from which they are imported (KernelBase in this case). If it sounds confusing, it is. But if you're really curious why that is, then so be itPatent documentThat explains the concept in detail.

Beyond Procedures: Understanding the function call stack (11)

From here it is a similar process to before. Open IDA, throw in kernelbase.dll and now you see two things: OpenProcessToken is shown as an exported function (first image) and as a basic function (second image):

Beyond Procedures: Understanding the function call stack (12)
Beyond Procedures: Understanding the function call stack (13)

You can choose any of them (since they take you to the same line in the teardown output). At this point you will probably see a text representation, but you can left-click and select Graphical representation for a more visual representation of the function (not shown).

Beyond Procedures: Understanding the function call stack (14)

In both cases, if we look at the function, we can see that it calls a separate function. In this case, the pink text marks this call: "__imp_NtOpenProcessToken". Again, __imp_ means it's an imported function. So we need to go back to the "Import" tab and look for it. However, in this case we don't need to worry about an API set since NtOpenProcessToken(A) is imported from NTDLL(B). As a side note, you may notice that when we search for NtOpenProcessToken, we find a similar function (NtOpenProcessTokenEx) that appears to be related, but not on our function call stack. If you're interested, I'd recommend looking back at this function to find out why it's different and in what situation it might be called instead of NtOpenProcessToken.

Beyond Procedures: Understanding the function call stack (15)

From here we can open ntdll.dll in IDA and do our final search. As before, we see NtOpenProcess in both the "Exports" list (A) and the Functions list (B):

Beyond Procedures: Understanding the function call stack (16)

If we select and open this (text or map view is fine), we see that this function makes a final call in the form of a "syscall" or system call (greensquare):

Beyond Procedures: Understanding the function call stack (17)

This system call is the last step in the function call stack and represents the basic level operation. In this case, we can call this operation "Token Open". At this point, there are no other DLLs to go into further detail - that's how deep we go for our analysis. Graphically, this function call stack looks like this:

Beyond Procedures: Understanding the function call stack (18)

Well, if you're like me, you might think that this whole process seems overkill just to find out what's going on under the hood. And that's easy to imagine when the first call is to OpenProcessToken and the underlying system call shows essentially the same thing happening. However, this is not the case with all Win32 APIs (moreHere), and as you use that knowledge and delve into other tools, you'll find there's often more to it than just a simple process. Or a tool may have several IF-ELSE statements that perform multiple sets of operations to pursue a specific goal. And these are, of course, what can make a particular tool stand out when it comes to understanding what the tool does under the hood. While I won't go into all of these variants, keep in mind that this walkthrough is only meant to give you enough knowledge to do it yourself. I know that once I had the tools to do this, I started asking a lot more questions, doing a lot more research, and learning a lot more about the intricacies of the function call stack.

Of course, if you remember, there are other Win32 APIs referenced in the function. As I said, I won't go through this one explicitly, because the instructions here would just be repeated multiple times for roughly the same operation. However, should you decide to go through them all, you'll find that your final product will look something like this:

Beyond Procedures: Understanding the function call stack (19)

If your script looks slightly different, this is an opportunity I encourage you to take another look at the PowerUp script to find out why there is a discrepancy. If you're having a hard time, I'll present the methodology I've learned that I hope you can use in further iterations of analyzing the function call stack.


For clarity, I present the general methodology I followed for this blog in a list format with minimal description. I assume you have read the previous section. So if you have a question about a step here, I'll explain it above.

  1. Start with the tool you want to view.
  2. Perform a static analysis of the code. Can you find references to Win32 APIs? If not, that's fine, but you'll need to dig into the features a bit to see what's going on. An example of where such a problem can arise is in a tool like Impacket, where functions are written in Python, but ultimately point to a function that you can then examine for further analysis.
  3. Review the feature's documentation and locate the DLL associated with the identified feature. While Microsoft has noteverythingIf documented, these features should be documented at a high level and include the DLL in the requirements section.
  4. Call the DLL in IDA and be careful where to look for your target function. There are three places you can and should look in sequence: (1) Export (far right tab) - the function shouldalwaysappear in this list; (2) Functions (leftmost window)  –  the functionbe able toappear in this list; (3) Import (tab, far right) - the functionbe able toare in this list, but first check what happens under "Exports" and "Function".
  5. If you find functions that call an imported function ("__imp_"), look at the library they refer to. This can be a separate DLL, or it can be an API set with a naming convention like "api-ms-win-security-base-l1-1-0" or something similar. If it is another DLL, repeat steps 4-5.
  6. If the referenced library is an API set, don't forget to use Get-NtApiSet from the NtObjectManager module. This helps you find the host module the function was exported from and avoids the confusion of API sets pointing to the DLL you just came from. Don't forget to list "hosts" to verify which host module is the final resting place of each function.
  7. Once you get to NTDLL, look for the base system call. This is the last call on the function call stack and the last point in determining the underlying operation that is happening in user mode. Note that you can find NTDLL-level (or even higher-level) functions that reference multiple functions. This requires additional analysis, but the steps described here are enough to keep digging.
  8. Once you've identified the entire function call stack, move on to the next function and make sure they're aligned in a meaningful way. Sometimes functions are written one after the other; Sometimes there are many IF-ELSE statements that can distort your analysis.
  9. Share Your Experiences  -  If you've learned something new, I encourage you to write down your own experiences and share what you've learned.


Going through this process was a bit daunting for me at first, but I was eager to get better because I wanted to move beyond the level of "procedure" that we usually get stuck on for some reason. For me, this mental model of "tactics, technique, and procedure" ended at the level of "procedure," and I didn't think about what might lie below that. My mistake.

But as I went through this process, I realized that it's less daunting than it first appears. In some ways, it can be very simple and easy to understand what's going on under the hood. I've also learned a bit more about what goes on under the hood of a particular tool, and now that I've looked at different tools, I'm starting to see similarities in how they work. Between opening or creating handles, opening tokens, requesting tokens, and closing handles, these operations share a commonality between tools that doesn't immediately become apparent without close inspection.

Of course, it should be noted that my own walkthrough is quite simple - and it's supposed to be. I want to help others get started, so this blog may not be enough for the in-depth technical analysis you need to delve into more complex tools. This is especially true if you don't have the codebase to look at, but that's a whole other matter. This also applies to tools that use Remote Procedure Calls (RPC) - there is a similar methodology with some minor differences that I won't cover here.

If you have any questions, get in touch here:LinkedIn, ofOn Twitter. I look forward to helping others in their own learning. From here, I'd also recommend continuing with Jared Atkinson's blog series:Detection: from tactical to functionalas we continue to cover things like:how different tools start to take advantage of similar features;how to deal with RPCcompared to standard Win32 APIs;composite functions, which contain multiple functions nested in one; Andfunctional variationsthat makesthousandsVariations on how an attacker can perform a specific operation. For more information or the references I've listed, see the following resources. Otherwise, good luck everyone as you continue to look under the hood to better understand the technologies we work with.

Special thanks

A lot of recognition goes into itJohnny Johnsonfor answering a barrage of questions through my own learning experience,Jared AtkinsonBefore breaking off and making the first click, and before thatEvan McBroomfor descending to the patent level to answer one of my many "but why" questions.





Arrows GraphingSite



More information about the CallStack Feature:

Patent document

Beyond Procedures: Understanding the function call stack (20)

Beyond Procedures: Understanding the function call stackwas originally published inContributions from SpectreOps team memberson Medium, where people continue the conversation by highlighting and responding to that story.

*** This is a syndicated blog from Security Bloggers NetworkContributions from SpectreOps team members - Mediumwritten byNathan D.. Read the original post:

Top Articles
Latest Posts
Article information

Author: Ms. Lucile Johns

Last Updated: 01/20/2023

Views: 6807

Rating: 4 / 5 (61 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Ms. Lucile Johns

Birthday: 1999-11-16

Address: Suite 237 56046 Walsh Coves, West Enid, VT 46557

Phone: +59115435987187

Job: Education Supervisor

Hobby: Genealogy, Stone skipping, Skydiving, Nordic skating, Couponing, Coloring, Gardening

Introduction: My name is Ms. Lucile Johns, I am a successful, friendly, friendly, homely, adventurous, handsome, delightful person who loves writing and wants to share my knowledge and understanding with you.