GCC 2022
x86-64 assembly, linux reverse-engineering!
Binary exploitation is a broad topic within cybersecurity that comes down to finding a vulnerability in the program and exploiting it to ... modify the program’s functions
~infosecwriteups.com
Example #1
Example #2
function stacks
int foo(int index, int val) {
int arr[4] = {1, 2, 3, 4};
int untouched = 0x1337;
arr[index] = val;
return untouched;
}
void main() {
foo(0, 0xdead);
}
Function locals
int foo(int index, int val) {
int arr[4] = {1, 2, 3, 4};
int untouched = 0x1337;
arr[index] = val;
return untouched;
}
void main() {
foo(7, 0xdead);
}
Out of bounds locals
int main() {
char buf[16];
int untouched = 0xdead;
gets(buf);
return untouched;
}
Buffer overflow (variable)
int foo(int index, int val) {
int arr[4] = {1, 2, 3, 4};
int untouched = 0x1337;
arr[index] = val;
return untouched;
}
void main() {
foo(?, 0xdead);
}
Overwrite saved rip (OOB)
int main() {
char buf[16];
int untouched = 0xdead;
gets(buf);
return untouched;
}
Overwrite saved rip (Buffer)
Heap-based buffer overflow in the pcnet_receive
function in hw/net/pcnet.c
in QEMU allows guest OS administrators to cause a denial of service (instance crash) or possibly execute arbitrary code via a series of packets in loopback mode.
Normally used to run different guest operating systems, e.g. IOT firmware, Linux
emulating other CPU architectures, e.g. ARM, MIPS
This piece of code in pcnet_receive
appends a CRC checksum to the end of a packet.
uint8_t *src = s->buffer; /* this is the packet */
/* ... */
else if (s->looptest == PCNET_LOOPTEST_CRC ||
!CSR_DXMTFCS(s) || size < MIN_BUF_SIZE+4) { /* just one of these conditions need to be true */
uint32_t fcs = ~0; /* the accumulated checksum */
uint8_t *p = src;
/* go through the packet byte by byte, and keep updating the checksum */
while (p != &src[size]) /* stop after reading up to the given size */
CRC(fcs, *p++); /* update fcs with the CRC checksum, also increments p */
*(uint32_t *)p = htonl(fcs); /* save the checksum at the end of the packet */
size += 4;
}
Hmm, everything looks ok so far?
PCNetState
structure
struct PCNetState_st {
NICState *nic;
NICConf conf;
QEMUTimer *poll_timer;
int rap, isr, lnkst;
uint32_t rdra, tdra;
uint8_t prom[16];
uint16_t csr[128];
uint16_t bcr[32];
int xmit_pos;
uint64_t timer;
MemoryRegion mmio;
uint8_t buffer[4096]; /* the packet */
qemu_irq irq; /* can we write the checksum into here 🤔 */
void (*phys_mem_read)(void *dma_opaque, hwaddr addr,
uint8_t *buf, int len, int do_bswap);
void (*phys_mem_write)(void *dma_opaque, hwaddr addr,
uint8_t *buf, int len, int do_bswap);
void *dma_opaque;
int tx_busy;
int looptest;
};
This is the structure of s
in the previous slide
Btw, the program accepts a packet (buffer
) with size
of 4kB (4096
bytes) 😏
IRQState
structure
typedef void (*qemu_irq_handler)(void *opaque, int n, int level);
struct IRQState {
Object parent_obj;
qemu_irq_handler handler;
void *opaque;
int n;
};
typedef struct IRQState *qemu_irq;
struct PCNetState_st {
/* ... */
uint8_t buffer[4096]; /* the packet */
qemu_irq irq; /* can we write the checksum into here 🤔 */
/* ... */
};
qemu_irq
is a pointer to an IRQState
Importantly, there is a function pointer (qemu_irq_handler handler
) in an IRQState
handler
used?A few times... e.g. pcnet_receive --> pcnet_update_irq --> qemu_set_irq
void qemu_set_irq(qemu_irq irq, int level)
{
if (!irq)
return;
irq->handler(irq->opaque, irq->n, level);
}
So, what if we can control handler
?
Attacker sends a packet of 4096
bytes
/*
* pcnet_receive()
* appends checksum to end of Buffer
* |
* |
* if buffer size is 4096
* | PCNetState_st
* | +--------+
* write the checksum | ... |
* out-of-bounds +--------+
* | | buffer |
* | +--------+
* +------------>| irq +----+ IRQState
* +--------+ | +---------+
* | ... | | | ... |
* +--------+ | +---------+
* +------>| handler |
* +---------+
* | ... |
* +---------+
*/
The irq
field is overwritten
The attacker can create a fake IRQState
somewhere in memory. Then, set the packet contents so that its checksum is the address of the fake IRQState
/*
* pcnet_receive()
* appends checksum to end of buffer
* | Fake IRQState
* | +--------------+
* if buffer size is 4096 | ... |
* | PCNetState_st +--------------+
* | +--------+ +------->| handler |
* write the checksum | ... | | +--------------+
* out-of-bounds +--------+ | | ... |
* | | buffer | | +--------------+
* | +--------+ |
* +------------>| irq +----+ IRQState
* +--------+ x +---------+
* | ... | x | ... |
* +--------+ | +---------+
* +------>| handler |
* +---------+
* | ... |
* +---------+
*/
The handler
field is now attacker-controlled
At the end of pcnet_receive
, it calls pcnet_update_irq
, which calls qemu_set_irq
, which calls the attacker-controlled handler
/*
* pcnet_receive
* |
* pcnet_receive() v
* appends checksum to end of buffer pcnet_update_irq
* | Fake IRQState |
* | +--------------+ v
* if buffer size is 4096 | ... | qemu_set_irq
* | PCNetState_st +--------------+ |
* | +--------+ +------->| handler |<----------------+
* write the checksum | ... | | +--------------+
* out-of-bounds +--------+ | | ... |
* | | buffer | | +--------------+
* | +--------+ |
* +------------>| irq +----+ IRQState
* +--------+ x +---------+
* | ... | x | ... |
* +--------+ | +---------+
* +------>| handler |
* +---------+
* | ... |
* +---------+
*/
Attacker now has arbitrary code execution
IRQState
Need to chain this bug with a memory leak vulnerability, e.g. out-of-bounds read/copy