FreeFileSync: Connection Limits and the Tamper Check

About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.

This research is published for educational and defensive purposes. The target sample is anonymized throughout.


FreeFileSync: Connection Limits and the Tamper Check

FreeFileSync ships its source openly, but the official binaries also contain code that isn’t in the public tree — including a donation-gated performance limit and an integrity check. This is a walkthrough of both.

Breaking the performance limit

The parallel-connection control is disabled in the UI:

Disabled parallel-connection control

Reading the open-source code leads to the key constructor:

CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, Zstring& sftpKeyFileLastSelected, size_t& parallelOps, bool canChangeParallelOp) :
    CloudSetupDlgGenerated(parent),
    sftpKeyFileLastSelected_(sftpKeyFileLastSelected),
    folderPathPhraseOut_(folderPathPhrase),
    parallelOpsOut_(parallelOps)

Inside the body:

m_spinCtrlConnectionCount->SetValue(parallelOps);

m_spinCtrlConnectionCount->Disable(); // this disables the control
m_staticTextConnectionCountDescr->Hide();

m_spinCtrlChannelCountSftp->Disable();
m_buttonChannelCountSftp  ->Disable();

Dynamic debugging locates the spot:

Locating the disable call in a debugger

Nop the call and the control becomes enabled:

Control enabled after the nop

The integrity check

Apply the patch and run it — a verification dialog appears, and clicking OK exits the program:

Verification dialog that exits on OK

Debugging from that message leads to the verification logic; the WV error string gives the position away:

The verification code

FreeFileSync’s official build contains code absent from the open-source version, and it uses a simple string-obfuscation function:

The string-obfuscation function

IDA’s pseudocode:

char *__fastcall decodestring(char *outstr, char *inputstr, int key)
{
char *v3; // r9
int v5; // r8d
int v6; // ebx
char *v7; // rcx
char *v8; // r8
char *v9; // r10
char *result; // rax

v3 = inputstr;
v5 = key % 95;
v6 = v5 + 95;
if ( v5 >= 0 )
v6 = v5;
if ( *((_QWORD *)inputstr + 3) <= 0xFui64 )
{
v7 = inputstr;
v8 = inputstr;
}
else
{
v7 = *(char **)inputstr;
inputstr = *(char **)inputstr;
v8 = (char **)v3;
}
v9 = &inputstr[((_QWORD *)v3 + 2)];
if ( v8 != v9 )
{
do
{
if ( (unsigned __int8)(*v7 - 32) <= 0x5Eu )
*v7 = (*v7 + v6 - 32) % 95 + 32;
++v7;
} while ( v7 != v9 );
}
*(_OWORD *)outstr = 0i64;
*((_QWORD *)outstr + 2) = 0i64;
*((_QWORD *)outstr + 3) = 0i64;
*(_OWORD *)outstr = *(_OWORD *)v3;
*((_OWORD *)outstr + 1) = *((_OWORD *)v3 + 1);
*((_QWORD *)v3 + 3) = 15i64;
*((_QWORD *)v3 + 2) = 0i64;
*v3 = 0;
result = outstr;
*((_QWORD *)v3 + 3) = 15i64;
return result;
}

Here’s the cleaned-up version, in full:

#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>

typedef struct {
	union {
		char* ptr;
		char	local_buf[16];
	};
	size_t	size;
	size_t	capacity;
} EncryptedString;

int64_t decodestring(int64_t dest_ptr, EncryptedString* src, int key)
{
	int shift = key % 95;
	if (shift < 0)
	{
		shift += 95;
	}

	char* data_start;
	if (src->capacity <= 15)
	{
		data_start = src->local_buf;
	}
	else {
		data_start = src->ptr;
	}

	size_t	length = src->size;
	char* data_end = data_start + length;


	for (char* p = data_start; p < data_end; ++p)
	{
		unsigned char c = *p;
		if (c >= 32 && c <= 126)
		{
			c = (c - 32 + shift) % 95;
			*p = (char)(c + 32);
		}
	}
	EncryptedString* dest = (EncryptedString*)dest_ptr;
	*dest = *src;
	src->capacity = 15;
	src->size = 0;
	src->local_buf[0] = '\0';

	return(dest_ptr);
}


int main(void)
{
	char	out1[256] = { 0 };
	char	out2[256] = { 0 };

	char	p1[] = {
		0x5E, 0x70, 0x75, 0x5D, 0x6C, 0x79, 0x70, 0x6D, 0x21, 0x5B, 0x79, 0x7C, 0x7A, 0x7B, 0x00, 0x00,
		0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};
	char	p2[] = {
		0x5F, 0x71, 0x76, 0x7C, 0x7A, 0x7D, 0x7B, 0x7C, 0x36, 0x6C, 0x74, 0x74, 0x00, 0x00, 0x00, 0x00,
		0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};

	decodestring(out1, p1, 0xFFFFFFF9);
	decodestring(out2, p2, 0xFFFFFFF8);

	printf("%s\n", out1);
	printf("%s\n", out2);
	return(0);
}

The output:

Decrypted output

WinVerifyTrust

Wintrust.dll

So the check uses WinVerifyTrust to validate the binary’s signature and decide whether it’s been modified. Make that function return a valid result — or patch the following branch — and the program runs normally, with the parallel-performance control working as intended.

Running normally with the check bypassed


Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).

Get in touch