one_byte
检查保护,开了地址随机化,没开canary
1 | [*] '/home/starrysky/beginctf/one_byte/pwn' |
查看程序,发现可以溢出一字节,也对应了题目的one-byte,程序会打开flag文件并且输出一字节,要读出flag必然需要printf,所以覆盖返回地址低位到printf处即可
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
exp如下
1 | from pwn import * |
gift_rop
打开程序,发现是静态编译的c
,所以可以通过ropgadget
直接获取ropchain
,指令
1 | ROPgadget --binary ./pwn --ropchain |
执行指令之后会返回很多gadget
地址以及一段现成的ropchain
,如下
1 | from struct import pack |
这段ropchain
是控制返回地址之后执行的内容,由于python
语法格式,要把p +=
前面的多余空格去掉,这段代码发过去会报错,因为最后一段重复执行add rax,1
,长度太长了,所以就需要从返回的gadget
中找到可替代的gadget
,格式就是将gadget
地址放在Q
后面。execve
的系统调用号在64位中是59,所以最后一段的目的就是把rax
加到59,但是返回的gadget
太多,可以用grep
筛选一下
1 | ROPgadget --binary ./pwn --ropchain | grep 'rax' |
找到以下两条gadget
,用这两条代替add rax, 1
凑到59
1 | 0x0000000000471267 : add rax, 2 ; ret |
exp如下
1 | from pwn import * |
由于程序中有close
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
所以getshell以后还需要重定向标准输出,输入exec 1>&0
即可
exec
- 代替
shell
执行命令,区别是shell
执行完之后会回到shell
,而exec
会直接退出。- 文件重定向,也就是
exec 1>&0
这样将文件描述符为1
的文件重定向到0
上标准输出
(close(1))
和标准错误(close(2))
,有shell
但获得不了输出,可以通过exec 1>&0
重定向
unhappy
这题就是写shellcode,但是shellcode里不能含有HAPYhapy
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
推荐一个网站https://shell-storm.org/online/Online-Assembler-and-Disassembler/
要在限制下写的话可以利用sub
和add
去凑出要实现的代码,比如add rdi,0x61
,可以写成add rdi, 0x60
add rdi,0x1
,但是这题有一个简便的方法就是输入shellcode
来执行read
,通过syscall
调用的read
就没有了check
的限制,但是第二次输入需要先sleep
停顿一下
先试着用execve(’/bin/sh’, 0,0)
来getshell
1 | add edi, 0x30 |
打到远程会发现没有cat flag
的权限,但是unhappy
有rws
权限,也就是执行unhappy
有root
权限,所以把shellcode
改成orw
即可,直接write
会报错,这里也学习到了一个知识点:
ssize_t sendfile(int out_fd,int in_fd,off_t* offset,size_t count);
sendfile
函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝
最终exp如下
1 | from pwn import * |
ezpwn
test command
不能用cat
和sh
(官方wp
指出test command
对指令过滤不严,也可以利用test command
),test filename
不能输入flag
,teat data
功能是输入一个index
,向数组该index
处输入一个字符,而程序中有shell
函数,所以调试一下计算返回地址到该数组之间的距离作为index
,把返回地址地位改成shell
函数低位即可,需要注意的是输入格式,行末要加回车不然会覆盖成回车
1 | unsigned __int64 main_loop() |
exp如下
1 | from pwn import * |
no_money
格式化字符串,但是禁用$
,一般用$
定位,本题中也学到一个知识点:’%p’ * n + ‘%hn’ = ‘%n$hn’
,利用这一点,先泄露程序基址加上shell
地址最后改返回地址低位为shell
地址即可
1 | from pwn import * |
cat
开了canary
但是没开pie
1 | checksec pwn |
程序中有后门函数,main
中向bss
段两个变量输入数据,vul
中向栈中输入数据
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
重点就是绕过canary
,这里学到一个知识点:strcat
会从第一个\x00
开始拼接,strcpy
会在末尾写\x00
,查看栈中数据
1 | pwndbg> stack |
利用:read3
覆盖掉dest到canary
末尾的\x00
,strcat
拼接会从canary
之后开始拼接,最后利用strcpy
将canary
末尾恢复成\x00
exp
1 | from pwn import * |
aladdin
非常…痛苦的一题,本地和远程的偏移竟然不一样…用不了ogg
,bss
段上的chance=4
,只能利用三次,所以这三次要先把chance
改掉
1 | while ( --chance ) |
查看栈结构,利用这几个地址来泄露一些需要的值:code_base、stack、libc_base
1 | pwndbg> stack |
输入的wish
是bss
段的,也就是非栈上的格式化字符串,需要用栈作为跳板去改其他地方的数据,本题的利用思路就是第一次输入用于泄露地址,第二次输入用来设置跳板,第三次输入用来改printf
返回地址到val
中的输入,继续输入改chance
值,但也不能改太大,用到的链如下
1 | 0x7fffffffdde8 —▸ 0x7fffffffded8 —▸ 0x7fffffffe260 ◂— '/home/starrysky/game_2024/beginctf/aladdin/pwn' |
修改成功后就可以无限利用fmt
,但这题有沙箱,只能orw
1 | aladdin seccomp-tools dump ./pwn |
但是把栈直接改orw
比较麻烦,所以可以在返回地址先构造read
,然后用read
读rop
链实现orw
exp
1 | from pwn import * |
zeheap
存在uaf
漏洞,但是edit
和show
受到mark
的限制
1 | unsigned __int64 delete() |
将unsorted bin
中的堆重复释放到tcache
中,就可以利用这个堆泄露libc
地址,并且将tcache
中的堆和unsorted bin
中的堆都申请出来就可以造成堆块复用,其中一个释放到tcache
再用另一个改fd
为free_hook
最后申请出来改成system
exp
1 | from pwn import * |