Yesterday, Matt Green asked me to take a look one of the leaked NSA tools, SECONDDATE, to try to confirm some of its behavior. See Sam Biddle’s Intercept article for details on that.
SECONDDATE is cross-platform and is apparently designed to be loaded into some existing process’s address space. The code relies on the functionality of the host process. In an apparent attempt to simplify the construction of the code, the host functionality is captured by a table of function (and, I’m guessing, data) pointers. Calling a host function requires indexing into the table. If this sounds like calling a function in a dynamic library which could be loaded at any address, you’re right.
Calling a host function
As a simple example of calling a host function, consider the SD_getTime
function which does nothing but call some function in this table. Here’s the x86 version of the function.
SD_getTime:
push ebx
sub esp, 8
call __i686_get_pc_thunk_bx
add ebx, 3EA3h
mov ecx, ds:(OS_VER_ptr - 0E3CCh)[ebx]
mov edx, [ecx]
mov eax, [edx]
call dword ptr [eax+58h]
add esp, 8
pop ebx
retn
The equivalent C code is a little clearer, but obfuscates the mechanism.
int SD_getTime()
{
return (*(int (**)(void))(*(_DWORD *)OS_VER + 0x58))();
}
The __i686_get_pc_thunk_bx
function simply sets ebx
to be the address of the following instruction which happens to be 0xA529
. Now IDA has helpfully identified that this is trying to load from the address named OS_VER_ptr
(located at address 0x70
). And indeed, we can confirm this is the case by computing (0x70
- 0xE3CC
) + 0xA529
+ 0x3EA3
= 0x70
. This is standard position-independent code. OS_VER_ptr
is the address of a pointer to OS_VER
which is itself the address of a pointer to the table of functions. Finally, 0x58
bytes into that table is the address of the host function to call.
Now I said that this is cross-platform and indeed, you can see that the PPC version is very similar.
offset_to_got2_end:.long -0xD24 # DATA XREF: SD_getTime+14r
SD_getTime:
.set var_8, -8
.set arg_4, 4
stwu sp, -0x18(sp) # Store Word with Update
mflr r7 # Move from link register
bcl 20, 4*cr7+so, loc_8E88 # Branch Conditional
loc_8E88:
stw r30, 0x18+var_8(sp) # Store Word
mflr r30 # Move from link register
lwz r6, -0x10(r30) # offset_to_got_end # Load Word and Zero
stw r7, 0x18+arg_4(sp) # Store Word
add r30, r6, r30 # Add
lwz r5, -0x7FFC(r30) # 0x120
lwz r4, 0(r5) # Load Word and Zero
lwz r3, 0(r4) # Load Word and Zero
lwz r0, 0x58(r3) # Load Word and Zero
mtctr r0 # Move to count register
bctrl # Branch unconditionally
lwz r30, 0x18+var_8(sp) # Load Word and Zero
lwz r4, 0x18+arg_4(sp) # Load Word and Zero
addi sp, sp, 0x18 # Add Immediate
mtlr r4 # Move to link register
blr # Branch unconditionally
The mechanism is roughly the same: the bcl
stores the address of the following instruction into the link register which is moved to r30
. The offset_to_got2_end
variable holds the offset to the end of the .got2
section which is added to r30
. Then -0x7FFC
is added to r30
and a word is loaded which happens to be the address of OS_VER
.
.got2:00000120 .long OS_VER
As before, OS_VER
is loaded (into r4
) which holds the address of the host function table. This is loaded into r3
and finally the word 0x58
bytes into the table is loaded into r0
, moved to the count register, and called.
Setting up the host function table
The host function table must be constructed ahead of time for each operating system architecture and version. The table along with the code most be loaded into memory and the OS_VER
variable needs to be set to the table’s address. This is handled by code in the .got_loader
(which also fixes up the .got
/.got2
references to things like PCRE tables).
The mechanism for setting OS_VER
appears different for different versions of SECONDDATE. For example, in the PPC (BANANAGLEE/BG2100/Install/LP/Modules/PPC/SecondDate-2123.exe
) and MIPS (BANANAGLEE/BG2100/Install/LP/Modules/MIPS/SecondDate-2122.exe
) versions, the address of the table is hard coded. In the PPC version, it is set to 0x61FF8
.
bl _dummy_load # Branch
_dummy_load:
mflr r0 # Move from link register
mr r21, r0 # Move Register
addi r21, r21, -0x28 # Add Immediate
addi r22, r21, _end # Add Immediate
lis r23, 6 # 0x61FF8 # Load Immediate Shifted
addi r23, r23, 0x1FF8 # 0x61FF8 # Add Immediate
stw r23, 4(r22) # Store Word
(The final store is storing 0x61FF8
four bytes beyond _end
which happens to be OS_VER
.) In the MIPS version, it is 0x80000
.
bal dummy # Branch Always and Link
nop
dummy:
la $s1, dummy # Load Address
beqz $s1, cleanup # Branch on Zero
subu $s1, $ra, $s1 # Subtract Unsigned
la $s3, OS_VER # Load Address
addu $s3, $s1 # Add Unsigned
lui $s4, 8 # Load Upper Immediate
sw $s4, 0($s3) # Store Word
The newer x86 version (BANANAGLEE/BG3000/Install/LP/Modules/PIX/SecondDate-3021.exe
) is a little more interesting. It expects the address of the table to be the four bytes in memory directly preceding the binary.
_start:
pusha
call SELF
SELF:
pop edx
sub edx, 6
mov eax, edx
sub eax, 4
mov ecx, eax
mov ds:OS_VER[edx], ecx
This code appears at the very beginning of the binary so the sub edx, 6
sets edx
to the loaded address of _start
. Therefore the final mov
is setting OS_VER
to be _start - 4
.
Overall impression
Although I was only looking at this code for a very specific purpose and didn’t examine its real functionality in depth, the code quality and general design seems much better than the redirector code I was looking at two days ago.