nul.pw - 2022年1月
type: channel
link: https://www.nul.pw/2022/01/
description: 云虚拟化、操作系统与二进制安全笔记自留地
items: ssh client代码阅读 (unfinished)
items: xz 源码阅读 - 2
items: xz 源码阅读 - 1
items: psproc 源码阅读 - 5
items: psproc 源码阅读 - 4
items: psproc源码阅读 - 3
items: psproc源码阅读 - 2
items: psproc源码阅读 - 1

ssh client代码阅读 (unfinished)
type: item
date: 2022-01-29T09:41:00+00:00
description: 继续来读代码。今天读ssh client。在这之前，让我们先了解一下ssh协议。 SSH协议介绍如下： https://www.ssh.com/academy/ssh/protocol SSH协议是一个经典的CS模型，SSH的客户端主导连接设置过程，并使用公钥加密来验证SSH服务器的身份。 在设置阶段之后，SSH协议使用强对称加密和散列算法来确保客户端和服务器之间交换的数据的私密性和完整性。 1. 客户端链接服务器；（C->S） 2. 服务端发送服务端的公钥；（S->C） 3. 服务端和客户端互相沟通，产生一个安全通道；（C<->S） 4. 用户登录到服务端的操作系统里 SSH常用的协议包括: RFC 4251 - Secure Shell (SSH) 协议架构 RFC 4253 - Secure Shell (SSH) 传输层协议 RFC 4252 - Secure Shell (SSH) 身份验证协议 RFC 4254 - Secure Shell (SSH) 连接协议 以及一个基于SSH的，SFTP文件传输协议。看RFC不如直接看代码，开始。在ssh.c中有一个大型结构体session_state。connection_in和out两个变量用于保存通信时的文件描述符，connection_in用于读取；out用于写入；如果是socket的话，他们两个可以是同一个描述符。对应的，receive/send_context用于加解密时两个方向的上下文。input/output用于加解密时传输的原始数据。incoming/outgoing_packet是当前正在处理的收取/发送包。compression_buffer用于packet的加解密。 struct session_state { int connection_in; int connection_out; /* Protocol flags for the remote side. */ u_int remote_protocol_flags; /* Encryption context for receiving data. Only used for decryption. */ struct sshcipher_ctx *receive_context; /* Encryption context for sending data. Only used for encryption. */ struct sshcipher_ctx *send_context; /* Buffer for raw input data from the socket. */ struct sshbuf *input; /* Buffer for raw output data going to the socket. */ struct sshbuf *output; /* Buffer for the partial outgoing packet being constructed. */ struct sshbuf *outgoing_packet; /* Buffer for the incoming packet currently being processed. */ struct sshbuf *incoming_packet; /* Scratch buffer for packet compression/decompression. */ struct sshbuf *compression_buffer; #ifdef WITH_ZLIB /* Incoming/outgoing compression dictionaries */ z_stream compression_in_stream; z_stream compression_out_stream; #endif int compression_in_started; int compression_out_started; int compression_in_failures; int compression_out_failures; /* default maximum packet size */ u_int max_packet_size; /* Flag indicating whether this module has been initialized. */ int initialized; /* Set to true if the connection is interactive. */ int interactive_mode; /* Set to true if w…

xz 源码阅读 - 2
type: item
date: 2022-01-14T09:25:00+00:00
description: 上篇文章说到coder->sequence被设置成了“SEQ_BLOCK”。 // Fall through case SEQ_BLOCK: { const lzma_ret ret = coder->block_decoder.code( coder->block_decoder.coder, allocator, in, in_pos, in_size, out, out_pos, out_size, action); if (ret != LZMA_STREAM_END) return ret; // Block decoded successfully. Add the new size pair to // the Index hash. return_if_error(lzma_index_hash_append(coder->index_hash, lzma_block_unpadded_size( &coder->block_options), coder->block_options.uncompressed_size)); coder->sequence = SEQ_BLOCK_HEADER; break; } 随后，这里就开始调用block_decoder.code。前面他被设置成了block_decode： next->coder = coder; next->code = &block_decode; next->end = &block_decoder_end; coder->next = LZMA_NEXT_CODER_INIT; // Basic initializations coder->sequence = SEQ_CODE; coder->block = block; coder->compressed_size = 0; coder->uncompressed_size = 0; 因此，我们查看block_decode的代码。由于代码比较长，这里继续加序号阅读。 static lzma_ret block_decode(void *coder_ptr, const lzma_allocator *allocator, const uint8_t *restrict in, size_t *restrict in_pos, size_t in_size, uint8_t *restrict out, size_t *restrict out_pos, size_t out_size, lzma_action action) { lzma_block_coder *coder = coder_ptr; block_decode:1. 注意这里的coder->sequence是另一个“sequence”，它最开始是被初始化成SEQ_CODE的。因此第一步从调用coder->next.code开始。 coder = coder_ptr（参数1）是上一步的coder->block_decoder.coder，因此这个调用会调用：coder->block_decoder.coder->next.coder，这些decoder由filter决定。 switch (coder->sequence) { case SEQ_CODE: { const size_t in_start = *in_pos; const size_t out_start = *out_pos; const lzma_ret ret = coder->next.code(coder->next.coder, allocator, in, in_pos, in_size, out, out_pos, out_size, action); const size_t in_used = *in_pos - in_start; const size_t out_used = *out_pos - out_start; // NOTE: We compare to compressed_limit here, which prevents // the total size of the Block growing past LZMA_VLI_MAX. if (update_size(&coder->compressed_size, in_used, coder->compressed_limit) || update_size(&coder->uncompressed_size, out_used, coder->block->uncompressed_size)) return LZMA_DATA_ERROR; block_decod…

xz 源码阅读 - 1
type: item
date: 2022-01-13T11:33:00+00:00
description: 继续开坑xz。这次看的是https://sourceforge.net/projects/lzmautils/下的文件（不是go语言写的那个）。根据其示例代码，要解压xz程序，入口是“lzma_code”，让我们从这里开始。 先介绍一下背景。要解压xz文件，需要设置一个lzma_stream对象，并将其next_in设置为数据，avail_in设置为数据长度，然后调用lzma_code开始解析。 为了举例，先从I:\xz-5.2.5\doc\examples_old\xz_pipe_decomp.c:xz_decompress这个函数看起。 /* note: in_file and out_file must be open already */ int xz_decompress (FILE *in_file, FILE *out_file) { lzma_stream strm = LZMA_STREAM_INIT; /* alloc and init lzma_stream struct */ …… /* initialize xz decoder */ ret_xz = lzma_stream_decoder (&strm, memory_limit, flags); if (ret_xz != LZMA_OK) { fprintf (stderr, "lzma_stream_decoder error: %d\n", (int) ret_xz); return RET_ERROR_INIT; } lzma_stream_decoder 调用lzma_next_strm_init， extern LZMA_API(lzma_ret) lzma_stream_decoder(lzma_stream *strm, uint64_t memlimit, uint32_t flags) { lzma_next_strm_init(lzma_stream_decoder_init, strm, memlimit, flags); strm->internal->supported_actions[LZMA_RUN] = true; strm->internal->supported_actions[LZMA_FINISH] = true; return LZMA_OK; } 对应的，宏定义如下，所以相当于1、lzma_strm_init(strm);；2、lzma_stream_decoder_init(strm->internal->next, strm->allocator, ...args...);。 /// Initializes lzma_strm and calls func() to initialize strm->internal->next. /// (The function being called will use lzma_next_coder_init()). If /// initialization fails, memory that wasn't freed by func() is freed /// along strm->internal. #define lzma_next_strm_init(func, strm, ...) \ do { \ return_if_error(lzma_strm_init(strm)); \ const lzma_ret ret_ = func(&(strm)->internal->next, \ (strm)->allocator, __VA_ARGS__); \ if (ret_ != LZMA_OK) { \ lzma_end(strm); \ return ret_; \ } \ } while (0) #endif 调用的lzma_stream_decoder_init如下。 extern lzma_ret lzma_stream_decoder_init( lzma_next_coder *next, const lzma_allocator *allocator, uint64_t memlimit, uint32_t flags) { lzma_next_coder_init(&lzma_stream_decoder_init, next, allocator); if (flags & ~LZMA_SUPPORTED_FLAGS) return LZMA_OPTIONS_ERROR; lzma_next_coder_init也是一个宏，它设置next->init，也就是这里的strm->internal->next->init = …

psproc 源码阅读 - 5
type: item
date: 2022-01-13T07:04:00+00:00
description: 剩余的代码中，最重要的是show_one_proc，它读取结构体并解析其中的一项，调用其print函数打印到屏幕上。 /********** show one process (NULL proc prints header) **********/ //#define SPACE_AMOUNT page_size #define SPACE_AMOUNT 144 static char *saved_outbuf; void show_one_proc(const proc_t *restrict const p, const format_node *restrict fmt) { /* unknown: maybe set correct & actual to 1, remove +/- 1 below */ int correct = 0; /* screen position we should be at */ int actual = 0; /* screen position we are at */ int amount = 0; /* amount of text that this data is */ int leftpad = 0; /* amount of space this column _could_ need */ int space = 0; /* amount of space we actually need to print */ int dospace = 0; /* previous column determined that we need a space */ int legit = 0; /* legitimately stolen extra space */ int sz = 0; /* real size of data in outbuffer */ int tmpspace = 0; char *restrict const outbuf = saved_outbuf; static int did_stuff = 0; /* have we ever printed anything? */ p为-1时是其最后一次调用。如果仍然有内容，则打印剩余的内容。 if(-1==(long)p) { /* true only once, at the end */ if(did_stuff) return; /* have _never_ printed anything, but might need a header */ if(!--lines_to_next_header) { lines_to_next_header = header_gap; show_one_proc(NULL,fmt); } /* fprintf(stderr, "No processes available.\n"); */ /* legal? */ exit(1); } 如果是其他情况，则生成这样的递归栈输出fmt。fmt（format node）就是那一组预定义的标头和格式化的数组。 if(p) { /* not header, maybe we should call ourselves for it */ if(!--lines_to_next_header) { lines_to_next_header = header_gap; show_one_proc(NULL,fmt); } } did_stuff = 1; if(active_cols>(int)OUTBUF_SIZE) fprintf(stderr,_("fix bigness error\n")); /* print row start sequence */ for(;;) { legit = 0; if(fmt->next) { max_rightward = fmt->width; tmpspace = 0; } else { tmpspace = correct-actual; if (tmpspace<1) { tmpspace = dospace; max_rightward = active_cols-actual-tmpspace; } else { max_rightward = active_cols - ( (correct>actual) ? correct : actual ); } } if(max_rightward <= 0) max_rightward = 0; else if(max_rightward >= OUTBUF_SIZE) max_rightward = OUT…

psproc 源码阅读 - 4
type: item
date: 2022-01-12T08:21:00+00:00
description: 回到main中，还剩最后一点点代码： lists_and_needs(); finalize_stacks(); //<=== if(forest_type || sort_list) fancy_spew(); else simple_spew(); /* no sort, no forest */ show_one_proc((proc_t *)-1,format_list); /* no output yet? */ procps_pids_unref(&Pids_info); return 0; } finalize_stacks是一个……基本由宏组成的函数。中间重复的宏太多了我就删掉了。 static void finalize_stacks (void) { format_node *f_node; sort_node *s_node; #if (PIDSITEMS < 60) # error PIDSITEMS (common.h) should be at least 60! #endif /* first, ensure minimum result structures for items which may or may not actually be displayable ... */ Pids_index = 0; // needed by for selections chkREL(CMD) chkREL(ID_EGID) ……………… chkREL(extra) chkREL(noop) // now accommodate any results not yet satisfied f_node = format_list; while (f_node) { (*f_node->pr)(NULL, NULL); f_node = f_node->next; } s_node = sort_list; while (s_node) { if (s_node->xe) (*s_node->xe)(NULL, NULL); s_node = s_node->next; } procps_pids_reset(Pids_info, Pids_items, Pids_index); } 其中，chkREL的定义如下： #define namREL(e) rel_ ## e #define makEXT(e) extern int namREL(e); #define makREL(e) int namREL(e) = -1; #define chkREL(e) if (namREL(e) < 0) { \ Pids_items[Pids_index] = PIDS_ ## e; \ namREL(e) = (Pids_index < PIDSITEMS) ? Pids_index++ : rel_noop; } 展开一下就是： if(rel_XX < 0) { Pids_items[Pids_index] = PIDS_XX; rel_XX = (Pids_index < PIDSITEMS) ? Pids_index++ : rel_noop; } chkREL实际做的事情就是初始化Pids_items中各不同的rel_XXX项。然后，对format_list中的每一项，都调用其pr()来处理。pr其实就是print函数，调用snprintf向其outbuf来输出内容。然后，对sort_list中的每一项，调用其xe()来处理。最后，调用procps_pids_reset。之前看过一次就不再重复了。 回到main中，下一个函数是fancy_spew，当然仅当开启forest_type / sort_list后才调用。 if(forest_type || sort_list) fancy_spew(); //<--- else simple_spew(); /* no sort, no forest */ show_one_proc((proc_t *)-1,format_list); /* no output yet? */ procps_pids_unref(&Pids_info); return 0; } fancy_spew定义如下： /***** sorted or forest */ static void fancy_spew(void) { struct pids_fetch *pidread; enum pids_fetch_type which; proc_t *buf; int i, n = 0; which = (thread_flags & TF_loose_tasks) ? …

psproc源码阅读 - 3
type: item
date: 2022-01-11T07:54:00+00:00
description: 接着上一篇的来，首先这个ARG_SYSV的花括号是真的很风骚，我也是第一次看到把case放在if的花括号里面的。anyway，先看这两个分支共同会到达的部分parse_bsd_option。 case ARG_SYSV: if(!force_bsd) { /* else go past case ARG_BSD */ err = parse_sysv_option(); break; case ARG_BSD: if(force_bsd && !(personality & PER_FORCE_BSD)) return _("way bad"); } prefer_bsd_defaults = 1; err = parse_bsd_option(); break; BSD options的处理由一个大函数完成，这也是我们用ps时常用的语法。开头几句它检查命令的格式是否和设置冲突，然后对flag中每一个字符进行switch case来处理。 /************************* parse BSD options **********************/ static const char *parse_bsd_option(void) { const char *arg; const char *err; flagptr = ps_argv[thisarg]; /* assume we _have_ a '-' */ if(flagptr[0]=='-') { if(!force_bsd) return _("cannot happen - problem #1"); } else { flagptr--; /* off beginning, will increment before use */ if(personality & PER_FORCE_BSD) { if(!force_bsd) return _("cannot happen - problem #2"); } else { if(force_bsd) return _("second chance parse failed, not BSD or SysV"); } } while(*++flagptr) { switch(*flagptr) { case '0' ... '9': /* end */ 这里的处理没什么新意，挑几个之前没出现过的函数来读一下。 case 'O': /* end */ trace("O like o + defaults, add new columns after PID, also sort\n"); arg=get_opt_arg(); if(!arg) return _("format or sort specification must follow O"); defer_sf_option(arg, SF_B_O); return NULL; /* can't have any more options */ break; 首先是defer_sf_option。其实出现过了，但是当时我懒得看，现在看一下它在做什么。defer_sf_option是一个中等长度的函数，开头依然是熟悉的初始化，初始化要用到的sf_node结构体。支持的sort & format一共7种，在common.h中定义。 /* sorting & formatting */ /* U,B,G is Unix,BSD,Gnu and then there is the option itself */ #define SF_U_O 1 #define SF_U_o 2 #define SF_B_O 3 #define SF_B_o 4 #define SF_B_m 5 /* overloaded: threads, sort, format */ #define SF_G_sort 6 #define SF_G_format 7 /************ Main parser calls this to save lists for later **********/ /* store data for later and return 1 if arg looks non-standard */ int defer_sf_option(const char *arg, int source) { sf_node *sfn; char buf[16]; int dist; const format_struct *fs; int need_item = 1; sfn = xmalloc(sizeof(sf_node)); …

psproc源码阅读 - 2
type: item
date: 2022-01-10T11:00:00+00:00
description: main中接下来的函数都比较重要，所以这里就分段来介绍了。 arg_parse(argc,argv); /* check for invalid combination of arguments */ arg_check_conflicts(); 首先是arg_parse。 int arg_parse(int argc, char *argv[]) { const char *err = NULL; const char *err2 = NULL; ps_argc = argc; ps_argv = argv; thisarg = 0; if(personality & PER_FORCE_BSD) goto try_bsd; err = parse_all_options(); if(err) goto try_bsd; err = thread_option_check(); if(err) goto try_bsd; err = process_sf_options(); if(err) goto try_bsd; err = select_bits_setup(); if(err) goto try_bsd; choose_dimensions(); return 0; try_bsd: trace("--------- now try BSD ------\n"); reset_global(); reset_parser(); reset_sortformat(); format_flags = 0; ps_argc = argc; ps_argv = argv; thisarg = 0; /* no need to reset flagptr */ force_bsd=1; prefer_bsd_defaults=1; if(!( (PER_OLD_m|PER_BSD_m) & personality )) /* if default m setting... */ personality |= PER_OLD_m; /* Prefer old Linux over true BSD. */ /* Do not set PER_FORCE_BSD! It is tested below. */ err2 = parse_all_options(); if(err2) goto total_failure; err2 = thread_option_check(); if(err2) goto total_failure; err2 = process_sf_options(); if(err2) goto total_failure; err2 = select_bits_setup(); if(err2) goto total_failure; choose_dimensions(); return 0; total_failure: reset_parser(); if(personality & PER_FORCE_BSD) fprintf(stderr, _("error: %s\n"), err2); else fprintf(stderr, _("error: %s\n"), err); do_help(NULL, EXIT_FAILURE); } 先看第一部分，全局变量personality由set_personality设置，大体就是根据不同的操作系统和架构，来设置不同的参数。 int arg_parse(int argc, char *argv[]) { const char *err = NULL; const char *err2 = NULL; ps_argc = argc; ps_argv = argv; thisarg = 0; if(personality & PER_FORCE_BSD) goto try_bsd; 对BSD而言，其personality是包含PER_FORCE_BSD位的，但是对linux则没有。因此如果有这个位，则优先尝试bsd。 case_bsd: personality = PER_FORCE_BSD | PER_BSD_h | PER_BSD_m; prefer_bsd_defaults = 1; bsd_j_format = "FB_j"; bsd_l_format = "FB_l"; /* bsd_s_format not used */ bsd_u_format = "FB_u"; bsd_v_format = "FB_v"; return NULL; 否则继续执行parse_all_options()。 err = parse_all_option…

psproc源码阅读 - 1
type: item
date: 2022-01-10T06:47:00+00:00
description: 好久没有写文章了（2021年一年都没写……），随便开点新坑，从简单的代码来读起。 从psproc工程下的ps代码开始。 display.c: /***** no comment */ int main(int argc, char *argv[]) { atexit(close_stdout); myname = strrchr(*argv, '/'); if (myname) ++myname; else myname = *argv; Hertz = procps_hertz_get(); setlocale (LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); setenv("TZ", ":/etc/localtime", 0); 先从main看起，首先，atexit函数设置close_stdout为其退出时的处理函数，这个库函数确实少见。 ATEXIT(3) Linux Programmer's Manual ATEXIT(3) NAME atexit - register a function to be called at normal process termination SYNOPSIS #include <stdlib.h> int atexit(void (*function)(void)); 然后搜索argv[0]，并找到"/"之后的内容作为自己的文件名，如果没有就直接用argv[0]。 procps_hertz_get用于获取CPU的时钟频率（sysconf(_SC_CLK_TCK)），如果获取失败返回100。 然后设置区域信息，并设置环境变量TZ为/etc/localtime。 然后是一段信号处理的函数。将一些黑名单信号以外的信号传递给singal_handler。 #ifdef DEBUG init_stack_trace(argv[0]); #else do { struct sigaction sa; int i = 32; memset(&sa, 0, sizeof(sa)); sa.sa_handler = signal_handler; sigfillset(&sa.sa_mask); while(i--) switch(i) { default: sigaction(i,&sa,NULL); case 0: case SIGCONT: case SIGINT: /* ^C */ case SIGTSTP: /* ^Z */ case SIGTTOU: /* see stty(1) man page */ case SIGQUIT: /* ^\ */ case SIGPROF: /* profiling */ case SIGKILL: /* can not catch */ case SIGSTOP: /* can not catch */ case SIGWINCH: /* don't care if window size changes */ case SIGURG: /* Urgent condition on socket (4.2BSD) */ ; } } while (0); #endif 接下来是几个相对比较重要的处理代码。 reset_global(); /* must be before parser */ arg_parse(argc,argv); /* check for invalid combination of arguments */ arg_check_conflicts(); /* arg_show(); */ trace("screen is %ux%u\n",screen_cols,screen_rows); /* printf("sizeof(proc_t) is %d.\n", sizeof(proc_t)); */ trace("======= ps output follows =======\n"); 首先是reset_global()。global.c:reset_global用于初始化所有的环境变量。我们依次阅读它的代码。 global.c /************ Call this to reinitialize everything ***************/ void reset_global(void) { proc_t *p; int i; reset_selection_list(); >> static void reset_selection_list(void) { selection_node *old; selec…