On January 10, 2023, ManageEngine released a security advisory for CVE-2022-47966 (discovered by Khoadha of Viettel Cyber Security) affecting a wide range of products. The vulnerability allows an attacker to gain remote code execution by issuing a HTTP POST request containing a malicious SAML response. This vulnerability is a result of using an outdated version of Apache Santuario for XML signature validation.
We started our initial research by examining the differences between ServiceDesk Plus version 14003 and version 14004. By default, Service Desk is installed into
. We installed both versions and extracted the jar files for comparison.
While there are many jar files that have been updated, we notice that there was a single jar file that has been completely changed.
from Apache Santuario was updated from 1.4.1 to 2.2.3. Version 1.4.1 is over a decade old.
That is a large version jump, but if we start with the 1.4.2 release notes we find an interesting change:
Switch order of XML Signature validation steps. See Issue 44629.
Issue 44629 can be found here. It describes switching the order of XML signature validation steps and the security implications.
Signature Validation – cryptographically validate the
element. This assures that the
element has not been tampered with.
While the official XML signature validation spec lists reference validation followed by signature validation, these two steps can be performed in any order. Since the reference validation step can involve processing attacker controlled XML
, one should always perform the signature validation step first to ensure that the transforms came from a trusted source.
SAML Information Flow Refresher
Applications that support single sign-on typically use an authorization solution like SAML. When a user logs into a remote service, that service forwards the authentication request to the SAML Identity Provider. The SAML Identity Provider will then validate that the user credentials are correct and that they are authorized to access the specified service. The Identity Provider then returns a response to the client which is forwarded to the Service Provider.
The information flow of a login request via SAML can been seen below. One of the critical pieces is understanding that the information flow uses the client’s browser to relay all information between the Service Provider (SP) and the Identity Provider (IDP). In this attack, we send a request containing malicious SAML XML directly to the service provider’s Assertion Consumer (ACS) URL.
Vulnerability Ingredient 1: SAML Validation Order
Understanding that SAML information flow allows an attacker to introduce or modify the SAML data in transit, it should now be clear why the Apache Santuario update to now perform signature validation to occur before reference validation was so important. This vulnerability will abuse the verification order as the first step in exploitation. See below for the diff between v1.4.1 and v.1.4.2.
In v1.4.1, reference validation happened near the top of the code block with the call to
. In v1.4.2, the call to
was moved to the end of the function after the signature verification in
Vulnerability Ingredient 2: XSLT Injection
element can contain a
element responsible for describing how to modify an element before calculating its digest. Transforms allow for arbitrarily complex operations through the use of XSL Transformations (XSLT).
Abusing the order of SAML validation in Apache Santuario v1.4.1 and Java’s XSLT library providing access to run arbitrary Java classes, we can exploit this vulnerability in ManageEngine products to gain remote code execution.
SAML SSO Configuration
Security Assertion Markup Language (SAML) is a specification for sharing authentication and authorization information between an application or service provider and an identity provider. SAML with single sign on allows users to not have to worry about maintaining credentials for all of the apps they use and it gives IT administrators a centralized location for user management.
SAML uses XML signature verification to ensure the secure transfer of messages passed between service providers and identity providers.
We can enable SAML SSO by navigating to
Admin -> Users & Permissions -> SAML Single Sign On
where we can enter our identity provider information. Once properly configured, we will see “Log in with SAML Single Sign On” on the logon page:
Since ServiceDesk runs as a service, there is no desktop to display the GUI for
so we use ProcessExplorer to check the success of the exploit.
This proof of concept was also tested against Endpoint Central and we expect this POC to work unmodified on many of the ManageEngine products that share some of their codebase with ServiceDesk Plus or EndpointCentral.
Notably, the AD-related products (AdManager, etc) have additional checks on the SAML responses that must pass. They perform checks to verify that the SAML response looks like it came from the expected identity provider. Our POC has an optional
argument to provide information to use for the
element. Additionally, AD-related products have a different SAML logon endpoint URL that contains a guid. How to determine this information in an automated fashion is left as an exercise for the reader.
In summary, when Apache Santuario is <= v1.4.1, the vulnerability is trivially exploitable and made possible via several conditions:
Reference validation is performed before signature validation, allowing for the execution of malicious XSLT transforms.
Execution of XSLT transforms allows an attacker to execute arbitrary Java code.
This vulnerability is still exploitable even when Apache Santuario is between v1.4.1 and v2.2.3, which some of the affected ManageEngine products were using at the time, such as Password Manager Pro. The original research, Khoadha, documents further bypasses of validation in their research and is definitely worth a read.
Windows ZIP extraction bug (CVE-2022-41049) lets attackers craft ZIP files, which evade warnings on attempts to execute packaged files, even if ZIP file was downloaded from the Internet.
In October 2022, I’ve come across a tweet from 5th July, from @wdormann, who reported a discovery of a new method for bypassing MOTW, using a flaw in how Windows handles file extraction from ZIP files.
Will Dormann @wdormann The ISO in question here takes advantage of several default behaviors: 1) MotW doesn’t get applied to ISO contents 2) Hidden files aren’t displayed 3) .LNK file extensions are always hidden, regardless of the Explorer preference to hide known file extensions.
So if it were a ZIP instead of ISO, would MotW be fine? Not really. Even though Windows tries to apply MotW to extracted ZIP contents, it’s really quite bad at it. Without trying too hard, here I’ve got a ZIP file where the contents retain NO protection from Mark of the Web.
This sounded to me like a nice challenge to freshen up my rusty RE skills. The bug was also a 0-day, at the time. It has already been reported to Microsoft, without a fix deployed for more than 90 days.
What I always find the most interesting about vulnerability research write-ups is the process on how one found the bug, what tools were used and what approach was taken. I wanted this post to be like this.
Now that the vulnerability has been fixed, I can freely publish the details.
What I found out, based on public information about the bug and demo videos, was that Windows, somehow, does not append MOTW to files extracted from ZIP files.
Mark-of-the-web is really another file attached as an Alternate Data Stream (ADS), named
, and it is only available on NTFS filesystems. The ADS file always contains the same content:
For example, when you download a ZIP file
, from the Internet, the browser will automatically add
ADS to it, with the above contents, to indicate that the file has been downloaded from the Internet and that Windows needs to warn the user of any risks involving this file’s execution.
This is what happens when you try to execute an executable like a JScript file, through double-clicking, stored in a ZIP file, with MOTW attached.
Clearly the user would think twice before opening it when such popup shows up. This is not the case, though, for specially crafted ZIP files bypassing that feature.
Let’s find the cause of the bug.
Identifying the culprit
What I knew already from my observation is that the bug was triggered when
process handles the extraction of ZIP files. I figured the process must be using some internal Windows library for handling ZIP files unpacking and I was not mistaken.
module loaded within Explorer process and it looked like a good starting point. I booted up IDA with conveniently provided symbols from Microsoft, to look around.
function immediately caught my attention. I created a sample ZIP file with a packaged JScript file, for testing, which had a single instruction:
WScript.Echo("YOU GOT HACKED!!1");
I then added a MOTW ADS file with Notepad and filled it with MOTW contents, mentioned above:
I loaded up
debugger, attached it to
and set up a breakpoint on
. When I double-clicked the JS file, the breakpoint triggered and I could confirm I’m on the right path.
One of the function calls I noticed nearby, revealed an interesting pattern in IDA. Right after the file is extracted and specific conditions are meet,
function is called, followed by a call to
, which opens the extracted file.
Having a hunch that
is the function responsible for adding MOTW to extracted file, I nopped its call and found that I stopped getting the MOTW warning popup, when I tried executing a JScript file from within the ZIP.
It was clear to me that if I managed to manipulate the execution flow in such a way that the branch, executing this function is skipped, I will be able to achieve the desired effect of bypassing the creation of MOTW on extracted files. I looked into the function to investigate further.
I noticed that
tries to combine the TEMP folder path with the zipped file filename, extracted from the ZIP file, and when this function fails, the function quits, skipping the creation of MOTW file.
Considering that I controlled the filename of the extracted ZIP file, I could possibly manipulate its content to trigger
to fail and as a result achieve my goal.
turned out to be a wrapper around
function with output buffer size limit set to fixed value of
bytes. I thought that if I managed to create a really long filename or use some special characters, which would be ignored by the function handling the file extraction, but would trigger the length check in
to fail, it could work.
I opened 010 Editor, which I highly recommend for any kind of hex editing work, and opened my sample ZIP file with a built-in ZIP template.
I spent few hours testing with different filename lengths, with different special characters, just to see if the extraction function would behave in erratic way. Unfortunately I found out that there was another path length check, called prior to the one I’ve been investigating. It triggered much earlier and prevented me from exploiting this one specific check. I had to start over and consider this path a dead end.
I looked if there are any controllable branching conditions, that would result in not triggering the call to
at all, but none of them seemed to be dependent on any of the internal ZIP file parameters. I considered looking deeper into
function and found out that when
call succeeds, it creates a
COM objects, which has its three methods called:
CAttachmentServices::SetReferrer(unsigned short const * __ptr64)
CAttachmentServices::SetSource(unsigned short const * __ptr64)
CAttachmentServices::SaveWithUI(struct HWND__ * __ptr64)
realized I am about to go deep down a rabbit hole and I may spend there much longer than a hobby project like that should require. I had to get a public exploit sample to speed things up.
error, when using the exploit sample and succeeded when using the original, untampered test sample.
It looked like the majority of parameters were constant, as you can see on the screenshot above. The only place where I’d expect anything dynamic was in the structure of
parameter. After closer inspection and half an hour of closely comparing the contents of the parameter structures from two calls, I concluded that both failing and succeeding calls use exactly the same parameters.
This led me to realize that something had to be happening prior to the creation of the ADS file, which I did not account for. There was no better way to figure that out than to use Process Monitor, which honestly I should’ve used long before I even opened IDA 😛.
I set up my filters to only list file operations related to files extracted to TEMP directory, starting with
The test sample clearly succeeded in creating the
While the exploit sample failed:
Through comparison of these two listings, I could not clearly see any drastic differences. I exported the results as text files and compared them in a text editor. That’s when I could finally spot it.
Prior to creating
ADS file, the call to
was made with
I looked up what was that
attribute, which apparently is not set for the file when extracting from the original test sample and then…
file attribute stands for
. The file stored in a ZIP file has the read-only attribute set, which is set also on the file extracted from the ZIP. Obviously when Windows tries to attach the
ADS, to it, it fails, because the file has a read-only attribute and any write operation on it will fail with
It doesn’t even seem to be a bug, since everything is working as expected 😛. The file attributes in a ZIP file are set in
parameter of the
structure and its value corresponds to the ones, which carried over from MS-DOS times, as stated in ZIP file format documentation I found online.
4.4.15 external file attributes: (4 bytes)
The mapping of the external attributes is
host-system dependent (see 'version made by'). For
MS-DOS, the low order byte is the MS-DOS directory
attribute byte. If input came from standard input, this
field is set to zero.
4.4.2 version made by (2 bytes)
126.96.36.199 The upper byte indicates the compatibility of the file
attribute information. If the external file attributes
are compatible with MS-DOS and can be read by PKZIP for
DOS version 2.04g then this value will be zero. If these
attributes are not compatible, then this value will
identify the host system on which the attributes are
compatible. Software can use this information to determine
the line record format for text files etc.
188.8.131.52 The current mappings are:
0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)
1 - Amiga 2 - OpenVMS
3 - UNIX 4 - VM/CMS
5 - Atari ST 6 - OS/2 H.P.F.S.
7 - Macintosh 8 - Z-System
9 - CP/M 10 - Windows NTFS
11 - MVS (OS/390 - Z/OS) 12 - VSE
13 - Acorn Risc 14 - VFAT
15 - alternate MVS 16 - BeOS
17 - Tandem 18 - OS/400
19 - OS X (Darwin) 20 thru 255 - unused
184.108.40.206 The lower byte indicates the ZIP specification version
(the version of this document) supported by the software
used to encode the file. The value/10 indicates the major
version number, and the value mod 10 is the minor version
Changing the value of external attributes to anything with the lowest bit set e.g.
, would effectively make the file read-only with Windows being unable to create MOTW for it, after extraction.
I honestly expected the bug to be much more complicated and I definitely shot myself in the foot, getting too excited to start up IDA, instead of running Process Monitor first. I started with IDA first as I didn’t have an exploit sample in the beginning and I was hoping to find the bug, through code analysis. Bottom line, I managed to learn something new about Windows internals and how extraction of ZIP files is handled.
As a bonus, Mitja Kolsek from 0patch asked me to confirm if their patch worked and I was happy to confirm that it did!
The patch was clean and reliable as seen in the screenshot from a debugger:
I’ve been also able to have a nice chat with Will Dormann, who initially discovered this bug, and his story on how he found it is hilarious:
I merely wanted to demonstrate how an exploit in a ZIP was safer (by way of prompting the user) than that *same* exploit in an ISO. So how did I make the ZIP? I:
1) Dragged the files out of the mounted ISO
2) Zipped them. That's it. The ZIP contents behaved the same as the ISO.
Every mounted ISO image is listing all files in read-only mode. Drag & dropping files from read-only partition, to a different one, preserves the read-only attribute set for created files. This is how Will managed to unknowingly trigger the bug.
Will also made me realize that 7zip extractor, even though having announced they began to add MOTW to every file extracted from MOTW marked archive, does not add MOTW by default and this feature has to be enabled manually.
I mentioned it as it may explain why MOTW is not always considered a valid security boundary. Vulnerabilities related to it may be given low priority and be even ignored by Microsoft for 90 days.
About two years ago I quit being a full-time red team operator. However, it still is a field of expertise that stays very close to my heart. A few weeks ago, I was looking for a new side project and decided to pick up an old red teaming hobby of mine: bypassing/evading endpoint protection solutions.
In this post, I’d like to lay out a collection of techniques that together can be used to bypassed industry leading enterprise endpoint protection solutions. This is purely for educational purposes for (ethical) red teamers and alike, so I’ve decided not to publicly release the source code. The aim for this post is to be accessible to a wide audience in the security industry, but not to drill down to the nitty gritty details of every technique. Instead, I will refer to writeups of others that deep dive better than I can.
In adversary simulations, a key challenge in the “initial access” phase is bypassing the detection and response capabilities (EDR) on enterprise endpoints. Commercial command and control frameworks provide unmodifiable shellcode and binaries to the red team operator that are heavily signatured by the endpoint protection industry and in order to execute that implant, the signatures (both static and behavioural) of that shellcode need to be obfuscated.
In this post, I will cover the following techniques, with the ultimate goal of executing malicious shellcode, also known as a (shellcode) loader:
1. Shellcode encryption
Let’s start with a basic but important topic, static shellcode obfuscation. In my loader, I leverage a XOR or RC4 encryption algorithm, because it is easy to implement and doesn’t leave a lot of external indicators of encryption activities performed by the loader. AES encryption to obfuscate static signatures of the shellcode leaves traces in the import address table of the binary, which increase suspicion. I’ve had Windows Defender specifically trigger on AES decryption functions (e.g.
etc.) in earlier versions of this loader.
2. Reducing entropy
Many AV/EDR solutions consider binary entropy in their assessment of an unknown binary. Since we’re encrypting the shellcode, the entropy of our binary is rather high, which is a clear indicator of obfuscated parts of code in the binary.
There are several ways of reducing the entropy of our binary, two simple ones that work are:
Adding low entropy resources to the binary, such as (low entropy) images.
Adding strings, such as the English dictionary or some of
A more elegant solution would be to design and implement an algorithm that would obfuscate (encode/encrypt) the shellcode into English words (low entropy). That would kill two birds with one stone.
3. Escaping the (local) AV sandbox
Many EDR solutions will run the binary in a local sandbox for a few seconds to inspect its behaviour. To avoid compromising on the end user experience, they cannot afford to inspect the binary for longer than a few seconds (I’ve seen Avast taking up to 30 seconds in the past, but that was an exception). We can abuse this limitation by delaying the execution of our shellcode. Simply calculating a large prime number is my personal favourite. You can go a bit further and deterministically calculate a prime number and use that number as (a part of) the key to your encrypted shellcode.
4. Import table obfuscation
You want to avoid suspicious Windows API (WINAPI) from ending up in our IAT (import address table). This table consists of an overview of all the Windows APIs that your binary imports from other system libraries. A list of suspicious (oftentimes therefore inspected by EDR solutions) APIs can be found here. Typically, these are
dumpbin /exports <binary.exe>
will list all the imports. For the most part, we’ll use Direct System calls to bypass both EDR hooks (refer to section 7) of suspicious WINAPI calls, but for less suspicious API calls this method works just fine.
We add the function signature of the WINAPI call, get the address of the WINAPI in
and then create a function pointer to that address:
Obfuscating strings using a character array cuts the string up in smaller pieces making them more difficult to extract from a binary.
The call will still be to an
WINAPI, and will not bypass any hooks in WINAPIs in
, but is purely to remove suspicious functions from the IAT.
5. Disabling Event Tracing for Windows (ETW)
Many EDR solutions leverage Event Tracing for Windows (ETW) extensively, in particular Microsoft Defender for Endpoint (formerly known as Microsoft ATP). ETW allows for extensive instrumentation and tracing of a process’ functionality and WINAPI calls. ETW has components in the kernel, mainly to register callbacks for system calls and other kernel operations, but also consists of a userland component that is part of
is a DLL loaded into the process of our binary, we have full control over this DLL and therefore the ETW functionality. There are quite a fewdifferent bypasses for ETW in userspace, but the most common one is patching the function
which is called to write/log ETW events. We fetch its address in
, and replace its first instructions with instructions to return 0 (
I’ve found the above method to still work on the two tested EDRs, but this is a noisy ETW patch.
6. Evading common malicious API call patterns
Most behavioural detection is ultimately based on detecting malicious patterns. One of these patters is the order of specific WINAPI calls in a short timeframe. The suspicious WINAPI calls briefly mentioned in section 4 are typically used to execute shellcode and therefore heavily monitored. However, these calls are also used for benign activity (the
pattern in combination with a memory allocation and write of ~250KB of shellcode) and so the challenge for EDR solutions is to distinguish benign from malicious calls. Filip Olszak wrote a great blog post leveraging delays and smaller chunks of allocating and writing memory to blend in with benign WINAPI call behaviour. In short, his method adjusts the following behaviour of a typical shellcode loader:
Instead of allocating one large chuck of memory and directly write the ~250KB implant shellcode into that memory, allocate small contiguous chunks of e.g. <64KB memory and mark them as
. Then write the shellcode in a similar chunk size to the allocated memory pages.
Introduce delays between every of the above mentioned operations. This will increase the time required to execute the shellcode, but will also make the consecutive execution pattern stand out much less.
One catch with this technique is to make sure you find a memory location that can fit your entire shellcode in consecutive memory pages. Filip’s DripLoader implements this concept.
The loader I’ve built does not inject the shellcode into another process but instead starts the shellcode in a thread in its own process space using
. An unknown process (our binary will de facto have low prevalence) into other processes (typically a Windows native ones) is suspicious activity that stands out (recommended read “Fork&Run – you’re history”). It is much easier to blend into the noise of benign thread executions and memory operations within a process when we run the shellcode within a thread in the loader’s process space. The downside however is that any crashing post-exploitation modules will also crash the process of the loader and therefore the implant. Persistence techniques as well as running stable and reliable BOFs can help to overcome this downside.
7. Direct system calls and evading “mark of the syscall”
The loader leverages direct system calls for bypassing any hooks put in
by the EDRs. I want to avoid going into too much detail on how direct syscalls work, since it’s not the purpose of this post and a lot of great posts have been written about it (e.g. Outflank).
In short, a direct syscall is a WINAPI call directly to the kernel system call equivalent. Instead of calling the
we call its kernel equivalent
defined in the Windows kernel. This is great because we’re bypassing any EDR hooks used to monitor calls to (in this example)
In order to call a system call directly, we fetch the syscall ID of the system call we want to call from
, use the function signature to push the correct order and types of function arguments to the stack, and call the
instruction. There are several tools that arrange all this for us, SysWhispers2 and SysWhisper3 are two great examples. From an evasion perspective, there are two issues with calling direct system calls:
that is loaded by default (and hooked by the EDR) with a fresh copy from
is the first DLL that gets loaded by any Windows process. EDR solutions make sure their DLL is loaded shortly after, which puts all the hooks in place in the loaded
before our own code will execute. If our code loads a fresh copy of
in memory afterwards, those EDR hooks will be overwritten. RefleXXion is a C++ library that implements the research done for this technique by MDSec. RelfeXXion uses direct system calls
to get a handle to a clean
(registry path with previously loaded DLLs). It then overwrites the
section of the loaded
, which flushes out the EDR hooks.
I recommend to use adjust the RefleXXion library to use the same trick as described above in section 7.
9. Spoofing the thread call stack
The next two sections cover two techniques that provide evasions against detecting our shellcode in memory. Due to the beaconing behaviour of an implant, for a majority of the time the implant is sleeping, waiting for incoming tasks from its operator. During this time the implant is vulnerable for memory scanning techniques from the EDR. The first of the two evasions described in this post is spoofing the thread call stack.
When the implant is sleeping, its thread return address is pointing to our shellcode residing in memory. By examining the return addresses of threads in a suspicious process, our implant shellcode can be easily identified. In order to avoid this, want to break this connection between the return address and shellcode. We can do so by hooking the
function. When that hook is called (by the implant/beacon shellcode), we overwrite the return address with
and call the original
returns, we put the original return address back in place so the thread returns to the correct address to continue execution. Mariusz Banach has implemented this technique in his ThreadStackSpoofer project. This repo provides much more detail on the technique and also outlines some caveats.
We can observe the result of spoofing the thread call stack in the two screenshots below, where the non-spoofed call stack points to non-backed memory locations and a spoofed thread call stack points to our hooked Sleep (
) function and “cuts off” the rest of the call stack.
10. In-memory encryption of beacon
The other evasion for in-memory detection is to encrypt the implant’s executable memory regions while sleeping. Using the same sleep hook as described in the section above, we can obtain the shellcode memory segment by examining the caller address (the beacon code that calls
and therefore our
hook). If the caller memory region is
and roughly the size of our shellcode, then the memory segment is encrypted with a XOR function and
is called. Then
returns, it decrypts the memory segment and returns to it.
Another technique is to register a Vectored Exception Handler (VEH) that handles
violation exceptions, decrypts the memory segments and changes the permissions to
. Then just before sleeping, mark the memory segments as
, so that when
returns, it throws a memory access violation exception. Because we registered a VEH, the exception is handled within that thread context and can be resumed at the exact same location the exception was thrown. The VEH can simply decrypt and change the permissions back to RX and the implant can continue execution. This technique prevents a detectible
The beacon shellcode that we execute in this loader ultimately is a DLL that needs to be executed in memory. Many C2 frameworks leverage Stephen Fewer’s ReflectiveLoader. There are many well written explanations of how exactly a relfective DLL loader works, and Stephen Fewer’s code is also well documented, but in short a Reflective Loader does the following:
Resolve addresses to necessary
WINAPIs required for loading the DLL (e.g.
Write the DLL and its sections to memory
Build up the DLL import table, so the DLL can call
Load any additional library’s and resolve their respective imported function addresses
Call the DLL entrypoint
Cobalt Strike added support for a custom way for reflectively loading a DLL in memory that allows a red team operator to customize the way a beacon DLL gets loaded and add evasion techniques. Bobby Cooke and Santiago P built a stealthy loader (BokuLoader) using Cobalt Strike’s UDRL which I’ve used in my loader. BokuLoader implements several evasion techniques:
Limit calls to
(commonly EDR hooked WINAPI call to resolve a function address, as we do in section 4)
Make sure to uncomment the two defines to leverage direct system calls via HellsGate & HalosGate and bypass ETW and AMSI (not really necessary, as we’ve already disabled ETW and are not injecting the loader into another process).
12. OpSec configurations in your Malleable profile
In your Malleable C2 profile, make sure the following options are configured, which limit the use of
marked memory (suspicious and easily detected) and clean up the shellcode after beacon has started.
set startrwx "false";
set userwx "false";
set cleanup "true";
set stomppe "true";
set obfuscate "true";
set sleep_mask "true";
set smartinject "true";
Combining these techniques allow you to bypass (among others) Microsoft Defender for Endpoint and CrowdStrike Falcon with 0 detections (tested mid April 2022), which together with SentinelOne lead the endpoint protection industry.
Of course this is just one and the first step in fully compromising an endpoint, and this doesn’t mean “game over” for the EDR solution. Depending on what post-exploitation activity/modules the red team operator choses next, it can still be “game over” for the implant. In general, either run BOFs, or tunnel post-ex tools through the implant’s SOCKS proxy feature. Also consider putting the EDR hooks patches back in place in our
hook to avoid detection of unhooking, as well as removing the ETW/AMSI patches.
It’s a cat and mouse game, and the cat is undoubtedly getting better.