Last Updated: 05-Mar-2006
Unleashing 10-User Conferencing in Skype 2.0 for Windows
Recently, Skype and Intel have announced
a deal that would limit Skype's functionality on all but specific Intel
processors. Currently, Skype 2.0 offers 10-way conference calls only on
Intel's latest dual-core CPUs, while other chips, including all AMD
chips, will only allow for 5-way conference calls. It is argued that
only those Intel dual-core CPUs meet the requirements - which would
imply that no AMD CPU is fast enough.
Now, what are these
requirements? Is there some kind of micro-benchmark built into Skype
which measures the processing speed? Or does Skype look for a specific
hidden CPU feature? As the details on the patch reveal, the code logic behind the limitation is quite simple:
If it's a CPU with "GenuineIntel" branding and has at least two cores, then allow 10 users; else limit to 5 users.
I've applied the patch, which is discussed below, to Skype version 126.96.36.199 (March, 1st). There is no virus or backdoor added!
You can download the original setup and the patch archive from this location:
the SkypeSetup.exe to install or update Skype. Then rename the patched
Skype executable to "Skype.exe" and use it instead of the original.
I have updated the patch on Mar, 5. The original (previous) one was not
working properly (e.g. Skype would probably close when receiving a
Details on the Patch
patch is the result of two stages: code analysis and design of the
patch. The code analysis, or reverse engineering, reveals the relevant
code block, which overrides Skype's limitation for Intel's dual-core
CPUs. The patch design isolates the minimal set of instructions that
need to be modified to cancel this limitation.
An initial analysis of the executable revealed that the code references the string "You can now add up to 9 people to this call - <a href="skype:?go#intel-10way">learn more</a>"". The relevant code block is
00672680 mov eax, dword ptr [00B8E6DC] <----- (*)
00672685 cmp dword ptr [eax], 00000004
if **(00B8E6DC) != 4 then skip
0067268A mov eax, dword ptr [ebp-04]
0067268D mov byte ptr [eax+0000015C], 01
00672699 xor ecx, ecx
0067269B mov edx, 00672880
006726A0 mov eax, dword ptr [ebp-04]
006726A3 mov eax, dword ptr [eax+00000138]
006726A9 call 00689858
interest here is also the code marked with (*). It reveals that the
string is somehow used if a certain memory location has the value 4.
Theory is, this 4 means "4 additional conference members"; and if
you're not running a dual-core Intel CPU, the string is shown and
motivates you to "upgrade" to such a CPU. OK, now we have something to
Next step is to find out, where this memory
location is modified (it's initial value in the executable is 4, so it
has to be changed in the code.) That's easy, only one location serves
as a candidate:
006253D6 sub eax, 00000001
006253D9 jno 006253E0
006253DB call 0040406C
006253E0 test eax, eax
006253E2 jns 006253E9
return value in eax
006253E9 mov edx, dword ptr [00B8E6DC]
006253EF mov dword ptr [edx],
**(00B8E6DC) := eax
Instead of analysing the call to 0x404064, we focus on the call to 0x71d474, marked above with (**):
0071D474 push ebp
0071D475 mov ebp, esp
0071D477 add esp, FFFFFFF8
0071D47A mov dword ptr [ebp-04], eax
0071D47D mov eax, dword ptr [ebp-04]
0071D480 mov eax, dword ptr [eax+04]
0071D483 push eax
0071D489 pop ecx
0071D48A mov dword ptr [ebp-08], eax
0071D48D mov eax, dword ptr [ebp-08]
0071D490 pop ecx
0071D491 pop ecx
0071D492 pop ebp
0072A02E in ax, dx
0072A02F loopnz 0072A019
0072A031 inc eax
0072A032 pop ecx
call reaches the "routine" marked with (****). However, it's not
regular code, it's garbarge. Or encrypted. The easiest way to find out
is to use a debugger. Setting the breakpoint to the code marked with
(**), 0x6253D1, and inspecting the memory reveals the true code. This
code itself is not shown here, since it's not too interesting and it's
not worthwile to drill down. Instead, by having access to decrypted
code, we may reveal new code of interest. What would that be? An
initial guess is usage of the "cpuid" instruction, which allows to
query a lot of information about the CPU. There are some usages in the
decrypted code, e.g. for querying MMX/SSE capabilities.
And there is this code, which is actually the core logic we're looking for:
007FF780 push ebp
007FF781 mov ebp, esp
007FF783 sub esp, 00000014
007FF786 push ebx
007FF787 push esi
007FF788 mov esi, 00000003
007FF78D mov [ebp-04], 00000001
007FF794 xor eax, eax
007FF796 cpuid #
returns Vendor string in ebx/ecx/edx
007FF798 mov dword ptr [ebp-14], ebx
007FF79B mov dword ptr [ebp-10], edx
007FF79E mov dword ptr [ebp-0C], ecx
007FF7A1 mov [ebp-08], 00
007FF7A5 cmp eax, 00000004
007FF7A8 jl 007FF7BD
007FF7AA mov eax, 00000004
007FF7AF xor ecx, ecx
007FF7B1 cpuid #
returns Deterministic Cache Parameters
007FF7B3 shr eax,
Max. number of processor cores -1
007FF7B6 and eax, 0000003F
Now, eax = Max. number of processor cores
007FF7BA mov dword ptr [ebp-04], eax
007FF7BD lea eax, dword ptr [ebp-14]
Pointer to string "GenuineIntel"
007FF7C5 push eax
compare vendor string with "GenuineIntel"
007FF7CB add esp, 00000008
007FF7CE test eax, eax # is it "GenuineIntel"?
No? then test for AMD...
# It's an Intel CPU
007FF7D2 mov edx, dword ptr [ebp-04] # number of CPU cores
007FF7D5 mov ecx, 00000001
007FF7DA cmp ecx, edx # cores > 1 ?
007FF7DC pop esi
007FF7DD sbb eax,
eax := (cores > 1) ? -1 : 0
007FF7DF pop ebx
eax := (cores > 1) ? 0 : 1
007FF7E1 mov esp, ebp
007FF7E3 pop ebp
007FF7E4 ret #
return eax as the result.
# It's not an Intel CPU
007FF7E5 lea edx, dword ptr [ebp-14]
Pointer to string "AuthenticAMD"
007FF7ED push edx
007FF7EE call 009DCDE0
007FF7F3 add esp, 00000008
007FF7F6 test eax,
is it "AuthenticAMD"?
007FF7F8 mov eax,
eax := 2
007FF7FD je 007FF801
007FF7FF mov eax,
eax := 3 (if not "AuthenticAMD")
007FF801 pop esi
007FF802 pop ebx
007FF803 mov esp, ebp
007FF805 pop ebp
007FF806 ret #
return eax as the result.
I've highlighted the most relevant instructions. The routine returns in register "eax" the value
- 0, if it's an Intel CPU ("GenuineIntel") with at least two cores,
- 1, if it's an Intel CPU ("GenuineIntel") with only one core,
- 2, if it's an AMD CPU ("AuthenticAMD"),
- 3, otherwise.
will be interesting to see how this result is used. Actually, there is
only one caller of the routine. Here's the essence of the logic:
007CCCE0 call 007FF780 #
check CPU vendor/cores (see above) -> eax
007CCCE5 mov esi,
copy result from eax to register esi
007CCCE7 lea ecx, dword ptr [ebx+16]
CF set to 0 if the source is 0; else 1.
007CCCEC sbb esi,
esi := Intel/Dual-Core ? 0 : -1
007CCCEE and esi,
esi := Intel/Dual-Core ? 0 : -5
007CCCF1 P add esi, 0000000A # esi := Intel/Dual-Core ? 10 : 5
007CCCF4 call 008A6260
007CCCF9 cmp esi, eax
007CCCFB jbe 007CCCFF
007CCCFD mov esi, eax
007CCCFF mov edx, dword ptr [ebx+0E]
007CCD02 push 00000000
either 10 or 5
007CCD05 push 0000007D
007CCD07 mov eax, dword ptr [edx+000006C1]
007CCD0D lea ecx, dword ptr [eax+12]
007CCD10 mov eax, dword ptr [eax+12]
007CCD13 call dword ptr
[eax] # calls code
007CCD15 pop edi
007CCD16 pop esi
007CCD17 mov al, 01
007CCD19 pop ebx
007CCD1A mov esp, ebp
007CCD1C pop ebp
Again, the relevant code is highlighted. The location 0x7CCCF1 is marked with a "P", since we will place the patch here.
looking at location 0x7CCCF1 ("P"), it's easy to see, how to build an
appropriate patch. We simply have to make sure that the register ESI
has the value 0, no matter what CPU we're running on. The "add"
instruction at 0x7CCCF1 ("P") will then result in 10, which is the
maximal allowed number of persons in a conference.
However, there are two obstacles:
- The code at 0x7CCCF1 is located in the encrypted code section of the binary.
whole code segment of the binary is checked or hashed by Skype. If
something has been modified, Skype will detect it and quits.
have two options here. One is to prepare an already decrypted binary,
and also find and circumvent the code checking/hashing. This seems to
be a lot of work. The second option is using code whch modifies the
bytes during runtime and cleans itself up afterwards. This seems simple
Let's see what we need:
- Unused space in the executable for the patching code.
call to the patching code. This call instruction has to be a) placed
somewhere in the non-encrypted code, b) executed after the decryption
has been done, and c) executed before the CPU/dual-core check is called.
point 1, remember, Skype uses a checksum/hash to check for modified
code. Thus, we have to be careful. But why place the patching code in
the code section, anyway? The header section of the binary has plenty
of space (0x88 bytes actually) and it is not checked by Skype. The
header resides at 0x400000 in memory and we have the range 0x400078 to
0x400100 for our use.
Regarding point 2, by using a debugger
and the proven trial-and-error method, an instruction for calling the
patching code, which fulfills requirements a) to c), is found at
00B7CC40 mov edi, 00B91680
Now that we have all that's required, let's design the patch logic.
we have to replace the instruction at 0xB7CC40 to call our routine with
the actual patching code, which is located in the executable's header
00B7CC40 call 00400078
The patching code works in two phases:
it is called from 0xB7CC40, it has to modify the CPUID check. Thus, it
replaces the code at location 0x7CCCF1 ("P") with a call to 0x400078
(i.e. to itself) and cleans up by restoring the original five bytes at
0xB7CC40. Finally, it sets the return address on the stack to 0xB7CC40,
so that the original code is executed.
- At some time, the
CPUID check logic is called next by Skype and the call at location
0x7CCCF1 ("P") to 0x400078 is executed. The patching code detects this
second phase by looking at the value in 0x7CCCF1. If it's already
modified (by phase one), we restore the original five bytes at 0x7CCCF1
as a clean-up measure and take action to allow for 10 conference users.
This is done by setting the ESI register to 0. Finally, it sets the
return address on the stack to 0x7CCCF1, so that the original code is
After the second phase, Skype's state is
modified to allow for 10 users. And the code segment is restored to its
original form, so Skype won't detect what has been done.
final problem needs to be solved: We cannot simply write into the code
segment - this would result in a memory protection exception, since
Windows and Skype itself set the access rights to "executable and
readable" (i.e. not writable). However, temporarily circumventing the
memory protection is easy. We just use Windows' VirtualProtect kernel
In summary, the patch code performs these steps:
- Make the code at 0x7CCCF0 - 0x7CCD00 writeable and save original access rights,
- Detect the current phase, by looking at the value in 0x7CCCF1:
- First Phase - Patch the bytes at 0x7CCCF1 to make a call the patching code (second phase).
Phase - Restore original contents of 0x7CCCF1 and modify the ESI
register. Since the registers have been saved on the stack, it modifies
the ESI value at stack offset 4.
- Restore original access rights to memory 0x7CCCF0 - 0x7CCD00.
- Restore original contents of 0xB7CC40.
Here's the complete assembler code:
00400078 push ebp
00400079 mov ebp, esp
0040007B sub esp,
Make room for two 32-bit local vars
1. Make CPUID-Check Code at 007CCCF1 writeable in memory.
0040007F lea eax, dword ptr [ebp-04]
00400082 push eax
location (007CCCF0 - 007CCD00)
0040008C mov esi, 00408258
00400093 mov eax, 007CCCF1
2. Has CPUID-Check at 0x007CCCF1 been restored to its original bytes?
00400098 cmp byte ptr [eax], E8
No, then goto 004000A9
# Case 2a: Modify 0x007CCCF1 to call patching code
0040009D mov dword ptr [eax], C33382E8
004000A3 mov [eax+04], FF
004000A7 jmp 004000B8
Case 2b: Restore original bytes at 0x007CCCF1 and clear ESI register
004000A9 mov dword ptr [eax], E80AC683 # Restore original 5 bytes
004000AF mov [eax+04],
- " -
004000B3 and dword ptr [esp+04], 00000000 # Set esi register on stack to 0.
3. Restore access rights of CPUID-Check Code at 007CCCF1 in memory
004000B8 lea eax, dword ptr [ebp-08]
004000BB push eax
restore original access rights
004000BF push 00000010
004000C1 push 007CCCF0
# 4. Restore original bytes at 0x00B7CC40
004000C8 lea eax, dword ptr [ebp-04]
004000CB push eax
004000CE push 00000010
location (00B7CC40 - 00B7CC50)
004000D7 mov eax, 00B7CC40
004000DC mov dword ptr [eax], B91680BF # Restore original 5 bytes
004000E2 mov [eax+04],
- " -
004000E6 lea eax, dword ptr [ebp-08]
004000E9 push eax
restore original access rights
004000ED push 00000010
004000EF push 00B7CC40
# Clean up and return to restored instruction
004000F7 mov esp, ebp
004000F9 pop ebp
004000FA sub dword ptr [esp], 00000005 # change EIP on the stack
screenshot below is from a system with an AMD X2 CPU. Please don't get
confused; this is Windows - just a bit visually enhanced by the
wonderful FlyakiteOSX transformation pack.