现充|junyu33

unctf2022pwn wp

Although I managed to pwn all challenges, the pwn player still lost badly.

The Ops level is quite frustrating; please stop putting files on Baidu Netdisk in the future.

pwn

welcomeUNCTF2022

int func()
{
  char s2[11]; // [esp+Ch] [ebp-1Ch] BYREF
  char s[13]; // [esp+17h] [ebp-11h] BYREF

  strcpy(s2, "UNCTF&2022");
  puts("Welcome to UNCTF2022 Please enter the password:");
  gets(s);
  if ( !strcmp(s, s2) )
    return system("cat /flag");
  else
    return puts("wrong!!!");
}

Simple check to test if netcat (nc) is working properly and the target machine status.

Rock Paper Scissors

Main Section

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+1Fh] [rbp-1h] BYREF

  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  srand(0xAu);
  puts("Do you know \x1B[1;36m\"rand()\"\x1B[0m ?");
  puts("In order to help you,I can tell you a secret!!!But you need to answer my question");
  puts("Will you learn about something of pwn later?(y/n)");
  __isoc99_scanf("%c", &v4);
  if ( v4 != 0x79 )
  {
    puts("lazy dog,you can't know my secret and get out!!!");
    exit(0);
  }
  puts("good boy,I trust you");
  puts("the secret is:");
  puts("I have set a seed for \x1B[1;36m\"srand()\"?\x1B[0m");
  puts("I'm so happy.Come and play games with me");
  playgame("I'm so happy.Come and play games with me");
  return 0;
}

Game Section

__int64 playgame()
{
  int v1; // [rsp+4h] [rbp-Ch]
  int v2; // [rsp+8h] [rbp-8h]
  int i; // [rsp+Ch] [rbp-4h]

  rules();
  for ( i = 1; i <= 100; ++i )
  {
    printf("\x1B[1;31mround[%d]\x1B[0m\n", (unsigned int)i);
    v2 = input();
    v1 = rand() % 3;
    if ( !v2 && v1 != 1 || v2 == 1 && v1 != 2 || v2 == 2 && v1 )
      gameover();
    puts("success!!!");
  }
  return backdoor();
}

Given that the random seed is set, simply generate a corresponding random number sequence under the same seed in a Linux environment, and input the winning choices accordingly.

move your heart

The main function follows the same logic as the second question and is omitted.

ssize_t back()
{
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  printf("gift:%p\n", buf);
  return read(0, buf, 0x30uLL);
}

This is a stack pivoting scenario. The payload is roughly:

p64(pop_rdi)+p64(buf+0x18)+p64(system_plt)+b'/bin/sh\0'+p64(buf)+p64(leave_ret)

It fits just right.

Check-in

Pretty interesting, covering integer overflow and exception handling.

Integer Overflow

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char nptr[32]; // [rsp+0h] [rbp-50h] BYREF
  char buf[44]; // [rsp+20h] [rbp-30h] BYREF
  size_t nbytes; // [rsp+4Ch] [rbp-4h]

  in1t(argc, argv, envp);
  puts("name: ");
  read(0, buf, 0x10uLL);
  buf[16] = 0;
  puts("Please input size: ");
  read(0, nptr, 8uLL);
  if ( atoi(nptr) > 32 || nptr[0] == '-' )
  {
    puts("No!!Hacker");
    exit(0);
  }
  LODWORD(nbytes) = atoi(nptr);
  read(0, nptr, (unsigned int)nbytes);
  return 0;
}

The buf variable is not particularly useful here, so we can ignore it for now.

Note that in the second-to-last line, the byte count is cast to an unsigned type, while atoi converts to int. This suggests a potential integer overflow vulnerability.

Since the first character cannot be a minus sign, it is possible to bypass this check by adding a space, which does not affect the result of atoi.

sigsegv

At this point, we can read data of unlimited length and can use methods like ROP to solve the challenge.

However, note that there is a sigsegv_handler function in 1nit, which means that upon receiving SIGSEGV, the flag is directly output.

void __noreturn sigsegv_handler()
{
  fprintf(stderr, "%s\n", flag);
  fflush(stderr);
  exit(1);
}

Therefore, simply inputting random data to overflow the stack is sufficient.

int_0x80

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+Ch] [rbp-124h]
  char buf[264]; // [rsp+10h] [rbp-120h] BYREF
  unsigned __int64 v6; // [rsp+118h] [rbp-18h]

  v6 = __readfsqword(0x28u);
  initial(argc, argv, envp);
  mmap((void *)0x10000, 0x1000uLL, 7, 34, -1, 0LL);
  puts("hello pwn");
  read(0, buf, 0x100uLL);
  for ( i = 0; i < strlen(buf) - 1; ++i )
  {
    if ( ((*__ctype_b_loc())[buf[i]] & 0x4000) == 0 )
      exit(0);
  }
  strcpy((char *)0x10000, buf);
  MEMORY[0x10000]();
  return 0;
}

Alphanumeric shellcode

You can use the AE64 library, remember to set the parameter to rsi.

fakehero

Suggest renaming it to fakeheap (As the name implies, a fake heap problem).

add Operation

__int64 __fastcall add(void **a1)
{
  void **v1; // rbx
  int v3; // [rsp+1Ch] [rbp-124h] BYREF
  char buf[268]; // [rsp+20h] [rbp-120h] BYREF
  __int64 size[2]; // [rsp+12Ch] [rbp-14h] BYREF

  v3 = 0;
  puts("Hi! Welcome to UNCTF!");
  puts("index: ");
  __isoc99_scanf("%u", &v3);
  puts("Size: ");
  __isoc99_scanf("%u", size);
  if ( LODWORD(size[0]) > 0x100 )
    error();
  v1 = &a1[v3];
  *v1 = malloc(LODWORD(size[0]));
  puts("Content: ");
  read(0, buf, 0x100uLL);
  memcpy(a1[v3], buf, LODWORD(size[0]));
  return 0LL;
}

Note that buf is copied directly from the stack and is not zeroed out, which allows leaking libc and ELF addresses. But this doesn't really matter.

The more important vulnerability is the lack of bounds checking on idx.

free&show Operation

__int64 __fastcall delete(void **a1)
{
  int v2; // [rsp+1Ch] [rbp-4h] BYREF

  puts("Index: ");
  __isoc99_scanf("%u", &v2);
  if ( !a1[v2] )
    error();
  puts((const char *)a1[v2]);
  free(a1[v2]);
  puts("Don't be far away from me.");
  return 0LL;
}

There's not much to say—it's a classic Use-After-Free (UAF) scenario. There is no way to edit the content in the heap after it is freed, and since there is no heap overflow earlier, it is basically impossible to perform a double free.

Solution

Observing the initialization function, the heap is executable here.

void init()
{
  int v0; // [rsp+14h] [rbp-Ch]
  unsigned __int64 ptr; // [rsp+18h] [rbp-8h]

  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  ptr = (unsigned __int64)malloc(0x1000uLL);
  v0 = sysconf(30);
  if ( v0 == -1 )
    perror("[-] sysconf failed");
  if ( mprotect((void *)(ptr & 0xFFFFFFFFFFFFF000LL), v0, 7) < 0 )
    perror("[-] mprotect failed");
  free((void *)ptr);
}

Through dynamic debugging, it is found that the pointer struct is relatively close to the return address. One can consider overwriting the return address to a heap address where shellcode has been written, and then exit to execute it.