HackToday Finals

circle-info

Team: girls band cry

Rank: 3rd / 10

Challenge
Category

Binary Exploitation

yqroo wants a job

Binary Exploitation

Analysis

given a binary called stegoscan, first lets check its type and security mechanism

└──╼ [β˜…]$ file stegoscan
stegoscan: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=984429b7f4d456663e5c4dbd7050a337e0530bbb, for GNU/Linux 3.2.0, not stripped
└──╼ [β˜…]$ pwn checksec stegoscan
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

luckily the author is kind enough to provide the source code:

stegoscan.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

#define MIN_IMGSIZE 400 // 20x20
#define MAX_IMGSIZE 900 // 30x30

#define TRIGGER_SIZE 15
uint8_t trigger[] = "hanasuru's-fans";

typedef struct {
  char signature[2];
  uint32_t fileSize;
  uint32_t reserved;
  uint32_t dataOffset;
  uint32_t headerSize;
  int32_t width;
  int32_t height;
  uint16_t colorPlanes;
  uint16_t bitsPerPixel;
  uint32_t compression;
  uint32_t imageSize;
  int32_t horizontalResolution;
  int32_t verticalResolution;
  uint32_t numColors;
  uint32_t importantColors;
} BMPFile;

void error(const char *error) {
  printf("ERROR: %s\n", error);
  exit(-1);
}

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);
	fread(&bmp->imageSize, sizeof(uint32_t), 1, file);
	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 sequenceDetected(const uint8_t *arr, uint32_t size) {
  for(int i=0; i<(size-TRIGGER_SIZE + 1); ++i) {
    if(memcmp(arr+i, trigger, TRIGGER_SIZE) == 0)
      return 1;
  }
  return 0;
}


void scan(const uint8_t *bitmap, uint32_t dim) {
  for(int i = 0; i < dim; ++i) {
    printf("[%02d] : ", i + 1);
    if(sequenceDetected(bitmap+(i * dim), dim))
      printf("FAIL\n");
    else
      printf("PASS\n");
  }
}

int main(int argc, char **argv) {
  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);

  uint8_t pixelBuf[bmp->imageSize];

  int c = 0, i = 0;
  while((c = fgetc(file)) != EOF)
    pixelBuf[i++] = (uint8_t)c;

  scan(pixelBuf, bmp->width);

  fclose(file);
  return 0;
}

__attribute__((constructor))
void setup(void) {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
}

the gist of the program is that it will parse a .bmp file and does an egghunt for the string "hanasuru's-fans" within the data.

we're also given a dummy.bmp to test the program's functionality

looking through this blogarrow-up-right I understand a bit more of the .bmp format:

as we can see the, the program parses all of the 4 section but the ColorTable section (thankfully by the author to reduce complexity hehe)

next, the program does some validation, such as:

  • signature check

  • minimum and maximum size

  • dimension

the vulnerability lies here:

the pixelBuf array is initialized with the size of imageSize, however fgetc reads until EOF .

imageSize is just a variable within the .bmp format and can be set arbitrarily without having to be the same as the actual .bmp size. this means it's possible to have the RasterData's size data section bigger than what is specified in imageSize. thus enabling a buffer overflow.

Exploitation

this type of challenge is called a one shot since we can only interact with the binary once and give our payload input only once, rather different than the usual heap CRUD if you're familiar with it where you can interact with it multiple times.

considering the binary is statically linked with no pie and canary, a one shot here is definitely feasible relatively easy.

to start with, let's create a function to craft our payload according to the .bmp format we saw before

next we'll make sure for the checks mentioned above are satisfied

next we'll format the Header and InfoHeader sections

next, we'll combine all the section plus the raw RasterData which will contain our payload

next to test if we can control execution's flow, we'll send the usual cyclic payload

however after a few run, most of the time it crashes because of a pointer dereference, I'm not sure why and what part causes it, but I decided to not care about it.

the time it succeded we get the offset of 488

due to the unreliableness, I decided to test it a few more times and found out that there would be occurrences where the offset will be different such as follow

to accomodate for it, at the start of the payload I sprayed a bunch of ret gadget to act as a ret slep.

next, I'll use the exact same method and gadget I explained in my previous writeuparrow-up-right to write the string path to flag.txt

and the rest of the payload would be the usual ORW.

the reason why I didn't decide to execve and spawn a shell is because the challenge is interfaced through a website where we would upload a .bmp and it will then ran againts the program, then the output will be given back to us.

here's the final payload being given to the site:

below is the full exploit:

yqroo wants a job

Analysis

we're given another binary this time with no source code

the binary itself is made out of assembly

seeing the other sections in ghidra I noticed a suspicious part

turns out it's a bunch of gadgets

running the program, it wil give a stack leak which is the address where our buffer starts

Exploitation

so the goal is quite simple, we have a buffer overflow and somehow we need to chain the gadgets to achieve code execution.

first thing to note is that the program doesn't return but rather jump

let's do the basic cyclic test with cyclic(0x200)

as you can see, our payload overflowed 16 bytes in total, with the first 8 bytes being the address where we want to jump.

this is relevant because notice in our gadget we have bunch of pop gadgets but they will be no use if we can't control what's being popped.

in order to call execve, we need to control RAX, RSI, RSI and RDX. after a bit of thought and trial error, two of these gadget are enough:

  • Gadget 1:

  • Gadget 2:

through the first gadget, we will able to control all of the registers but RAX, which will be controlled through the second gadget.

do notice that we can control RAX in the second gadget if we are able to control RCX in the first gadget.

first since the overflow is not enough to fully utilize the pop gadgets, we will need to do a stack pivot to the start of our payload. to do this let's calculate the offset from the leaked stack address

with that we're able to control RDX, RDI and RSI

next we'll discuss what to set those register with

  • RDX

this is quite meaningless so we'll set it to NULL

  • RSI

RSI is quite important as it is how we'll able to chain to the next gadget, it has to contain an address which contain a pointer to our next gadget as it is a jump dereference

  • RDI

same as RSI, however this is chained in our second gadget:

the target where we wanna jump to is of course, the syscall call.

  • RCX

RCX is relevant because it's what's will control RAX in the second gadget:

in the last screenshot, RAX was 0x73 thus to achieve RAX = 0x3b, RCX must be 0x38

combining all our payload now would be:

and we're able to hit execve, one small thing is that now RDI points to memory that contains one of our address, we can simply fix this by adjusting the offset where our pointer and the /bin/sh is located

and thus pwned

here's the full exploit:

Last updated