Write-Flag-Where2 [Google Ctf 2023]

pwn
writeup by: sunbather

Challenge Description

This challenge is not a classical pwn In order to solve it will take skills of your own An excellent primitive you get for free Choose an address and I will write what I see But the author is cursed or perhaps it’s just out of spite For the flag that you seek is the thing you will write ASLR isn’t the challenge so I’ll tell you what I’ll give you my mappings so that you’ll have a shot.

(this is the description from the first challenge, but it describes well what we have to do)

Solution

Basically we are given the following main function, that prints the mapping for the current process and then we’re given the possibility to write the flag to any address.

undefined8 main(void)
{
  int iVar1;
  ssize_t sVar2;
  undefined8 local_78;
  undefined8 local_70;
  undefined8 local_68;
  undefined8 local_60;
  undefined8 local_58;
  undefined8 local_50;
  undefined8 local_48;
  undefined8 local_40;
  uint local_2c;
  __off64_t local_28;
  int local_20;
  undefined4 local_1c;
  int local_18;
  int local_14;
  int local_10;
  int local_c;
  
  local_c = open("/proc/self/maps",0);
  read(local_c,maps,0x1000);
  close(local_c);
  local_10 = open("./flag.txt",0);
  if (local_10 == -1) {
    puts("flag.txt not found");
  }
  else {
    sVar2 = read(local_10,flag,0x80);
    if (0 < sVar2) {
      close(local_10);
      local_14 = dup2(1,10);
      local_18 = open("/dev/null",2);
      dup2(local_18,0x10);
      dup2(local_18,0x11);
      dup2(local_18,0x12);
      close(local_18);
      alarm(0);
      dprintf(local_14,
              "Was that too easy? Let\'s make it tough\nIt\'s the challenge from before, but I\'ve r emoved all the fluff\n"
             );
      dprintf(local_14,"%s\n\n",maps);
      while( true ) {
        local_78 = 0;
        local_70 = 0;
        local_68 = 0;
        local_60 = 0;
        local_58 = 0;
        local_50 = 0;
        local_48 = 0;
        local_40 = 0;
        sVar2 = read(local_14,&local_78,0x40);
        local_1c = (undefined4)sVar2;
        iVar1 = __isoc99_sscanf(&local_78,"0x%llx %u",&local_28,&local_2c);
        if ((iVar1 != 2) || (0x7f < local_2c)) break;
        local_20 = open("/proc/self/mem",2);
        lseek64(local_20,local_28,0);
        write(local_20,flag,(ulong)local_2c);
        close(local_20);
      }
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
    puts("flag.txt empty");
  }
  return 1;
}

The solution to the first challenge was to simply write the flag inside the string used in the puts to print it out. This time, nothing gets printed. So the next idea is to find a jump that we could modify to go somewhere up the .text section, possibly hitting the original dprintf of the maps. After looking for some time through the assembly and trying some jumps, we find this:

        00101428 8b 45 e8        MOV        EAX,dword ptr [RBP + local_20]
        0010142b 89 c7           MOV        EDI,EAX
        0010142d e8 4e fc        CALL       libc.so.6::close                                 int close(int __fd)
                 ff ff
        00101432 e9 1d ff        JMP        LAB_00101354
                 ff ff
                             LAB_00101437                                    XREF[1]:     001013d0(j)  
        00101437 90              NOP
        00101438 eb 01           JMP        LAB_0010143b
                             LAB_0010143a                                    XREF[1]:     001013d8(j)  
        0010143a 90              NOP

The first jump, at 00101432 is used to jump back to the beginning of the while loop. We can use it to maybe jump even higher. We try to overwrite the 1d byte (which is the relative offset of the jump) with the first byte of the flag, which is greater (C == 0x43). Sadly, this is not useful, it will jump even lower, because the offset is actually negative (see the ff bytes after, signifying it’s a negative value). So we would have to get a lower value than 1d, which sucks because ASCII letters start a lot higher. But, the flag should end with null bytes, so maybe we can overwrite the beginning of the flag with a null byte and then use that to overwrite the jump offset to null. Then, we just write the flag to maps and re-print it with dprintf. There is an issue with this though, now the flag won’t print because it has a null byte in the beginning. So we have to do it in another order:

  1. Write the flag to maps
  2. Write last byte of flag to first byte of flag (the null byte)
  3. Write the first byte of flag (null) to the 1d byte.
  4. ???
  5. Profit

This will jump higher and print the maps variable again, which now contains the flag. Great success!