Skip to content
KitploitKITPLOIT
ToolsBlog
Log in
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2021-4034-CTF-writeup — CTF pwn challenge writeup exploiting CVE-2021-4034 (pkexec) via heap manipulation, with Ghidra-based reverse engineering and a custom shelly.so helper binary. | Kitploit
Tools/GitHubGitHub/wechicken456/cve-2021-4034-ctf-writeup
ExploitationReverse EngineeringCTFLearning & EducationBinary ExploitationLabs & Practice
GitHubwechicken456/cve-2021-4034-ctf-writeup

CVE-2021-4034-CTF-writeup

CTF pwn challenge writeup exploiting CVE-2021-4034 (pkexec) via heap manipulation, with Ghidra-based reverse engineering and a custom shelly.so helper binary.

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
View Repository
2112 years agoNot yet reviewed
Share

CVE-2021-4034-CTF-writeup

This is a CTF pwn challenge that I wrote in C which requires the user to exploit the CVE-2021-4034 vulnerability. Players are given 2 binaries in the challenge directory in this repo. The chal binary implements the CTF challenge and the shelly.so is a helper binary.

How to emulate this challenge

At the time of writing this writeup, the Dockerfile is still not complete. The Dockerfile is required to deploy this challenge during a live CTF, but not locally: You can still emulate this challenge locally by setting up the user permissions and installing the vulnerable packages as follows:

  1. In order to successfully exploit this vulnerability, players who are not on a Linux machine should first install a Linux VM. Then, you will need to install a vulnerable kernel. Instructions on how to that: [https://askubuntu.com/a/700221]
  2. Install the packages libpolkit-gobject-1-0=0.105-26ubuntu1 libpolkit-agent-1-0=0.105-26ubuntu1 policykit-1=0.105-26ubuntu1.
  3. Create a flag.txt file owned by root:root in the current directory.
  4. Create an unprivileged user. Switch to this user.
  5. Download the files in the challenge folder to the current directory.
  6. Run chal as the unprivileged user.

Blind Analysis

Running the chal binary gives us a vague idea of what this binary does:

WELCOME TO THE HUB CTRL+ALT+DELICIOUS
We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every byte

1. ENTER THE HUB
2. QUIT
1
Order number: 0x7ffde93681f0
Enter your name: tin
1. ADD NEW ORDER
2. EDIT ORDER
3. SHOW ORDER
4. CANCEL ORDER
5. CHECKOUT
6. DONE
1
Pick your bread: aaaa
Select your spread: bbbb
Choose your veg: cccc
Slam your meat & egg: dddc
Any side notes for the cook? 0000
1. ADD NEW ORDER
2. EDIT ORDER
3. SHOW ORDER
4. CANCEL ORDER
5. CHECKOUT
6. DONE
1
Pick your bread: AAAA
Select your spread: BBBB
Choose your veg: CCCC
Slam your meat & egg: DDDD
Any side notes for the cook? 1111
1. ADD NEW ORDER
2. EDIT ORDER
3. SHOW ORDER
4. CANCEL ORDER
5. CHECKOUT
6. DONE
3
Enter order index: 0
aaaa, bbbb, cccc, dddc, 0000
1. ADD NEW ORDER
2. EDIT ORDER
3. SHOW ORDER
4. CANCEL ORDER
5. CHECKOUT
6. DONE
3
Enter order index: 1
AAAA, BBBB, CCCC, DDDD, 1111
1. ADD NEW ORDER
2. EDIT ORDER
3. SHOW ORDER
4. CANCEL ORDER
5. CHECKOUT
6. DONE
4
Enter order index: 1
1. ADD NEW ORDER
2. EDIT ORDER
3. SHOW ORDER
4. CANCEL ORDER
5. CHECKOUT
6. DONE
3
Enter order index: 1
Invalid index!
1. ADD NEW ORDER
2. EDIT ORDER
3. SHOW ORDER
4. CANCEL ORDER
5. CHECKOUT
6. DONE
5
/notes ./tin/notes
1. ADD NEW ORDER
2. EDIT ORDER
3. SHOW ORDER
4. CANCEL ORDER
5. CHECKOUT
6. DONE
6
1. ENTER THE HUB
2. QUIT
2
Come again :)

Playing around with the input size in the Add function and the indices of the Cancel functions don't give us anything special (no overflow or segmentation fault). Though, some interseting things:

  • It looks like the orders are place in a list (linked list perhaps) and they are 0-indexed?
  • There is this Order number: 0x7ffde93681f0 which seems to print some location on the stack?
  • Also, the program creates a directory with our input name along with 3 executables, one of which is the helper binary file we are given:
peasant@Tin-VM:~/Desktop$ ls
chal  chal.c  Dockerfile shelly.so  solve.py  tin
peasant@Tin-VM:~/Desktop$ ls -l tin/
total 28
-rwxrwx--- 1 peasant vboxsf     5 Feb  4 14:33 notes
-rwxrwx--- 1 peasant vboxsf    20 Feb  4 14:33 recipe
-rwxr-x--- 1 peasant vboxsf 16488 Feb  4 14:33 shelly.so
peasant@Tin-VM:~/Desktop$ cat tin/notes 
0000
peasant@Tin-VM:~/Desktop$ cat tin/recipe 
aaaa
bbbb
cccc
dddc

The executables contain our input.


Ghidra analysis

You can play around with the other functions and hope that you might run into some bugs (which is probable), but I'm gonna cut to the chase and open the program in Ghidra.

Comparing the strings that appear while running the program and the strings that are present in Ghidra, we can rename some of the FUN_* functions into familiar names:

undefined8 main(void)

{
  int iVar1;
  size_t sVar2;
  undefined2 *puVar3;
  long in_FS_OFFSET;
  int opt;
  int local_1c;
  char *local_18;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_18 = "/recipe";
  print("WELCOME TO THE HUB CTRL+ALT+DELICIOUS\n");
  print(
       "We\'re not just a sandwich hub. We are the beacon of flavors, serving a symphony in every by te\n\n"
       );
  while( true ) {
    print("1. ENTER THE HUB\n");
    print("2. QUIT\n");
    __isoc99_scanf(&DAT_001030c5,&opt);
    getc(stdin);
    if (opt != 1) break;
    printf("Order number: %p\n",&local_18);
    print("Enter your name: ");
    __isoc99_scanf(&DAT_0010334f,&DAT_00105120);
    sVar2 = strlen(&DAT_00105120);
    puVar3 = (undefined2 *)malloc(sVar2 + 2);
    DAT_00105100 = puVar3;
    *puVar3 = 0x2f2e;
    *(undefined *)(puVar3 + 1) = 0;
    strcpy((char *)(DAT_00105100 + 1),&DAT_00105120);
    iVar1 = FUN_001022f0(DAT_00105100,&DAT_00105060);
    if (iVar1 == -1) {
      mkdir((char *)DAT_00105100,0x1c0);
    }
    DAT_00105140 = 0;
    order_cnt = 0;
    for (local_1c = 0; local_1c < 10; local_1c = local_1c + 1) {
      *(undefined8 *)(&ptr_array + (long)local_1c * 8) = 0;
    }
    main_menu();
  }
  print("Come again :)\n");

The printf("Order number: %p\n",&local_18); prints out a location of a local variable on the stack.

A quick check in gdb shows us that the leaked address is the address of the pointer to the constant string /recipe:

...
Order number: 0x7fffffffdfc0
...
gef➤  x/gx 0x7fffffffdfc0
0x7fffffffdfc0:	0x0000555555559020
gef➤  x/s 0x0000555555559020
0x555555559020:	"/recipe"

We see a mkdir call, which creates a directory with our input name in the current directory.

These are consistent with our observation when we ran the program. Then it initializes some variable before calling the main_menu function, which looks something like:

  while( true ) {
    while( true ) {
      while( true ) {
        while( true ) {
          while( true ) {
            while( true ) {
              print("1. ADD NEW ORDER\n");
              print("2. EDIT ORDER\n");
              print("3. SHOW ORDER\n");
              print("4. CANCEL ORDER\n");
              print("5. CHECKOUT\n");
              print("6. DONE\n");
              __isoc99_scanf(&DAT_001030c5,&local_40);
              getc(stdin);
              if (local_40 != 1) break;
              add_order();
            }
            if (local_40 != 2) break;
            edit_order();
          }
          if (local_40 != 3) break;
          show_order();
        }
        if (local_40 != 4) break;
        cancel_order();
      }
      if (local_40 != 5) break;
      checkout();
    }
    if (local_40 == 6) break;
    if (local_40 == 0x539) {
      print(
           "\nGORDON RAMSAY: Finally, a worthy opponent, our battle will be legendary! I BET YOU CAN \'T GUESS THE SECRET RECIPE.\n"
           );
      fgets(inp,0x20,stdin);
      getrandom(random-bytes,0x10,0);
      for (local_3c = 0; local_3c < 0x10; local_3c = local_3c + 1) {
        if (inp[local_3c] != random-bytes[local_3c]) {
          print("...*Nuh Uh!*...\n");
                    /* WARNING: Subroutine does not return */
          exit(0);
        }
        print("...*Ooh Yes.. sCruMpTioUs*...");
      }
      print("Fine... I\'ll give you a taste.\n");
      FUN_00101504();
    }

If you're not familiar with REV, this is the decompilation for the switch statement in C. There's one interesting option a.k.a 0x539. It allows the players to guess 0x10 random bytes. If all the bytes are equal, then it calls FUN_00101504(); which calls system("cat flag.txt");. Otherwise, the program exits.

Download Tool