TOOR

[TOOR] 9.3. aslr_2 write up (미완)

lmxx 2023. 9. 24. 00:01
728x90
반응형

Full Code

#include <err.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stddef.h>

#define INSN_ENDBR64 (0xF30F1EFA) /* endbr64 */
#define CFI(f)                                              \
  ({                                                        \
    if (__builtin_bswap32(*(uint32_t*)(f)) != INSN_ENDBR64) \
      __builtin_trap();                                     \
    (f);                                                    \
  })

#define KEY_SIZE 0x20
typedef struct {
  char key[KEY_SIZE];
  char buf[KEY_SIZE];
  const char *error;
  int status;
  void (*throw)(int, const char*, ...);
} ctx_t;

void read_member(ctx_t *ctx, off_t offset, size_t size) {
  if (read(STDIN_FILENO, (void*)ctx + offset, size) <= 0) {
    ctx->status = EXIT_FAILURE;
    ctx->error = "I/O Error";
  }
  ctx->buf[strcspn(ctx->buf, "\n")] = '\0';

  if (ctx->status != 0)
    CFI(ctx->throw)(ctx->status, ctx->error);
}

void encrypt(ctx_t *ctx) {
  for (size_t i = 0; i < KEY_SIZE; i++)
    ctx->buf[i] ^= ctx->key[i];
}

int main() {
  ctx_t ctx = { .error = NULL, .status = 0, .throw = err };

  read_member(&ctx, offsetof(ctx_t, key), sizeof(ctx));
  read_member(&ctx, offsetof(ctx_t, buf), sizeof(ctx));

  encrypt(&ctx);
  write(STDOUT_FILENO, ctx.buf, KEY_SIZE);

  return 0;
}

code analysis

#define KEY_SIZE 0x20
typedef struct {
  char key[KEY_SIZE]; 
  char buf[KEY_SIZE]; 
  const char *error; 
  int status; 
  void (*throw)(int, const char*, ...);
} ctx_t;

구조체 는 이렇다.
크기는 0x58 정도 이다.

int main() {
  ctx_t ctx = { .error = NULL, .status = 0, .throw = err }; 

  read_member(&ctx, offsetof(ctx_t, key), sizeof(ctx)); 
  read_member(&ctx, offsetof(ctx_t, buf), sizeof(ctx));

  encrypt(&ctx);
  write(STDOUT_FILENO, ctx.buf, KEY_SIZE);

  return 0;
}

ctx 는 error 에 NULL, status 에 0 throw 에 err 로 초기화 된다.

다음에 read_member 로 입력을 받는데
ctx_t 구조체의 key 위 offset 을 offset 인자로 들어간다.
size로 ctx 가 들어간다.

void read_member(ctx_t *ctx, off_t offset, size_t size) {
  if (read(STDIN_FILENO, (void*)ctx + offset, size) <= 0) { 
    ctx->status = EXIT_FAILURE;
    ctx->error = "I/O Error"; 
  }
  ctx->buf[strcspn(ctx->buf, "\n")] = '\0'; 

  if (ctx->status != 0) 
    CFI(ctx->throw)(ctx->status, ctx->error); 
}

read_member 함수에서는
ctx+offset 으로 입력을 받는다.
근데 여기서 size 가 ctx 구조체의 크기이기 때문에 bof가 발생할 수 있다.

#define INSN_ENDBR64 (0xF30F1EFA) /* endbr64 */
#define CFI(f)                                              \
  ({                                                        \
    if (__builtin_bswap32(*(uint32_t*)(f)) != INSN_ENDBR64) \
      __builtin_trap();                                     \
    (f);                                                    \
  })

CFI 매크로의 경우에는 throw 로 넘겨진 주소를 호출한다.
이떄 bswap 해서 endbr64 가 아니면 trap을 건다.

	read_member(&ctx, offsetof(ctx_t, buf), sizeof(ctx)); 

ctx 구조체의 buf 멤버에 입력을 한다.

void encrypt(ctx_t *ctx) { 
  for (size_t i = 0; i < KEY_SIZE; i++)
    ctx->buf[i] ^= ctx->key[i]; // buf key 인덱스 해서 xor 작업 해서 buf에 넣는다.
}

이후에 encrypt 함수로
buf와 key 를 xor 해서 buf 에 넣는다.

	write(STDOUT_FILENO, ctx.buf, KEY_SIZE); 

이후에 write 함수로 ctx의 buf 를 출력해준다.

solution

ctx 의 status 가 0이 아니라면 CFI 메크로가 실행 되기 때문에
status 에 0이 아닌 값을 준다.

CFI 메크로에서 bswap32 조건을 통과 해야 하는데 이는 endbr64 명령줄이 있는 함수로 throw 를 줘야 한다.

그래서 그냥 system 함수를 throw 로 주고 const char * 형인 error 에 “/bin/sh\\x00” 을 줬는데 endbr 을 통과 못한다.

bswap 을 하면 endbr64 인 0xf30f1efa 가 아닌 0xcc0f1efa 가 되는데 cc 가 어디서 나온 것 인지 모르겠다.

이 글은 옵시디언을 이용해서 작성되었습니다.

728x90
반응형