现充|junyu33

Part of the solution of pwncollege

Point to https://dojo.pwn.college/challenges

As per the website requirements, only the solution ideas and exploits for the first two challenges are provided here.

heap

babyheap1.0~2.1——UAF

def exploit():
   malloc(flag_size)
   free()
   read_flag()
   puts()

babyheap3.0~3.1——uaf2.0

Note: tcache uses a LIFO structure.

babyheap4.0&4.1—tcache double free

This is the first time I've independently solved a heap challenge, even though it's the simplest type of tcache double free.

babyheap5.0 & 5.1 — Unsorted Bin Attack

libc 2.23 → libc 2.31

By exhausting all tcache bins, small bins and unsorted bins are triggered, allowing for chunk modification.

babyheap6.0&6.1——tcache poisoning

Simple tcache poisoning

https://wargames.ret2.systems/level/how2heap_tcache_poisoning_2.31

babyheap7.0&7.1——tcache poisoning 2.0

Since the flag remains unchanged and only the latter part of the secret is needed, simply set sec_addr += 8 and rerun the program.

babyheap8.0&8.1——Brute Force

Using the previous method, the last 12 bits of the secret can be leaked, while the first 4 bits can be obtained through brute force enumeration.

The script took an entire class period to run before producing the result.

babyheap9.0——tcache double free

Due to the UAF (Use-After-Free) vulnerability, after the first free operation, setting the next pointer to 0 enables a double free.

At this point, if the next pointer is modified to 0x42b321, the key can be observed in the provided heap information.

babyheap9.1—tcache double free | perthread corruption

Following the steps from the previous level, proceed to malloc twice. Although the challenge prevents you from reading pointers near the key, the malloc operation still executes.

Due to the linked list nature of tcache, the value at addr+8 will be zeroed out. After performing this operation once more, you can pass the verification by entering 16 \0 characters.

In theory, it is possible to modify the address to the perthread struct and read the 8-byte key at the head of the linked list, though this approach was not tested.

babyheap10.0&10.1——Heap on Stack

The stack address and ELF address are provided, so the heap can be directly placed on the stack, and the return address can be modified accordingly.

babyheap11.0&11.1——free_hook + arbitrary read

Since the ELF address and stack address were unknown, I first overwrote free_hook (note: when writing to free_hook, it's best to set the size field to \x7f) to point to system_plt, which granted a shell but without sufficient permissions.

Then, I discovered that executing the echo function would allocate an additional heap chunk containing addresses related to the ELF and the stack. Moreover, the echo function did not restrict the offset size, allowing these addresses to be retrieved.

Since I already had the ability to write to free_hook, I directly overwrote free_hook with the address of win to achieve the desired outcome.

babyheap12.0&12.1—modify size

Due to the mechanism of tcache, modifying the size of a heap chunk on the stack can bypass validity checks.

babyheap13.0——tcache double free

Similar to the approach in babyheap9.1, allocate memory near sec, then input a series of \0 to overwrite the key.

babyheap13.1—Modify Size

Since the offset between sec and the heap does not exceed one byte, after modifying the size of the heap on the stack, you can use malloc to allocate a slightly larger heap to fill with data.

babyheap14.0&14.1—modify size + leak elf & canary

Based on level13, use echo to leak the elf address and canary address, then perform stack overflow on the heap.

For 14.1, note that \x09 is not a valid input; it is recommended to use a later address instead.

This challenge is a bit tricky. I ended up leaking the ELF, stack, libc, and heap before realizing the UAF was already gone.

For arbitrary write, aside from double free, unlink is also an option. I didn't want to bother with intricate heap feng shui, so I just went with unlink to wrap things up.

The libc version used in the following program has been upgraded from 2.31 to 2.35.

Differences between glibc 2.35 and 2.31 include:

  • Safe-linking — The actual address is the encrypted value XORed with the previous item shifted right by 12 bits (introduced in 2.32).
  • Malloc alignment check — Addresses must be aligned to 0x10 (introduced in 2.33).
  • Removal of hooks such as __malloc_hook, __free_hook, etc. (introduced in 2.34).

babyheap16.0—tcache double free + safe-linking

The logic is the same as level 9, except that an XOR operation is required when modifying the next pointer.

babyheap16.1—tcache perthread corruption + safe-linking

Due to alignment checks, it is not possible to zero out the value at addr. Here, tcache perthread corruption must be used instead, which allows allocating a chunk precisely at the key's location for reading.

Additionally, the read value must be XORed to obtain the final key.

babyheap17.0——Canary Leak

Allocate the heap at the location rbp-0x10, obtain the canary from the printed information, and then perform a stack overflow.

By leveraging the null-termination feature of scanf, the next_chunk_size is changed from 0x101 to 0x100, thereby achieving unlink.

Subsequently, an entry in the ptr pointer array is overwritten with the return address, and the return address is overwritten via scanf to point to the win function.

The approach is similar: after unlink, directly use puts to read the sec array.

babyheap18.1 — Fake Chunk

Fake a heap chunk and directly overwrite the sec array.

babyheap19.0&19.1——chunk overlapping

https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/chunk-extend-overlapping/#1inusefastbinextend

babyheap20.0&20.1——Safe-Linking + libc.environ + ORW ROP

Leak the libc address via the unsorted bin and obtain the heap address through a heap overflow.

Allocate four chunks. The first chunk overflows into the second, changing its size to three times the original. By printing and leaking the key pointer, overflow the second chunk while keeping the third chunk unchanged. Modify the next pointer of the fourth chunk (according to the Safe-Linking feature, this allows modifying the next pointer of the third chunk) to libc.environ ^ (heap_base >> 12). Then, perform two malloc operations and print to obtain libc.environ.

Use libc.environ to retrieve the stack address for unlink.

Since directly obtaining a shell cannot read the flag, ORW ROP is used here for reading.

Kernel

babykernel1.0~6.1—Basic Definition

Reference this blog post: https://www.cnblogs.com/crybaby/p/14431651.html

Kernel shellcode is quite tricky.

Note: How to check if KASLR is enabled?

After entering the kernel, use dmesg to check if nokaslr appears at the end of the second line.

babykernel7.0~7.1——struct&debug

You need to pass in a structure similar to the following:

struct shellcode{
    unsigned long length;
    char shellcode[0x1000];
    unsigned long* shellcode_addr;
}

Note the following points:

babykernel8.0~8.1—Shellcode in Shellcode

In a nutshell—writing shellcode within shellcode.

The first entry into the kernel executes commit_creds(prepare_kernel_cred(0)), followed by a ret2usr to obtain a root shell. Then, upon re-entering, execute run_cmd /bin/chmod 777 /flag to modify the flag's permissions. (Note: spawning a shell in kernel mode has no effect.)

babykernel9.0~9.1——run_cmd

Directly change the printk pointer to run_cmd, then use rdi as the input to write /bin/chmod 777 /flag.

babykernel10.0—kaslr leak

Note that kaslr is only re-randomized after a kernel reboot, so rerunning the program will not change the original kernel function addresses.

Similar to userland aslr, the lower 5 bits of kaslr are fixed. Writing 256 bytes causes the address of printk to be leaked, thereby allowing the address of run_cmd to be found.

babykernel10.1—Partial Overwrite

Since this driver does not print its own input, we choose to overwrite the last 3 bytes of printk, meaning we need to brute-force the sixth-to-last bit of run_cmd.

babykernel11.0~12.1 – Memory Scanning

The program loads the flag into memory and then deletes the flag file, so the flag can only be found in memory.

From the Discord spoiler, it is known that scanning should start from 0xffff888000000000 (i.e., the value of the macro phys_to_virt(0), which is also the value of __PAGE_OFFSET_BASE_L4 in the source code). Then, use dmesg to search for the flag.

Note the following points:

file

According to Linux philosophy, everything is a file, so stdin(0), stdout(1), stderr(2), etc., are all files.

babyfile_level1—Arbitrary Write to File

If you do nothing and break after fwrite, the FILE structure will look like this:

pwndbg> x/30gx 0x18203b0
0x18203b0:      0x00000000fbad2c84      0x0000000001820590
0x18203c0:      0x0000000001820590      0x0000000001820590
0x18203d0:      0x0000000001820590      0x0000000001820690
0x18203e0:      0x0000000001821590      0x0000000001820590
0x18203f0:      0x0000000001821590      0x0000000000000000
0x1820400:      0x0000000000000000      0x0000000000000000
0x1820410:      0x0000000000000000      0x00007fb7069655c0
0x1820420:      0x0000000000000004      0x0000000000000000
0x1820430:      0x0000000000000000      0x0000000001820490
0x1820440:      0xffffffffffffffff      0x0000000000000000
0x1820450:      0x00000000018204a0      0x0000000000000000
0x1820460:      0x0000000000000000      0x0000000000000000
0x1820470:      0x00000000ffffffff      0x0000000000000000
0x1820480:      0x0000000000000000      0x00007fb7069614a0
0x1820490:      0x0000000000000000      0x0000000000000000

So you only need to modify the values from file+8 to file+0x48 to the desired write location.

Among these, 0xfbad2c00 is the magic number.

def exploit():
   fake_file = p64(0xfbad2c00) + p64(0x4040e0)*4 + p64(0x4041e0) + p64(0x4050e0) + p64(0x4040e0) + p64(0x4050e0)
   io.send(fake_file)

Finally, cat /tmp/babyfile.txt

babyfile_level2—Arbitrary Read from File

This time it's an arbitrary read:

pwndbg> x/30gx 0x6483b0
0x6483b0:       0x00000000fbad0230      0x0000000000000000
0x6483c0:       0x0000000000000000      0x0000000000648590
0x6483d0:       0x0000000000000000      0x0000000000000000
0x6483e0:       0x0000000000000000      0x0000000000648590
0x6483f0:       0x0000000000649590      0x0000000000000000
0x648400:       0x0000000000000000      0x0000000000000000
0x648410:       0x0000000000000000      0x00007fc110d2e5c0
0x648420:       0x0000000000000003      0x0000000000000000
0x648430:       0x0000000000000000      0x0000000000648490
0x648440:       0xffffffffffffffff      0x0000000000000000
0x648450:       0x00000000006484a0      0x0000000000000000
0x648460:       0x0000000000000000      0x0000000000000000
0x648470:       0x00000000ffffffff      0x0000000000000000
0x648480:       0x0000000000000000      0x00007fc110d2a4a0
0x648490:       0x0000000000000000      0x0000000000000000

Here, 0xfbad000 is the magic number.

Note that the size of _IO_buf_end - _IO_buf_base must be greater than (not equal to) the size of the buffer.

def exploit():
   fake_file = p64(0xfbad0000) + p64(0)*2 + p64(0x4041f8) + p64(0)*3 + p64(0x4041f8) + p64(0x4051f8)
   io.send(fake_file)

babyfile_level3——fileno redirection

Simply modify _fileno to standard output 1.

babyfile_level4——fileno redirection

PIE is not enabled. Redirect to input and enter the win address.

babyfile_level5—arbitrary write to file

Same as level1.

babyfile_level5——arbitrary read from file

Same as level2.